From 7a372affd9eb4af18c3e2a0ce4d61f23ca1305d5 Mon Sep 17 00:00:00 2001
From: Mark Freeman
Date: Wed, 13 Aug 2025 14:55:50 -0400
Subject: go/types, types2: rename definedType to declaredType and clarify docs
declaredType seems a better name for this function because it is reached
when processing a (Named or Alias) type declaration. In both cases, the
fromRHS field (rather than the underlying field) will be set.
Change-Id: Ibb1cc338e3b0632dc63f9cf2fd9d64cef6f1aaa5
Reviewed-on: https://go-review.googlesource.com/c/go/+/695955
Auto-Submit: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Robert Griesemer
---
src/cmd/compile/internal/types2/decl.go | 4 ++--
src/cmd/compile/internal/types2/typexpr.go | 20 ++++++++++----------
src/go/types/decl.go | 4 ++--
src/go/types/typexpr.go | 20 ++++++++++----------
4 files changed, 24 insertions(+), 24 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index 91d2492a53..8f196ece61 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -532,7 +532,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
check.collectTypeParams(&alias.tparams, tdecl.TParamList)
}
- rhs = check.definedType(tdecl.Type, obj)
+ rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil)
alias.fromRHS = rhs
@@ -576,7 +576,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
check.collectTypeParams(&named.tparams, tdecl.TParamList)
}
- rhs = check.definedType(tdecl.Type, obj)
+ rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil)
named.fromRHS = rhs
diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go
index 8601ce6277..303f782ac4 100644
--- a/src/cmd/compile/internal/types2/typexpr.go
+++ b/src/cmd/compile/internal/types2/typexpr.go
@@ -16,7 +16,7 @@ import (
// ident type-checks identifier e and initializes x with the value or type of e.
// If an error occurred, x.mode is set to invalid.
-// For the meaning of def, see Checker.definedType, below.
+// For the meaning of def, see Checker.declaredType, below.
// If wantType is set, the identifier e is expected to denote a type.
func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType bool) {
x.mode = invalid
@@ -149,14 +149,14 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// typ type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type.
func (check *Checker) typ(e syntax.Expr) Type {
- return check.definedType(e, nil)
+ return check.declaredType(e, nil)
}
// varType type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type and it must not be a
// constraint interface.
func (check *Checker) varType(e syntax.Expr) Type {
- typ := check.definedType(e, nil)
+ typ := check.declaredType(e, nil)
check.validVarType(e, typ)
return typ
}
@@ -187,11 +187,11 @@ func (check *Checker) validVarType(e syntax.Expr, typ Type) {
}).describef(e, "check var type %s", typ)
}
-// definedType is like typ but also accepts a type name def.
-// If def != nil, e is the type specification for the type named def, declared
-// in a type declaration, and def.typ.underlying will be set to the type of e
-// before any components of e are type-checked.
-func (check *Checker) definedType(e syntax.Expr, def *TypeName) Type {
+// declaredType is like typ but also accepts a type name def.
+// If def != nil, e is the type specification for the [Alias] or [Named] type
+// named def, and def.typ.fromRHS will be set to the [Type] of e immediately
+// after its creation.
+func (check *Checker) declaredType(e syntax.Expr, def *TypeName) Type {
typ := check.typInternal(e, def)
assert(isTyped(typ))
if isGeneric(typ) {
@@ -230,7 +230,7 @@ func goTypeName(typ Type) string {
}
// typInternal drives type checking of types.
-// Must only be called by definedType or genericType.
+// Must only be called by declaredType or genericType.
func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) {
if check.conf.Trace {
check.trace(e0.Pos(), "-- type %s", e0)
@@ -296,7 +296,7 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *TypeName) (T Type) {
case *syntax.ParenExpr:
// Generic types must be instantiated before they can be used in any form.
// Consequently, generic types cannot be parenthesized.
- return check.definedType(e.X, def)
+ return check.declaredType(e.X, def)
case *syntax.ArrayType:
typ := new(Array)
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index 2dab5cf7b9..c35edd8afa 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -607,7 +607,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName
check.collectTypeParams(&alias.tparams, tdecl.TypeParams)
}
- rhs = check.definedType(tdecl.Type, obj)
+ rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil)
alias.fromRHS = rhs
@@ -658,7 +658,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *TypeName
check.collectTypeParams(&named.tparams, tdecl.TypeParams)
}
- rhs = check.definedType(tdecl.Type, obj)
+ rhs = check.declaredType(tdecl.Type, obj)
assert(rhs != nil)
named.fromRHS = rhs
diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go
index 88ec4b77fc..b44fe4d768 100644
--- a/src/go/types/typexpr.go
+++ b/src/go/types/typexpr.go
@@ -16,7 +16,7 @@ import (
// ident type-checks identifier e and initializes x with the value or type of e.
// If an error occurred, x.mode is set to invalid.
-// For the meaning of def, see Checker.definedType, below.
+// For the meaning of def, see Checker.declaredType, below.
// If wantType is set, the identifier e is expected to denote a type.
func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bool) {
x.mode = invalid
@@ -148,14 +148,14 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *TypeName, wantType bo
// typ type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type.
func (check *Checker) typ(e ast.Expr) Type {
- return check.definedType(e, nil)
+ return check.declaredType(e, nil)
}
// varType type-checks the type expression e and returns its type, or Typ[Invalid].
// The type must not be an (uninstantiated) generic type and it must not be a
// constraint interface.
func (check *Checker) varType(e ast.Expr) Type {
- typ := check.definedType(e, nil)
+ typ := check.declaredType(e, nil)
check.validVarType(e, typ)
return typ
}
@@ -185,11 +185,11 @@ func (check *Checker) validVarType(e ast.Expr, typ Type) {
}).describef(e, "check var type %s", typ)
}
-// definedType is like typ but also accepts a type name def.
-// If def != nil, e is the type specification for the type named def, declared
-// in a type declaration, and def.typ.underlying will be set to the type of e
-// before any components of e are type-checked.
-func (check *Checker) definedType(e ast.Expr, def *TypeName) Type {
+// declaredType is like typ but also accepts a type name def.
+// If def != nil, e is the type specification for the [Alias] or [Named] type
+// named def, and def.typ.fromRHS will be set to the [Type] of e immediately
+// after its creation.
+func (check *Checker) declaredType(e ast.Expr, def *TypeName) Type {
typ := check.typInternal(e, def)
assert(isTyped(typ))
if isGeneric(typ) {
@@ -228,7 +228,7 @@ func goTypeName(typ Type) string {
}
// typInternal drives type checking of types.
-// Must only be called by definedType or genericType.
+// Must only be called by declaredType or genericType.
func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) {
if check.conf._Trace {
check.trace(e0.Pos(), "-- type %s", e0)
@@ -295,7 +295,7 @@ func (check *Checker) typInternal(e0 ast.Expr, def *TypeName) (T Type) {
case *ast.ParenExpr:
// Generic types must be instantiated before they can be used in any form.
// Consequently, generic types cannot be parenthesized.
- return check.definedType(e.X, def)
+ return check.declaredType(e.X, def)
case *ast.ArrayType:
if e.Len == nil {
--
cgit v1.3
From 7601cd388044485115532e0c25007eb4e1a7d35e Mon Sep 17 00:00:00 2001
From: Mark Freeman
Date: Mon, 27 Oct 2025 14:22:04 -0400
Subject: go/types: generate cycles.go
Change-Id: I09a31a1ccec082f5538736c48b211e4d567ee80c
Reviewed-on: https://go-review.googlesource.com/c/go/+/715420
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Robert Griesemer
Auto-Submit: Mark Freeman
---
src/go/types/cycles.go | 3 +++
src/go/types/generate_test.go | 5 +++++
2 files changed, 8 insertions(+)
(limited to 'src')
diff --git a/src/go/types/cycles.go b/src/go/types/cycles.go
index 87e8e9729b..d90e7de39c 100644
--- a/src/go/types/cycles.go
+++ b/src/go/types/cycles.go
@@ -1,3 +1,6 @@
+// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
+// Source: ../../cmd/compile/internal/types2/cycles.go
+
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go
index e5e0874d17..cf2824417c 100644
--- a/src/go/types/generate_test.go
+++ b/src/go/types/generate_test.go
@@ -126,6 +126,11 @@ var filemap = map[string]action{
"context.go": nil,
"context_test.go": nil,
"conversions.go": nil,
+ "cycles.go": func(f *ast.File) {
+ renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`)
+ renameSelectorExprs(f, "syntax.Name->ast.Ident", "rhs.Value->rhs.Name")
+ renameSelectors(f, "Trace->_Trace")
+ },
"errors_test.go": func(f *ast.File) { renameIdents(f, "nopos->noposn") },
"errsupport.go": nil,
"gccgosizes.go": nil,
--
cgit v1.3
From 77c5130100ac1b29bb2e04d0f5f99e200136c0af Mon Sep 17 00:00:00 2001
From: Mark Freeman
Date: Mon, 27 Oct 2025 15:57:57 -0400
Subject: go/types: minor simplification
Change-Id: Ia315cef2022197ec740023b67827bc810219b868
Reviewed-on: https://go-review.googlesource.com/c/go/+/715361
Reviewed-by: Robert Griesemer
Auto-Submit: Mark Freeman
LUCI-TryBot-Result: Go LUCI
---
src/go/types/generate_test.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/go/types/generate_test.go b/src/go/types/generate_test.go
index cf2824417c..2fa72c28bf 100644
--- a/src/go/types/generate_test.go
+++ b/src/go/types/generate_test.go
@@ -150,7 +150,7 @@ var filemap = map[string]action{
renameIdents(f, "syntax->ast")
renameSelectors(f, "ElemList->Elts")
},
- "lookup.go": func(f *ast.File) { fixTokenPos(f) },
+ "lookup.go": fixTokenPos,
"main_test.go": nil,
"map.go": nil,
"mono.go": func(f *ast.File) {
--
cgit v1.3
From 129d0cb543e1e15cdea706dd7230ee90c8d54446 Mon Sep 17 00:00:00 2001
From: Peter Beard
Date: Tue, 28 Oct 2025 10:26:26 -0600
Subject: net/http/cgi: accept INCLUDED as protocol for server side includes
The existing protocol check for fcgi/cgi requests did not properly
account for Apache SSI (Server-Side Includes) SERVER_PROTOCOL value of
INCLUDED.
Added check for well-known INCLUDED value for proper implementation of
the CGI Spec as specified in RFC 3875 - section 4.1.16.
The SERVER_PROTOCOL section of the specification is outlined at
https://www.rfc-editor.org/rfc/rfc3875.html#section-4.1.16
Fixes #70416
Change-Id: I129e606147e16d1daefb49ed6c13a561a88ddeb6
Reviewed-on: https://go-review.googlesource.com/c/go/+/715680
Reviewed-by: Damien Neil
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: Sean Liao
Auto-Submit: Sean Liao
---
src/net/http/cgi/child.go | 7 +++++--
src/net/http/cgi/child_test.go | 22 ++++++++++++++++++++++
2 files changed, 27 insertions(+), 2 deletions(-)
(limited to 'src')
diff --git a/src/net/http/cgi/child.go b/src/net/http/cgi/child.go
index e29fe20d7d..466d42c08e 100644
--- a/src/net/http/cgi/child.go
+++ b/src/net/http/cgi/child.go
@@ -57,8 +57,11 @@ func RequestFromMap(params map[string]string) (*http.Request, error) {
r.Proto = params["SERVER_PROTOCOL"]
var ok bool
- r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto)
- if !ok {
+ if r.Proto == "INCLUDED" {
+ // SSI (Server Side Include) use case
+ // CGI Specification RFC 3875 - section 4.1.16
+ r.ProtoMajor, r.ProtoMinor = 1, 0
+ } else if r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto); !ok {
return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
}
diff --git a/src/net/http/cgi/child_test.go b/src/net/http/cgi/child_test.go
index 18cf789bd5..f901bec1a8 100644
--- a/src/net/http/cgi/child_test.go
+++ b/src/net/http/cgi/child_test.go
@@ -154,6 +154,28 @@ func TestRequestWithoutRemotePort(t *testing.T) {
}
}
+// CGI Specification RFC 3875 - section 4.1.16
+// INCLUDED value for SERVER_PROTOCOL must be treated as an HTTP/1.0 request
+func TestIncludedServerProtocol(t *testing.T) {
+ env := map[string]string{
+ "REQUEST_METHOD": "GET",
+ "SERVER_PROTOCOL": "INCLUDED",
+ }
+ req, err := RequestFromMap(env)
+ if req.Proto != "INCLUDED" {
+ t.Errorf("unexpected change to SERVER_PROTOCOL")
+ }
+ if major := req.ProtoMajor; major != 1 {
+ t.Errorf("ProtoMajor: got %d, want %d", major, 1)
+ }
+ if minor := req.ProtoMinor; minor != 0 {
+ t.Errorf("ProtoMinor: got %d, want %d", minor, 0)
+ }
+ if err != nil {
+ t.Fatalf("expected INCLUDED to be treated as HTTP/1.0 request")
+ }
+}
+
func TestResponse(t *testing.T) {
var tests = []struct {
name string
--
cgit v1.3
From ff61991aed2871301e3f4fafff65fd67d6b170b5 Mon Sep 17 00:00:00 2001
From: Russ Cox
Date: Thu, 13 Nov 2025 14:33:53 -0500
Subject: cmd/go: fix flaky TestScript/mod_get_direct
Use our local git server instead of cloud.google.com/go,
which may go down or otherwise reject our traffic.
I built and installed git 2.23.0 and confirmed that this
updated test still fails if CL 196961 is rolled back.
So the test is still accurate at detecting the problem.
Change-Id: I58011f6cc62af2f21fbbcc526ba5401f4186eeaf
Reviewed-on: https://go-review.googlesource.com/c/go/+/720322
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Matloob
---
src/cmd/go/testdata/script/mod_get_direct.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'src')
diff --git a/src/cmd/go/testdata/script/mod_get_direct.txt b/src/cmd/go/testdata/script/mod_get_direct.txt
index 02b10ab6fd..2c89576446 100644
--- a/src/cmd/go/testdata/script/mod_get_direct.txt
+++ b/src/cmd/go/testdata/script/mod_get_direct.txt
@@ -2,14 +2,14 @@
# 'GOPROXY=direct go get golang.org/x/tools/gopls@master' did not correctly
# resolve the pseudo-version for its dependency on golang.org/x/tools.
-[!net:cloud.google.com] skip
+[short] skip
[!git] skip
env GO111MODULE=on
env GOPROXY=direct
env GOSUMDB=off
-go list -m cloud.google.com/go@main
+go list -m vcs-test.golang.org/git/tagtests.git@master
! stdout 'v0.0.0-'
-- go.mod --
--
cgit v1.3
From 17a02b910697800032aa686548992a554e8e9d82 Mon Sep 17 00:00:00 2001
From: 1911860538
Date: Mon, 10 Nov 2025 11:17:01 +0000
Subject: net/http: remove unused isLitOrSingle and isNotToken
isLitOrSingle and isNotToken are private and unused.
Change-Id: I07718d4496e92d5f75ed74986e174a8aa1f70a88
GitHub-Last-Rev: 722c4dccd85dca5d28a52e95a4f9efbab2b11807
GitHub-Pull-Request: golang/go#76216
Reviewed-on: https://go-review.googlesource.com/c/go/+/718700
Reviewed-by: Damien Neil
Auto-Submit: Sean Liao
Reviewed-by: Junyang Shao
Reviewed-by: Sean Liao
LUCI-TryBot-Result: Go LUCI
---
src/net/http/http.go | 4 ----
src/net/http/pattern.go | 8 --------
2 files changed, 12 deletions(-)
(limited to 'src')
diff --git a/src/net/http/http.go b/src/net/http/http.go
index e7959fa3b6..d346e60646 100644
--- a/src/net/http/http.go
+++ b/src/net/http/http.go
@@ -119,10 +119,6 @@ func removeEmptyPort(host string) string {
return host
}
-func isNotToken(r rune) bool {
- return !httpguts.IsTokenRune(r)
-}
-
// isToken reports whether v is a valid token (https://www.rfc-editor.org/rfc/rfc2616#section-2.2).
func isToken(v string) bool {
// For historical reasons, this function is called ValidHeaderFieldName (see issue #67031).
diff --git a/src/net/http/pattern.go b/src/net/http/pattern.go
index 8fd120e777..a5063807c6 100644
--- a/src/net/http/pattern.go
+++ b/src/net/http/pattern.go
@@ -394,14 +394,6 @@ func inverseRelationship(r relationship) relationship {
}
}
-// isLitOrSingle reports whether the segment is a non-dollar literal or a single wildcard.
-func isLitOrSingle(seg segment) bool {
- if seg.wild {
- return !seg.multi
- }
- return seg.s != "/"
-}
-
// describeConflict returns an explanation of why two patterns conflict.
func describeConflict(p1, p2 *pattern) string {
mrel := p1.compareMethods(p2)
--
cgit v1.3
From 704f841eab87462d4eefac07345c96f71fb6e964 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Thu, 13 Nov 2025 22:36:45 +0000
Subject: cmd/trace: annotation proc start/stop with thread and proc always
In the proc view, the thread ID is useful. In the thread view, the proc
ID is useful. Add both in both cases forever more.
Change-Id: I9cb7bd67a21ee17d865c25d73b2049b3da7aefbc
Reviewed-on: https://go-review.googlesource.com/c/go/+/720402
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Pratt
Auto-Submit: Michael Knyszek
---
src/cmd/trace/procgen.go | 8 +++++++-
src/cmd/trace/threadgen.go | 12 +++++++-----
src/internal/trace/traceviewer/format/format.go | 5 +++--
3 files changed, 17 insertions(+), 8 deletions(-)
(limited to 'src')
diff --git a/src/cmd/trace/procgen.go b/src/cmd/trace/procgen.go
index 060e62fe04..fc0a00e7ce 100644
--- a/src/cmd/trace/procgen.go
+++ b/src/cmd/trace/procgen.go
@@ -143,6 +143,13 @@ func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
viewerEv := traceviewer.InstantEvent{
Resource: uint64(proc),
Stack: ctx.Stack(viewerFrames(ev.Stack())),
+
+ // Annotate with the thread and proc. The proc is redundant, but this is to
+ // stay consistent with the thread view, where it's useful information.
+ Arg: format.SchedCtxArg{
+ ProcID: uint64(st.Resource.Proc()),
+ ThreadID: uint64(ev.Thread()),
+ },
}
from, to := st.Proc()
@@ -156,7 +163,6 @@ func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
start = ctx.startTime
}
viewerEv.Name = "proc start"
- viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
viewerEv.Ts = ctx.elapsed(start)
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
}
diff --git a/src/cmd/trace/threadgen.go b/src/cmd/trace/threadgen.go
index c2e2c86f6c..7f9e7a72f0 100644
--- a/src/cmd/trace/threadgen.go
+++ b/src/cmd/trace/threadgen.go
@@ -138,14 +138,17 @@ func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
}
}
- type procArg struct {
- Proc uint64 `json:"proc,omitempty"`
- }
st := ev.StateTransition()
viewerEv := traceviewer.InstantEvent{
Resource: uint64(ev.Thread()),
Stack: ctx.Stack(viewerFrames(ev.Stack())),
- Arg: procArg{Proc: uint64(st.Resource.Proc())},
+
+ // Annotate with the thread and proc. The thread is redundant, but this is to
+ // stay consistent with the proc view.
+ Arg: format.SchedCtxArg{
+ ProcID: uint64(st.Resource.Proc()),
+ ThreadID: uint64(ev.Thread()),
+ },
}
from, to := st.Proc()
@@ -159,7 +162,6 @@ func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
start = ctx.startTime
}
viewerEv.Name = "proc start"
- viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
viewerEv.Ts = ctx.elapsed(start)
// TODO(mknyszek): We don't have a state machine for threads, so approximate
// running threads with running Ps.
diff --git a/src/internal/trace/traceviewer/format/format.go b/src/internal/trace/traceviewer/format/format.go
index 83f3276704..2ec4dd4bdc 100644
--- a/src/internal/trace/traceviewer/format/format.go
+++ b/src/internal/trace/traceviewer/format/format.go
@@ -74,6 +74,7 @@ type ThreadCountersArg struct {
InSyscall int64
}
-type ThreadIDArg struct {
- ThreadID uint64
+type SchedCtxArg struct {
+ ThreadID uint64 `json:"thread,omitempty"`
+ ProcID uint64 `json:"proc,omitempty"`
}
--
cgit v1.3
From 9daaab305c4d1dede9e4f6efdc5e1268a69327e6 Mon Sep 17 00:00:00 2001
From: "matloob@golang.org"
Date: Tue, 11 Nov 2025 13:54:52 -0500
Subject: cmd/link/internal/ld: make runtime.buildVersion with experiments
valid
Specifically if there are experiments but no nonstandard toolchain
suffix such as "-devel", the go version will not be valid according to
go/version.IsValid. To fix that, always put the X: part into the suffix,
resulting in, for example, go1.25.0-X:foo.
Fixes #75953
Change-Id: I6a6a696468f3ba9b82b6a410fb88831428e93b58
Reviewed-on: https://go-review.googlesource.com/c/go/+/719701
Reviewed-by: Michael Matloob
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Dmitri Shuralyov
Reviewed-by: Dmitri Shuralyov
---
src/cmd/go/internal/cache/hash.go | 3 +++
src/cmd/link/internal/ld/main.go | 6 +++++-
2 files changed, 8 insertions(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/cmd/go/internal/cache/hash.go b/src/cmd/go/internal/cache/hash.go
index 4f79c31500..27d275644a 100644
--- a/src/cmd/go/internal/cache/hash.go
+++ b/src/cmd/go/internal/cache/hash.go
@@ -51,6 +51,9 @@ func stripExperiment(version string) string {
if i := strings.Index(version, " X:"); i >= 0 {
return version[:i]
}
+ if i := strings.Index(version, "-X:"); i >= 0 {
+ return version[:i]
+ }
return version
}
diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go
index d913953944..a4b3a0422f 100644
--- a/src/cmd/link/internal/ld/main.go
+++ b/src/cmd/link/internal/ld/main.go
@@ -188,7 +188,11 @@ func Main(arch *sys.Arch, theArch Arch) {
buildVersion := buildcfg.Version
if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
- buildVersion += " X:" + goexperiment
+ sep := " "
+ if !strings.Contains(buildVersion, "-") { // See go.dev/issue/75953.
+ sep = "-"
+ }
+ buildVersion += sep + "X:" + goexperiment
}
addstrdata1(ctxt, "runtime.buildVersion="+buildVersion)
--
cgit v1.3
From ddd8558e61c80f51a2c65565f8354d12d3c6a418 Mon Sep 17 00:00:00 2001
From: Mark Freeman
Date: Tue, 28 Oct 2025 17:54:52 -0400
Subject: go/types, types2: swap object.color for Checker.objPathIdx
The type checker assigns types to objects. An object can be in
1 of 3 states, which are mapped to colors. This is stored as a
field on the object.
- white : type not known, awaiting processing
- grey : type pending, in processing
- black : type known, done processing
With the addition of Checker.objPathIdx, which maps objects to
a path index, presence in the map could signal grey coloring.
White and black coloring can be signaled by presence of
object.typ (a simple nil check).
This change removes the object.color field and its associated
methods, replacing it with an equivalent use of Checker.objPathIdx.
Checker.objPathIdx is integrated into the existing push and pop
methods, while slightly simplifying their signatures.
Note that the concept of object coloring remains the same - we
are merely changing and simplifying the mechanism which represents
the colors.
Change-Id: I91fb5e9a59dcb34c08ffc5b4ebc3f20a400094b6
Reviewed-on: https://go-review.googlesource.com/c/go/+/715840
Reviewed-by: Robert Griesemer
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Mark Freeman
---
src/cmd/compile/internal/types2/check.go | 31 +++---
src/cmd/compile/internal/types2/cycles.go | 1 -
src/cmd/compile/internal/types2/decl.go | 147 +++++++++----------------
src/cmd/compile/internal/types2/object.go | 54 ++-------
src/cmd/compile/internal/types2/scope.go | 2 -
src/cmd/compile/internal/types2/sizeof_test.go | 16 +--
src/cmd/compile/internal/types2/universe.go | 8 +-
src/go/types/check.go | 31 +++---
src/go/types/cycles.go | 1 -
src/go/types/decl.go | 147 +++++++++----------------
src/go/types/object.go | 54 ++-------
src/go/types/scope.go | 2 -
src/go/types/sizeof_test.go | 16 +--
src/go/types/universe.go | 8 +-
14 files changed, 170 insertions(+), 348 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go
index 25cda4f73d..42218b4caf 100644
--- a/src/cmd/compile/internal/types2/check.go
+++ b/src/cmd/compile/internal/types2/check.go
@@ -171,12 +171,13 @@ type Checker struct {
usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
- firstErr error // first error encountered
- methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
- untyped map[syntax.Expr]exprInfo // map of expressions without final type
- delayed []action // stack of delayed action segments; segments are processed in FIFO order
- objPath []Object // path of object dependencies during type inference (for cycle reporting)
- cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
+ firstErr error // first error encountered
+ methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
+ untyped map[syntax.Expr]exprInfo // map of expressions without final type
+ delayed []action // stack of delayed action segments; segments are processed in FIFO order
+ objPath []Object // path of object dependencies during type-checking (for cycle reporting)
+ objPathIdx map[Object]int // map of object to object path index during type-checking (for cycle reporting)
+ cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object)
@@ -248,19 +249,22 @@ func (check *Checker) later(f func()) *action {
return &check.delayed[i]
}
-// push pushes obj onto the object path and returns its index in the path.
-func (check *Checker) push(obj Object) int {
+// push pushes obj onto the object path and records its index in the path index map.
+func (check *Checker) push(obj Object) {
+ if check.objPathIdx == nil {
+ check.objPathIdx = make(map[Object]int)
+ }
+ check.objPathIdx[obj] = len(check.objPath)
check.objPath = append(check.objPath, obj)
- return len(check.objPath) - 1
}
-// pop pops and returns the topmost object from the object path.
-func (check *Checker) pop() Object {
+// pop pops an object from the object path and removes it from the path index map.
+func (check *Checker) pop() {
i := len(check.objPath) - 1
obj := check.objPath[i]
- check.objPath[i] = nil
+ check.objPath[i] = nil // help the garbage collector
check.objPath = check.objPath[:i]
- return obj
+ delete(check.objPathIdx, obj)
}
type cleaner interface {
@@ -319,6 +323,7 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.untyped = nil
check.delayed = nil
check.objPath = nil
+ check.objPathIdx = nil
check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker,
diff --git a/src/cmd/compile/internal/types2/cycles.go b/src/cmd/compile/internal/types2/cycles.go
index fa739a2b84..b916219c97 100644
--- a/src/cmd/compile/internal/types2/cycles.go
+++ b/src/cmd/compile/internal/types2/cycles.go
@@ -54,7 +54,6 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
// tname is marked grey - we have a cycle on the path beginning at start.
// Mark tname as invalid.
tname.setType(Typ[Invalid])
- tname.setColor(black)
// collect type names on cycle
var cycle []Object
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index 8f196ece61..2df34f3b94 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -62,114 +62,77 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability
}
- check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
+ check.trace(obj.Pos(), "-- checking %s (objPath = %s)", obj, pathString(check.objPath))
check.indent++
defer func() {
check.indent--
- check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color())
+ check.trace(obj.Pos(), "=> %s", obj)
}()
}
- // Checking the declaration of obj means inferring its type
- // (and possibly its value, for constants).
- // An object's type (and thus the object) may be in one of
- // three states which are expressed by colors:
+ // Checking the declaration of an object means determining its type
+ // (and also its value for constants). An object (and thus its type)
+ // may be in 1 of 3 states:
//
- // - an object whose type is not yet known is painted white (initial color)
- // - an object whose type is in the process of being inferred is painted grey
- // - an object whose type is fully inferred is painted black
+ // - not in Checker.objPathIdx and type == nil : type is not yet known (white)
+ // - in Checker.objPathIdx : type is pending (grey)
+ // - not in Checker.objPathIdx and type != nil : type is known (black)
//
- // During type inference, an object's color changes from white to grey
- // to black (pre-declared objects are painted black from the start).
- // A black object (i.e., its type) can only depend on (refer to) other black
- // ones. White and grey objects may depend on white and black objects.
- // A dependency on a grey object indicates a cycle which may or may not be
- // valid.
+ // During type-checking, an object changes from white to grey to black.
+ // Predeclared objects start as black (their type is known without checking).
//
- // When objects turn grey, they are pushed on the object path (a stack);
- // they are popped again when they turn black. Thus, if a grey object (a
- // cycle) is encountered, it is on the object path, and all the objects
- // it depends on are the remaining objects on that path. Color encoding
- // is such that the color value of a grey object indicates the index of
- // that object in the object path.
-
- // During type-checking, white objects may be assigned a type without
- // traversing through objDecl; e.g., when initializing constants and
- // variables. Update the colors of those objects here (rather than
- // everywhere where we set the type) to satisfy the color invariants.
- if obj.color() == white && obj.Type() != nil {
- obj.setColor(black)
- return
- }
-
- switch obj.color() {
- case white:
- assert(obj.Type() == nil)
- // All color values other than white and black are considered grey.
- // Because black and white are < grey, all values >= grey are grey.
- // Use those values to encode the object's index into the object path.
- obj.setColor(grey + color(check.push(obj)))
- defer func() {
- check.pop().setColor(black)
- }()
-
- case black:
- assert(obj.Type() != nil)
- return
-
- default:
- // Color values other than white or black are considered grey.
- fallthrough
-
- case grey:
- // We have a (possibly invalid) cycle.
- // In the existing code, this is marked by a non-nil type
- // for the object except for constants and variables whose
- // type may be non-nil (known), or nil if it depends on the
- // not-yet known initialization value.
- // In the former case, set the type to Typ[Invalid] because
- // we have an initialization cycle. The cycle error will be
- // reported later, when determining initialization order.
- // TODO(gri) Report cycle here and simplify initialization
- // order code.
+ // A black object may only depend on (refer to) to other black objects. White
+ // and grey objects may depend on white or black objects. A dependency on a
+ // grey object indicates a (possibly invalid) cycle.
+ //
+ // When an object is marked grey, it is pushed onto the object path (a stack)
+ // and its index in the path is recorded in the path index map. It is popped
+ // and removed from the map when its type is determined (and marked black).
+
+ // If this object is grey, we have a (possibly invalid) cycle. This is signaled
+ // by a non-nil type for the object, except for constants and variables whose
+ // type may be non-nil (known), or nil if it depends on a not-yet known
+ // initialization value.
+ //
+ // In the former case, set the type to Typ[Invalid] because we have an
+ // initialization cycle. The cycle error will be reported later, when
+ // determining initialization order.
+ //
+ // TODO(gri) Report cycle here and simplify initialization order code.
+ if _, ok := check.objPathIdx[obj]; ok {
switch obj := obj.(type) {
- case *Const:
- if !check.validCycle(obj) || obj.typ == nil {
- obj.typ = Typ[Invalid]
- }
-
- case *Var:
- if !check.validCycle(obj) || obj.typ == nil {
- obj.typ = Typ[Invalid]
+ case *Const, *Var:
+ if !check.validCycle(obj) || obj.Type() == nil {
+ obj.setType(Typ[Invalid])
}
-
case *TypeName:
if !check.validCycle(obj) {
- // break cycle
- // (without this, calling underlying()
- // below may lead to an endless loop
- // if we have a cycle for a defined
- // (*Named) type)
- obj.typ = Typ[Invalid]
+ obj.setType(Typ[Invalid])
}
-
case *Func:
if !check.validCycle(obj) {
- // Don't set obj.typ to Typ[Invalid] here
- // because plenty of code type-asserts that
- // functions have a *Signature type. Grey
- // functions have their type set to an empty
- // signature which makes it impossible to
+ // Don't set type to Typ[Invalid]; plenty of code asserts that
+ // functions have a *Signature type. Instead, leave the type
+ // as an empty signature, which makes it impossible to
// initialize a variable with the function.
}
-
default:
panic("unreachable")
}
+
assert(obj.Type() != nil)
return
}
+ if obj.Type() != nil { // black, meaning it's already type-checked
+ return
+ }
+
+ // white, meaning it must be type-checked
+
+ check.push(obj)
+ defer check.pop()
+
d := check.objMap[obj]
if d == nil {
check.dump("%v: %s should have been declared", obj.Pos(), obj)
@@ -221,8 +184,8 @@ func (check *Checker) validCycle(obj Object) (valid bool) {
}
// Count cycle objects.
- assert(obj.color() >= grey)
- start := obj.color() - grey // index of obj in objPath
+ start, found := check.objPathIdx[obj]
+ assert(found)
cycle := check.objPath[start:]
tparCycle := false // if set, the cycle is through a type parameter list
nval := 0 // number of (constant or variable) values in the cycle
@@ -764,17 +727,8 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature)
obj.typ = sig // guard against cycles
- // Avoid cycle error when referring to method while type-checking the signature.
- // This avoids a nuisance in the best case (non-parameterized receiver type) and
- // since the method is not a type, we get an error. If we have a parameterized
- // receiver type, instantiating the receiver type leads to the instantiation of
- // its methods, and we don't want a cycle error in that case.
- // TODO(gri) review if this is correct and/or whether we still need this?
- saved := obj.color_
- obj.color_ = black
fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.TParamList, fdecl.Type)
- obj.color_ = saved
// Set the scope's extent to the complete "func (...) { ... }"
// so that Scope.Innermost works correctly.
@@ -921,10 +875,9 @@ func (check *Checker) declStmt(list []syntax.Decl) {
// the innermost containing block."
scopePos := s.Name.Pos()
check.declare(check.scope, s.Name, obj, scopePos)
- // mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl)
- obj.setColor(grey + color(check.push(obj)))
+ check.push(obj) // mark as grey
+ defer check.pop()
check.typeDecl(obj, s, nil)
- check.pop().setColor(black)
default:
check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)
diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go
index ce129dbf59..dd2d415790 100644
--- a/src/cmd/compile/internal/types2/object.go
+++ b/src/cmd/compile/internal/types2/object.go
@@ -42,18 +42,12 @@ type Object interface {
// 0 for all other objects (including objects in file scopes).
order() uint32
- // color returns the object's color.
- color() color
-
// setType sets the type of the object.
setType(Type)
// setOrder sets the order number of the object. It must be > 0.
setOrder(uint32)
- // setColor sets the object's color. It must not be white.
- setColor(color color)
-
// setParent sets the parent scope of the object.
setParent(*Scope)
@@ -102,41 +96,9 @@ type object struct {
name string
typ Type
order_ uint32
- color_ color
scopePos_ syntax.Pos
}
-// color encodes the color of an object (see Checker.objDecl for details).
-type color uint32
-
-// An object may be painted in one of three colors.
-// Color values other than white or black are considered grey.
-const (
- white color = iota
- black
- grey // must be > white and black
-)
-
-func (c color) String() string {
- switch c {
- case white:
- return "white"
- case black:
- return "black"
- default:
- return "grey"
- }
-}
-
-// colorFor returns the (initial) color for an object depending on
-// whether its type t is known or not.
-func colorFor(t Type) color {
- if t != nil {
- return black
- }
- return white
-}
-
// Parent returns the scope in which the object is declared.
// The result is nil for methods and struct fields.
func (obj *object) Parent() *Scope { return obj.parent }
@@ -164,13 +126,11 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ }
-func (obj *object) color() color { return obj.color_ }
func (obj *object) scopePos() syntax.Pos { return obj.scopePos_ }
func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) setType(typ Type) { obj.typ = typ }
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
-func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos syntax.Pos) { obj.scopePos_ = pos }
func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
@@ -247,7 +207,7 @@ type PkgName struct {
// NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos syntax.Pos, pkg *Package, name string, imported *Package) *PkgName {
- return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported}
+ return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, nopos}, imported}
}
// Imported returns the package that was imported.
@@ -263,7 +223,7 @@ type Const struct {
// NewConst returns a new constant with value val.
// The remaining arguments set the attributes found with all Objects.
func NewConst(pos syntax.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
- return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val}
+ return &Const{object{nil, pos, pkg, name, typ, 0, nopos}, val}
}
// Val returns the constant's value.
@@ -288,7 +248,7 @@ type TypeName struct {
// argument for NewNamed, which will set the TypeName's type as a side-
// effect.
func NewTypeName(pos syntax.Pos, pkg *Package, name string, typ Type) *TypeName {
- return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}}
+ return &TypeName{object{nil, pos, pkg, name, typ, 0, nopos}}
}
// NewTypeNameLazy returns a new defined type like NewTypeName, but it
@@ -402,7 +362,7 @@ func NewField(pos syntax.Pos, pkg *Package, name string, typ Type, embedded bool
// newVar returns a new variable.
// The arguments set the attributes found with all Objects.
func newVar(kind VarKind, pos syntax.Pos, pkg *Package, name string, typ Type) *Var {
- return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, kind: kind}
+ return &Var{object: object{nil, pos, pkg, name, typ, 0, nopos}, kind: kind}
}
// Anonymous reports whether the variable is an embedded field.
@@ -452,7 +412,7 @@ func NewFunc(pos syntax.Pos, pkg *Package, name string, sig *Signature) *Func {
// as this would violate object.{Type,color} invariants.
// TODO(adonovan): propose to disallow NewFunc with nil *Signature.
}
- return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil}
+ return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
}
// Signature returns the signature (type) of the function or method.
@@ -534,7 +494,7 @@ type Label struct {
// NewLabel returns a new label.
func NewLabel(pos syntax.Pos, pkg *Package, name string) *Label {
- return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false}
+ return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
}
// A Builtin represents a built-in function.
@@ -545,7 +505,7 @@ type Builtin struct {
}
func newBuiltin(id builtinId) *Builtin {
- return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id}
+ return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
}
// Nil represents the predeclared value nil.
diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go
index 566184df73..10c624be2e 100644
--- a/src/cmd/compile/internal/types2/scope.go
+++ b/src/cmd/compile/internal/types2/scope.go
@@ -217,10 +217,8 @@ func (*lazyObject) Exported() bool { panic("unreachable") }
func (*lazyObject) Id() string { panic("unreachable") }
func (*lazyObject) String() string { panic("unreachable") }
func (*lazyObject) order() uint32 { panic("unreachable") }
-func (*lazyObject) color() color { panic("unreachable") }
func (*lazyObject) setType(Type) { panic("unreachable") }
func (*lazyObject) setOrder(uint32) { panic("unreachable") }
-func (*lazyObject) setColor(color color) { panic("unreachable") }
func (*lazyObject) setParent(*Scope) { panic("unreachable") }
func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
func (*lazyObject) scopePos() syntax.Pos { panic("unreachable") }
diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go
index 092e82318a..f206c12fc3 100644
--- a/src/cmd/compile/internal/types2/sizeof_test.go
+++ b/src/cmd/compile/internal/types2/sizeof_test.go
@@ -36,14 +36,14 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24},
// Objects
- {PkgName{}, 60, 96},
- {Const{}, 64, 104},
- {TypeName{}, 56, 88},
- {Var{}, 64, 104},
- {Func{}, 64, 104},
- {Label{}, 60, 96},
- {Builtin{}, 60, 96},
- {Nil{}, 56, 88},
+ {PkgName{}, 56, 96},
+ {Const{}, 60, 104},
+ {TypeName{}, 52, 88},
+ {Var{}, 60, 104},
+ {Func{}, 60, 104},
+ {Label{}, 56, 96},
+ {Builtin{}, 56, 96},
+ {Nil{}, 52, 88},
// Misc
{Scope{}, 60, 104},
diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go
index 332cd174f9..1ecef97d51 100644
--- a/src/cmd/compile/internal/types2/universe.go
+++ b/src/cmd/compile/internal/types2/universe.go
@@ -98,7 +98,6 @@ func defPredeclaredTypes() {
// interface.
{
universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
- universeAnyNoAlias.setColor(black)
// ensure that the any TypeName reports a consistent Parent, after
// hijacking Universe.Lookup with gotypesalias=0.
universeAnyNoAlias.setParent(Universe)
@@ -107,7 +106,6 @@ func defPredeclaredTypes() {
// into the Universe, but we lean toward the future and insert the Alias
// representation.
universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
- universeAnyAlias.setColor(black)
_ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
def(universeAnyAlias)
}
@@ -115,7 +113,6 @@ func defPredeclaredTypes() {
// type error interface{ Error() string }
{
obj := NewTypeName(nopos, nil, "error", nil)
- obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil)
// error.Error() string
@@ -136,7 +133,6 @@ func defPredeclaredTypes() {
// type comparable interface{} // marked as comparable
{
obj := NewTypeName(nopos, nil, "comparable", nil)
- obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil)
// interface{} // marked as comparable
@@ -165,7 +161,7 @@ func defPredeclaredConsts() {
}
func defPredeclaredNil() {
- def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}})
+ def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
}
// A builtinId is the id of a builtin function.
@@ -289,7 +285,7 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope.
func def(obj Object) {
- assert(obj.color() == black)
+ assert(obj.Type() != nil)
name := obj.Name()
if strings.Contains(name, " ") {
return // nothing to do
diff --git a/src/go/types/check.go b/src/go/types/check.go
index 44d3ae5586..638b1f6fcc 100644
--- a/src/go/types/check.go
+++ b/src/go/types/check.go
@@ -191,12 +191,13 @@ type Checker struct {
usedPkgNames map[*PkgName]bool // set of used package names
mono monoGraph // graph for detecting non-monomorphizable instantiation loops
- firstErr error // first error encountered
- methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
- untyped map[ast.Expr]exprInfo // map of expressions without final type
- delayed []action // stack of delayed action segments; segments are processed in FIFO order
- objPath []Object // path of object dependencies during type inference (for cycle reporting)
- cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
+ firstErr error // first error encountered
+ methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
+ untyped map[ast.Expr]exprInfo // map of expressions without final type
+ delayed []action // stack of delayed action segments; segments are processed in FIFO order
+ objPath []Object // path of object dependencies during type-checking (for cycle reporting)
+ objPathIdx map[Object]int // map of object to object path index during type-checking (for cycle reporting)
+ cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object)
@@ -268,19 +269,22 @@ func (check *Checker) later(f func()) *action {
return &check.delayed[i]
}
-// push pushes obj onto the object path and returns its index in the path.
-func (check *Checker) push(obj Object) int {
+// push pushes obj onto the object path and records its index in the path index map.
+func (check *Checker) push(obj Object) {
+ if check.objPathIdx == nil {
+ check.objPathIdx = make(map[Object]int)
+ }
+ check.objPathIdx[obj] = len(check.objPath)
check.objPath = append(check.objPath, obj)
- return len(check.objPath) - 1
}
-// pop pops and returns the topmost object from the object path.
-func (check *Checker) pop() Object {
+// pop pops an object from the object path and removes it from the path index map.
+func (check *Checker) pop() {
i := len(check.objPath) - 1
obj := check.objPath[i]
- check.objPath[i] = nil
+ check.objPath[i] = nil // help the garbage collector
check.objPath = check.objPath[:i]
- return obj
+ delete(check.objPathIdx, obj)
}
type cleaner interface {
@@ -343,6 +347,7 @@ func (check *Checker) initFiles(files []*ast.File) {
check.untyped = nil
check.delayed = nil
check.objPath = nil
+ check.objPathIdx = nil
check.cleaners = nil
// We must initialize usedVars and usedPkgNames both here and in NewChecker,
diff --git a/src/go/types/cycles.go b/src/go/types/cycles.go
index d90e7de39c..bd894258b1 100644
--- a/src/go/types/cycles.go
+++ b/src/go/types/cycles.go
@@ -57,7 +57,6 @@ func (check *Checker) directCycle(tname *TypeName, pathIdx map[*TypeName]int) {
// tname is marked grey - we have a cycle on the path beginning at start.
// Mark tname as invalid.
tname.setType(Typ[Invalid])
- tname.setColor(black)
// collect type names on cycle
var cycle []Object
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index c35edd8afa..05cc63e01c 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -63,114 +63,77 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
if check.indent == 0 {
fmt.Println() // empty line between top-level objects for readability
}
- check.trace(obj.Pos(), "-- checking %s (%s, objPath = %s)", obj, obj.color(), pathString(check.objPath))
+ check.trace(obj.Pos(), "-- checking %s (objPath = %s)", obj, pathString(check.objPath))
check.indent++
defer func() {
check.indent--
- check.trace(obj.Pos(), "=> %s (%s)", obj, obj.color())
+ check.trace(obj.Pos(), "=> %s", obj)
}()
}
- // Checking the declaration of obj means inferring its type
- // (and possibly its value, for constants).
- // An object's type (and thus the object) may be in one of
- // three states which are expressed by colors:
+ // Checking the declaration of an object means determining its type
+ // (and also its value for constants). An object (and thus its type)
+ // may be in 1 of 3 states:
//
- // - an object whose type is not yet known is painted white (initial color)
- // - an object whose type is in the process of being inferred is painted grey
- // - an object whose type is fully inferred is painted black
+ // - not in Checker.objPathIdx and type == nil : type is not yet known (white)
+ // - in Checker.objPathIdx : type is pending (grey)
+ // - not in Checker.objPathIdx and type != nil : type is known (black)
//
- // During type inference, an object's color changes from white to grey
- // to black (pre-declared objects are painted black from the start).
- // A black object (i.e., its type) can only depend on (refer to) other black
- // ones. White and grey objects may depend on white and black objects.
- // A dependency on a grey object indicates a cycle which may or may not be
- // valid.
+ // During type-checking, an object changes from white to grey to black.
+ // Predeclared objects start as black (their type is known without checking).
//
- // When objects turn grey, they are pushed on the object path (a stack);
- // they are popped again when they turn black. Thus, if a grey object (a
- // cycle) is encountered, it is on the object path, and all the objects
- // it depends on are the remaining objects on that path. Color encoding
- // is such that the color value of a grey object indicates the index of
- // that object in the object path.
-
- // During type-checking, white objects may be assigned a type without
- // traversing through objDecl; e.g., when initializing constants and
- // variables. Update the colors of those objects here (rather than
- // everywhere where we set the type) to satisfy the color invariants.
- if obj.color() == white && obj.Type() != nil {
- obj.setColor(black)
- return
- }
-
- switch obj.color() {
- case white:
- assert(obj.Type() == nil)
- // All color values other than white and black are considered grey.
- // Because black and white are < grey, all values >= grey are grey.
- // Use those values to encode the object's index into the object path.
- obj.setColor(grey + color(check.push(obj)))
- defer func() {
- check.pop().setColor(black)
- }()
-
- case black:
- assert(obj.Type() != nil)
- return
-
- default:
- // Color values other than white or black are considered grey.
- fallthrough
-
- case grey:
- // We have a (possibly invalid) cycle.
- // In the existing code, this is marked by a non-nil type
- // for the object except for constants and variables whose
- // type may be non-nil (known), or nil if it depends on the
- // not-yet known initialization value.
- // In the former case, set the type to Typ[Invalid] because
- // we have an initialization cycle. The cycle error will be
- // reported later, when determining initialization order.
- // TODO(gri) Report cycle here and simplify initialization
- // order code.
+ // A black object may only depend on (refer to) to other black objects. White
+ // and grey objects may depend on white or black objects. A dependency on a
+ // grey object indicates a (possibly invalid) cycle.
+ //
+ // When an object is marked grey, it is pushed onto the object path (a stack)
+ // and its index in the path is recorded in the path index map. It is popped
+ // and removed from the map when its type is determined (and marked black).
+
+ // If this object is grey, we have a (possibly invalid) cycle. This is signaled
+ // by a non-nil type for the object, except for constants and variables whose
+ // type may be non-nil (known), or nil if it depends on a not-yet known
+ // initialization value.
+ //
+ // In the former case, set the type to Typ[Invalid] because we have an
+ // initialization cycle. The cycle error will be reported later, when
+ // determining initialization order.
+ //
+ // TODO(gri) Report cycle here and simplify initialization order code.
+ if _, ok := check.objPathIdx[obj]; ok {
switch obj := obj.(type) {
- case *Const:
- if !check.validCycle(obj) || obj.typ == nil {
- obj.typ = Typ[Invalid]
- }
-
- case *Var:
- if !check.validCycle(obj) || obj.typ == nil {
- obj.typ = Typ[Invalid]
+ case *Const, *Var:
+ if !check.validCycle(obj) || obj.Type() == nil {
+ obj.setType(Typ[Invalid])
}
-
case *TypeName:
if !check.validCycle(obj) {
- // break cycle
- // (without this, calling underlying()
- // below may lead to an endless loop
- // if we have a cycle for a defined
- // (*Named) type)
- obj.typ = Typ[Invalid]
+ obj.setType(Typ[Invalid])
}
-
case *Func:
if !check.validCycle(obj) {
- // Don't set obj.typ to Typ[Invalid] here
- // because plenty of code type-asserts that
- // functions have a *Signature type. Grey
- // functions have their type set to an empty
- // signature which makes it impossible to
+ // Don't set type to Typ[Invalid]; plenty of code asserts that
+ // functions have a *Signature type. Instead, leave the type
+ // as an empty signature, which makes it impossible to
// initialize a variable with the function.
}
-
default:
panic("unreachable")
}
+
assert(obj.Type() != nil)
return
}
+ if obj.Type() != nil { // black, meaning it's already type-checked
+ return
+ }
+
+ // white, meaning it must be type-checked
+
+ check.push(obj) // mark as grey
+ defer check.pop()
+
d := check.objMap[obj]
if d == nil {
check.dump("%v: %s should have been declared", obj.Pos(), obj)
@@ -222,8 +185,8 @@ func (check *Checker) validCycle(obj Object) (valid bool) {
}
// Count cycle objects.
- assert(obj.color() >= grey)
- start := obj.color() - grey // index of obj in objPath
+ start, found := check.objPathIdx[obj]
+ assert(found)
cycle := check.objPath[start:]
tparCycle := false // if set, the cycle is through a type parameter list
nval := 0 // number of (constant or variable) values in the cycle
@@ -857,17 +820,8 @@ func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
sig := new(Signature)
obj.typ = sig // guard against cycles
- // Avoid cycle error when referring to method while type-checking the signature.
- // This avoids a nuisance in the best case (non-parameterized receiver type) and
- // since the method is not a type, we get an error. If we have a parameterized
- // receiver type, instantiating the receiver type leads to the instantiation of
- // its methods, and we don't want a cycle error in that case.
- // TODO(gri) review if this is correct and/or whether we still need this?
- saved := obj.color_
- obj.color_ = black
fdecl := decl.fdecl
check.funcType(sig, fdecl.Recv, fdecl.Type)
- obj.color_ = saved
// Set the scope's extent to the complete "func (...) { ... }"
// so that Scope.Innermost works correctly.
@@ -980,10 +934,9 @@ func (check *Checker) declStmt(d ast.Decl) {
// the innermost containing block."
scopePos := d.spec.Name.Pos()
check.declare(check.scope, d.spec.Name, obj, scopePos)
- // mark and unmark type before calling typeDecl; its type is still nil (see Checker.objDecl)
- obj.setColor(grey + color(check.push(obj)))
+ check.push(obj) // mark as grey
+ defer check.pop()
check.typeDecl(obj, d.spec, nil)
- check.pop().setColor(black)
default:
check.errorf(d.node(), InvalidSyntaxTree, "unknown ast.Decl node %T", d.node())
}
diff --git a/src/go/types/object.go b/src/go/types/object.go
index 7bf705cb81..57158c1595 100644
--- a/src/go/types/object.go
+++ b/src/go/types/object.go
@@ -45,18 +45,12 @@ type Object interface {
// 0 for all other objects (including objects in file scopes).
order() uint32
- // color returns the object's color.
- color() color
-
// setType sets the type of the object.
setType(Type)
// setOrder sets the order number of the object. It must be > 0.
setOrder(uint32)
- // setColor sets the object's color. It must not be white.
- setColor(color color)
-
// setParent sets the parent scope of the object.
setParent(*Scope)
@@ -105,41 +99,9 @@ type object struct {
name string
typ Type
order_ uint32
- color_ color
scopePos_ token.Pos
}
-// color encodes the color of an object (see Checker.objDecl for details).
-type color uint32
-
-// An object may be painted in one of three colors.
-// Color values other than white or black are considered grey.
-const (
- white color = iota
- black
- grey // must be > white and black
-)
-
-func (c color) String() string {
- switch c {
- case white:
- return "white"
- case black:
- return "black"
- default:
- return "grey"
- }
-}
-
-// colorFor returns the (initial) color for an object depending on
-// whether its type t is known or not.
-func colorFor(t Type) color {
- if t != nil {
- return black
- }
- return white
-}
-
// Parent returns the scope in which the object is declared.
// The result is nil for methods and struct fields.
func (obj *object) Parent() *Scope { return obj.parent }
@@ -167,13 +129,11 @@ func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ }
-func (obj *object) color() color { return obj.color_ }
func (obj *object) scopePos() token.Pos { return obj.scopePos_ }
func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) setType(typ Type) { obj.typ = typ }
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
-func (obj *object) setColor(color color) { assert(color != white); obj.color_ = color }
func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos }
func (obj *object) sameId(pkg *Package, name string, foldCase bool) bool {
@@ -250,7 +210,7 @@ type PkgName struct {
// NewPkgName returns a new PkgName object representing an imported package.
// The remaining arguments set the attributes found with all Objects.
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
- return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, black, nopos}, imported}
+ return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, nopos}, imported}
}
// Imported returns the package that was imported.
@@ -266,7 +226,7 @@ type Const struct {
// NewConst returns a new constant with value val.
// The remaining arguments set the attributes found with all Objects.
func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const {
- return &Const{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, val}
+ return &Const{object{nil, pos, pkg, name, typ, 0, nopos}, val}
}
// Val returns the constant's value.
@@ -291,7 +251,7 @@ type TypeName struct {
// argument for NewNamed, which will set the TypeName's type as a side-
// effect.
func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
- return &TypeName{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}}
+ return &TypeName{object{nil, pos, pkg, name, typ, 0, nopos}}
}
// NewTypeNameLazy returns a new defined type like NewTypeName, but it
@@ -405,7 +365,7 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, embedded bool)
// newVar returns a new variable.
// The arguments set the attributes found with all Objects.
func newVar(kind VarKind, pos token.Pos, pkg *Package, name string, typ Type) *Var {
- return &Var{object: object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, kind: kind}
+ return &Var{object: object{nil, pos, pkg, name, typ, 0, nopos}, kind: kind}
}
// Anonymous reports whether the variable is an embedded field.
@@ -455,7 +415,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
// as this would violate object.{Type,color} invariants.
// TODO(adonovan): propose to disallow NewFunc with nil *Signature.
}
- return &Func{object{nil, pos, pkg, name, typ, 0, colorFor(typ), nopos}, false, nil}
+ return &Func{object{nil, pos, pkg, name, typ, 0, nopos}, false, nil}
}
// Signature returns the signature (type) of the function or method.
@@ -537,7 +497,7 @@ type Label struct {
// NewLabel returns a new label.
func NewLabel(pos token.Pos, pkg *Package, name string) *Label {
- return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid], color_: black}, false}
+ return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false}
}
// A Builtin represents a built-in function.
@@ -548,7 +508,7 @@ type Builtin struct {
}
func newBuiltin(id builtinId) *Builtin {
- return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid], color_: black}, id}
+ return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id}
}
// Nil represents the predeclared value nil.
diff --git a/src/go/types/scope.go b/src/go/types/scope.go
index 81366df741..e44b097dc5 100644
--- a/src/go/types/scope.go
+++ b/src/go/types/scope.go
@@ -220,10 +220,8 @@ func (*lazyObject) Exported() bool { panic("unreachable") }
func (*lazyObject) Id() string { panic("unreachable") }
func (*lazyObject) String() string { panic("unreachable") }
func (*lazyObject) order() uint32 { panic("unreachable") }
-func (*lazyObject) color() color { panic("unreachable") }
func (*lazyObject) setType(Type) { panic("unreachable") }
func (*lazyObject) setOrder(uint32) { panic("unreachable") }
-func (*lazyObject) setColor(color color) { panic("unreachable") }
func (*lazyObject) setParent(*Scope) { panic("unreachable") }
func (*lazyObject) sameId(*Package, string, bool) bool { panic("unreachable") }
func (*lazyObject) scopePos() token.Pos { panic("unreachable") }
diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go
index 4ff255ffa0..694ab32462 100644
--- a/src/go/types/sizeof_test.go
+++ b/src/go/types/sizeof_test.go
@@ -35,14 +35,14 @@ func TestSizeof(t *testing.T) {
{term{}, 12, 24},
// Objects
- {PkgName{}, 44, 80},
- {Const{}, 48, 88},
- {TypeName{}, 40, 72},
- {Var{}, 48, 88},
- {Func{}, 48, 88},
- {Label{}, 44, 80},
- {Builtin{}, 44, 80},
- {Nil{}, 40, 72},
+ {PkgName{}, 40, 80},
+ {Const{}, 44, 88},
+ {TypeName{}, 36, 72},
+ {Var{}, 44, 88},
+ {Func{}, 44, 88},
+ {Label{}, 40, 80},
+ {Builtin{}, 40, 80},
+ {Nil{}, 36, 72},
// Misc
{Scope{}, 44, 88},
diff --git a/src/go/types/universe.go b/src/go/types/universe.go
index 70935dc35f..8d2b99cf17 100644
--- a/src/go/types/universe.go
+++ b/src/go/types/universe.go
@@ -101,7 +101,6 @@ func defPredeclaredTypes() {
// interface.
{
universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
- universeAnyNoAlias.setColor(black)
// ensure that the any TypeName reports a consistent Parent, after
// hijacking Universe.Lookup with gotypesalias=0.
universeAnyNoAlias.setParent(Universe)
@@ -110,7 +109,6 @@ func defPredeclaredTypes() {
// into the Universe, but we lean toward the future and insert the Alias
// representation.
universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
- universeAnyAlias.setColor(black)
_ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
def(universeAnyAlias)
}
@@ -118,7 +116,6 @@ func defPredeclaredTypes() {
// type error interface{ Error() string }
{
obj := NewTypeName(nopos, nil, "error", nil)
- obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil)
// error.Error() string
@@ -139,7 +136,6 @@ func defPredeclaredTypes() {
// type comparable interface{} // marked as comparable
{
obj := NewTypeName(nopos, nil, "comparable", nil)
- obj.setColor(black)
typ := (*Checker)(nil).newNamed(obj, nil, nil)
// interface{} // marked as comparable
@@ -168,7 +164,7 @@ func defPredeclaredConsts() {
}
func defPredeclaredNil() {
- def(&Nil{object{name: "nil", typ: Typ[UntypedNil], color_: black}})
+ def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}})
}
// A builtinId is the id of a builtin function.
@@ -292,7 +288,7 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope.
func def(obj Object) {
- assert(obj.color() == black)
+ assert(obj.Type() != nil)
name := obj.Name()
if strings.Contains(name, " ") {
return // nothing to do
--
cgit v1.3
From 1e5e6663e958dcc9579fb38ffcd8a1999d75128d Mon Sep 17 00:00:00 2001
From: Michael Munday
Date: Fri, 7 Nov 2025 00:00:50 +0000
Subject: cmd/compile: remove unnecessary casts and types from riscv64 rules
This change shouldn't have any impact on codegen. It removes some
unnecessary int8 and int64 casts from the riscv64 rewrite rules.
It also removes explicit typing where the types already match:
`(OldOp ) => (NewOp )` is the same as `(OldOp) => (NewOp)`.
Change-Id: Ic02b65da8f548c8b9ad9ccb6627a03b7bf6f050f
Reviewed-on: https://go-review.googlesource.com/c/go/+/719220
Reviewed-by: Junyang Shao
Auto-Submit: Keith Randall
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
Commit-Queue: Junyang Shao
---
src/cmd/compile/internal/ssa/_gen/RISCV64.rules | 38 +++++------
src/cmd/compile/internal/ssa/rewriteRISCV64.go | 87 +++++++++++--------------
2 files changed, 57 insertions(+), 68 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
index 646948f2df..6166bd5584 100644
--- a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
+++ b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
@@ -689,36 +689,36 @@
(MOVDnop (MOVDconst [c])) => (MOVDconst [c])
// Avoid unnecessary zero and sign extension when right shifting.
-(SRAI [x] (MOVWreg y)) && x >= 0 && x <= 31 => (SRAIW [int64(x)] y)
-(SRLI [x] (MOVWUreg y)) && x >= 0 && x <= 31 => (SRLIW [int64(x)] y)
+(SRAI [x] (MOVWreg y)) && x >= 0 && x <= 31 => (SRAIW [x] y)
+(SRLI [x] (MOVWUreg y)) && x >= 0 && x <= 31 => (SRLIW [x] y)
// Replace right shifts that exceed size of signed type.
(SRAI [x] (MOVBreg y)) && x >= 8 => (SRAI [63] (SLLI [56] y))
(SRAI [x] (MOVHreg y)) && x >= 16 => (SRAI [63] (SLLI [48] y))
-(SRAI [x] (MOVWreg y)) && x >= 32 => (SRAIW [31] y)
+(SRAI [x] (MOVWreg y)) && x >= 32 => (SRAIW [31] y)
// Eliminate right shifts that exceed size of unsigned type.
-(SRLI [x] (MOVBUreg y)) && x >= 8 => (MOVDconst [0])
-(SRLI [x] (MOVHUreg y)) && x >= 16 => (MOVDconst [0])
-(SRLI [x] (MOVWUreg y)) && x >= 32 => (MOVDconst [0])
+(SRLI [x] (MOVBUreg y)) && x >= 8 => (MOVDconst [0])
+(SRLI [x] (MOVHUreg y)) && x >= 16 => (MOVDconst [0])
+(SRLI [x] (MOVWUreg y)) && x >= 32 => (MOVDconst [0])
// Fold constant into immediate instructions where possible.
(ADD (MOVDconst [val]) x) && is32Bit(val) && !t.IsPtr() => (ADDI [val] x)
(AND (MOVDconst [val]) x) && is32Bit(val) => (ANDI [val] x)
(OR (MOVDconst [val]) x) && is32Bit(val) => (ORI [val] x)
(XOR (MOVDconst [val]) x) && is32Bit(val) => (XORI [val] x)
-(ROL x (MOVDconst [val])) => (RORI [int64(int8(-val)&63)] x)
-(ROLW x (MOVDconst [val])) => (RORIW [int64(int8(-val)&31)] x)
-(ROR x (MOVDconst [val])) => (RORI [int64(val&63)] x)
-(RORW x (MOVDconst [val])) => (RORIW [int64(val&31)] x)
-(SLL x (MOVDconst [val])) => (SLLI [int64(val&63)] x)
-(SRL x (MOVDconst [val])) => (SRLI [int64(val&63)] x)
-(SLLW x (MOVDconst [val])) => (SLLIW [int64(val&31)] x)
-(SRLW x (MOVDconst [val])) => (SRLIW [int64(val&31)] x)
-(SRA x (MOVDconst [val])) => (SRAI [int64(val&63)] x)
-(SRAW x (MOVDconst [val])) => (SRAIW [int64(val&31)] x)
-(SLT x (MOVDconst [val])) && val >= -2048 && val <= 2047 => (SLTI [val] x)
-(SLTU x (MOVDconst [val])) && val >= -2048 && val <= 2047 => (SLTIU [val] x)
+(ROL x (MOVDconst [val])) => (RORI [-val&63] x)
+(ROLW x (MOVDconst [val])) => (RORIW [-val&31] x)
+(ROR x (MOVDconst [val])) => (RORI [val&63] x)
+(RORW x (MOVDconst [val])) => (RORIW [val&31] x)
+(SLL x (MOVDconst [val])) => (SLLI [val&63] x)
+(SLLW x (MOVDconst [val])) => (SLLIW [val&31] x)
+(SRL x (MOVDconst [val])) => (SRLI [val&63] x)
+(SRLW x (MOVDconst [val])) => (SRLIW [val&31] x)
+(SRA x (MOVDconst [val])) => (SRAI [val&63] x)
+(SRAW x (MOVDconst [val])) => (SRAIW [val&31] x)
+(SLT x (MOVDconst [val])) && is12Bit(val) => (SLTI [val] x)
+(SLTU x (MOVDconst [val])) && is12Bit(val) => (SLTIU [val] x)
// Replace negated left rotation with right rotation.
(ROL x (NEG y)) => (ROR x y)
@@ -782,7 +782,7 @@
(SRAI [x] (MOVDconst [y])) => (MOVDconst [int64(y) >> uint32(x)])
// Combine doubling via addition with shift.
-(SLLI [c] (ADD x x)) && c < t.Size() * 8 - 1 => (SLLI [c+1] x)
+(SLLI [c] (ADD x x)) && c < t.Size() * 8 - 1 => (SLLI [c+1] x)
(SLLI [c] (ADD x x)) && c >= t.Size() * 8 - 1 => (MOVDconst [0])
// SLTI/SLTIU with constants.
diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
index 191c7b3d48..24fef3fe72 100644
--- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go
+++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
@@ -7027,7 +7027,7 @@ func rewriteValueRISCV64_OpRISCV64ROL(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (ROL x (MOVDconst [val]))
- // result: (RORI [int64(int8(-val)&63)] x)
+ // result: (RORI [-val&63] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7035,7 +7035,7 @@ func rewriteValueRISCV64_OpRISCV64ROL(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORI)
- v.AuxInt = int64ToAuxInt(int64(int8(-val) & 63))
+ v.AuxInt = int64ToAuxInt(-val & 63)
v.AddArg(x)
return true
}
@@ -7057,7 +7057,7 @@ func rewriteValueRISCV64_OpRISCV64ROLW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (ROLW x (MOVDconst [val]))
- // result: (RORIW [int64(int8(-val)&31)] x)
+ // result: (RORIW [-val&31] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7065,7 +7065,7 @@ func rewriteValueRISCV64_OpRISCV64ROLW(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORIW)
- v.AuxInt = int64ToAuxInt(int64(int8(-val) & 31))
+ v.AuxInt = int64ToAuxInt(-val & 31)
v.AddArg(x)
return true
}
@@ -7087,7 +7087,7 @@ func rewriteValueRISCV64_OpRISCV64ROR(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (ROR x (MOVDconst [val]))
- // result: (RORI [int64(val&63)] x)
+ // result: (RORI [val&63] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7095,7 +7095,7 @@ func rewriteValueRISCV64_OpRISCV64ROR(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORI)
- v.AuxInt = int64ToAuxInt(int64(val & 63))
+ v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x)
return true
}
@@ -7105,7 +7105,7 @@ func rewriteValueRISCV64_OpRISCV64RORW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (RORW x (MOVDconst [val]))
- // result: (RORIW [int64(val&31)] x)
+ // result: (RORIW [val&31] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7113,7 +7113,7 @@ func rewriteValueRISCV64_OpRISCV64RORW(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64RORIW)
- v.AuxInt = int64ToAuxInt(int64(val & 31))
+ v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x)
return true
}
@@ -7212,7 +7212,7 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SLL x (MOVDconst [val]))
- // result: (SLLI [int64(val&63)] x)
+ // result: (SLLI [val&63] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7220,7 +7220,7 @@ func rewriteValueRISCV64_OpRISCV64SLL(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SLLI)
- v.AuxInt = int64ToAuxInt(int64(val & 63))
+ v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x)
return true
}
@@ -7246,7 +7246,7 @@ func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
}
// match: (SLLI [c] (ADD x x))
// cond: c < t.Size() * 8 - 1
- // result: (SLLI [c+1] x)
+ // result: (SLLI [c+1] x)
for {
t := v.Type
c := auxIntToInt64(v.AuxInt)
@@ -7258,7 +7258,6 @@ func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
break
}
v.reset(OpRISCV64SLLI)
- v.Type = t
v.AuxInt = int64ToAuxInt(c + 1)
v.AddArg(x)
return true
@@ -7286,7 +7285,7 @@ func rewriteValueRISCV64_OpRISCV64SLLW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SLLW x (MOVDconst [val]))
- // result: (SLLIW [int64(val&31)] x)
+ // result: (SLLIW [val&31] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7294,7 +7293,7 @@ func rewriteValueRISCV64_OpRISCV64SLLW(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SLLIW)
- v.AuxInt = int64ToAuxInt(int64(val & 31))
+ v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x)
return true
}
@@ -7304,7 +7303,7 @@ func rewriteValueRISCV64_OpRISCV64SLT(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SLT x (MOVDconst [val]))
- // cond: val >= -2048 && val <= 2047
+ // cond: is12Bit(val)
// result: (SLTI [val] x)
for {
x := v_0
@@ -7312,7 +7311,7 @@ func rewriteValueRISCV64_OpRISCV64SLT(v *Value) bool {
break
}
val := auxIntToInt64(v_1.AuxInt)
- if !(val >= -2048 && val <= 2047) {
+ if !(is12Bit(val)) {
break
}
v.reset(OpRISCV64SLTI)
@@ -7433,7 +7432,7 @@ func rewriteValueRISCV64_OpRISCV64SLTU(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SLTU x (MOVDconst [val]))
- // cond: val >= -2048 && val <= 2047
+ // cond: is12Bit(val)
// result: (SLTIU [val] x)
for {
x := v_0
@@ -7441,7 +7440,7 @@ func rewriteValueRISCV64_OpRISCV64SLTU(v *Value) bool {
break
}
val := auxIntToInt64(v_1.AuxInt)
- if !(val >= -2048 && val <= 2047) {
+ if !(is12Bit(val)) {
break
}
v.reset(OpRISCV64SLTIU)
@@ -7555,7 +7554,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SRA x (MOVDconst [val]))
- // result: (SRAI [int64(val&63)] x)
+ // result: (SRAI [val&63] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7563,7 +7562,7 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRAI)
- v.AuxInt = int64ToAuxInt(int64(val & 63))
+ v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x)
return true
}
@@ -7572,11 +7571,10 @@ func rewriteValueRISCV64_OpRISCV64SRA(v *Value) bool {
func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
- // match: (SRAI [x] (MOVWreg y))
+ // match: (SRAI [x] (MOVWreg y))
// cond: x >= 0 && x <= 31
- // result: (SRAIW [int64(x)] y)
+ // result: (SRAIW [x] y)
for {
- t := v.Type
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWreg {
break
@@ -7586,8 +7584,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
break
}
v.reset(OpRISCV64SRAIW)
- v.Type = t
- v.AuxInt = int64ToAuxInt(int64(x))
+ v.AuxInt = int64ToAuxInt(x)
v.AddArg(y)
return true
}
@@ -7633,7 +7630,7 @@ func rewriteValueRISCV64_OpRISCV64SRAI(v *Value) bool {
v.AddArg(v0)
return true
}
- // match: (SRAI [x] (MOVWreg y))
+ // match: (SRAI [x] (MOVWreg y))
// cond: x >= 32
// result: (SRAIW [31] y)
for {
@@ -7668,7 +7665,7 @@ func rewriteValueRISCV64_OpRISCV64SRAW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SRAW x (MOVDconst [val]))
- // result: (SRAIW [int64(val&31)] x)
+ // result: (SRAIW [val&31] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7676,7 +7673,7 @@ func rewriteValueRISCV64_OpRISCV64SRAW(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRAIW)
- v.AuxInt = int64ToAuxInt(int64(val & 31))
+ v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x)
return true
}
@@ -7686,7 +7683,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SRL x (MOVDconst [val]))
- // result: (SRLI [int64(val&63)] x)
+ // result: (SRLI [val&63] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7694,7 +7691,7 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRLI)
- v.AuxInt = int64ToAuxInt(int64(val & 63))
+ v.AuxInt = int64ToAuxInt(val & 63)
v.AddArg(x)
return true
}
@@ -7702,11 +7699,10 @@ func rewriteValueRISCV64_OpRISCV64SRL(v *Value) bool {
}
func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
v_0 := v.Args[0]
- // match: (SRLI [x] (MOVWUreg y))
+ // match: (SRLI [x] (MOVWUreg y))
// cond: x >= 0 && x <= 31
- // result: (SRLIW [int64(x)] y)
+ // result: (SRLIW [x] y)
for {
- t := v.Type
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWUreg {
break
@@ -7716,16 +7712,14 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break
}
v.reset(OpRISCV64SRLIW)
- v.Type = t
- v.AuxInt = int64ToAuxInt(int64(x))
+ v.AuxInt = int64ToAuxInt(x)
v.AddArg(y)
return true
}
- // match: (SRLI [x] (MOVBUreg y))
+ // match: (SRLI [x] (MOVBUreg y))
// cond: x >= 8
- // result: (MOVDconst [0])
+ // result: (MOVDconst [0])
for {
- t := v.Type
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVBUreg {
break
@@ -7734,15 +7728,13 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break
}
v.reset(OpRISCV64MOVDconst)
- v.Type = t
v.AuxInt = int64ToAuxInt(0)
return true
}
- // match: (SRLI [x] (MOVHUreg y))
+ // match: (SRLI [x] (MOVHUreg y))
// cond: x >= 16
- // result: (MOVDconst [0])
+ // result: (MOVDconst [0])
for {
- t := v.Type
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVHUreg {
break
@@ -7751,15 +7743,13 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break
}
v.reset(OpRISCV64MOVDconst)
- v.Type = t
v.AuxInt = int64ToAuxInt(0)
return true
}
- // match: (SRLI [x] (MOVWUreg y))
+ // match: (SRLI [x] (MOVWUreg y))
// cond: x >= 32
- // result: (MOVDconst [0])
+ // result: (MOVDconst [0])
for {
- t := v.Type
x := auxIntToInt64(v.AuxInt)
if v_0.Op != OpRISCV64MOVWUreg {
break
@@ -7768,7 +7758,6 @@ func rewriteValueRISCV64_OpRISCV64SRLI(v *Value) bool {
break
}
v.reset(OpRISCV64MOVDconst)
- v.Type = t
v.AuxInt = int64ToAuxInt(0)
return true
}
@@ -7790,7 +7779,7 @@ func rewriteValueRISCV64_OpRISCV64SRLW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SRLW x (MOVDconst [val]))
- // result: (SRLIW [int64(val&31)] x)
+ // result: (SRLIW [val&31] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
@@ -7798,7 +7787,7 @@ func rewriteValueRISCV64_OpRISCV64SRLW(v *Value) bool {
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SRLIW)
- v.AuxInt = int64ToAuxInt(int64(val & 31))
+ v.AuxInt = int64ToAuxInt(val & 31)
v.AddArg(x)
return true
}
--
cgit v1.3
From 0a569528ea355099af864f7612c3fa1973df30e4 Mon Sep 17 00:00:00 2001
From: Michael Munday
Date: Tue, 26 Aug 2025 21:17:36 +0100
Subject: cmd/compile: optimize comparisons with single bit difference
Optimize comparisons with constants that only differ by 1 bit (i.e.
a power of 2). For example:
x == 4 || x == 6 -> x|2 == 6
x != 1 && x != 5 -> x|4 != 5
Change-Id: Ic61719e5118446d21cf15652d9da22f7d95b2a15
Reviewed-on: https://go-review.googlesource.com/c/go/+/719420
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Auto-Submit: Keith Randall
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/generic.rules | 6 +
src/cmd/compile/internal/ssa/fuse.go | 8 +-
src/cmd/compile/internal/ssa/fuse_comparisons.go | 45 +++
src/cmd/compile/internal/ssa/rewritegeneric.go | 352 +++++++++++++++++++++++
test/codegen/fuse.go | 120 ++++++++
5 files changed, 530 insertions(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules
index 7e3aba1e5e..6efead03ad 100644
--- a/src/cmd/compile/internal/ssa/_gen/generic.rules
+++ b/src/cmd/compile/internal/ssa/_gen/generic.rules
@@ -337,6 +337,12 @@
(OrB ((Less|Leq)16U (Const16 [c]) x) (Leq16U x (Const16 [d]))) && uint16(c) >= uint16(d+1) && uint16(d+1) > uint16(d) => ((Less|Leq)16U (Const16 [c-d-1]) (Sub16 x (Const16 [d+1])))
(OrB ((Less|Leq)8U (Const8 [c]) x) (Leq8U x (Const8 [d]))) && uint8(c) >= uint8(d+1) && uint8(d+1) > uint8(d) => ((Less|Leq)8U (Const8 [c-d-1]) (Sub8 x (Const8 [d+1])))
+// single bit difference: ( x != c && x != d ) -> ( x|(c^d) != c )
+(AndB (Neq(64|32|16|8) x cv:(Const(64|32|16|8) [c])) (Neq(64|32|16|8) x (Const(64|32|16|8) [d]))) && c|d == c && oneBit(c^d) => (Neq(64|32|16|8) (Or(64|32|16|8) x (Const(64|32|16|8) [c^d])) cv)
+
+// single bit difference: ( x == c || x == d ) -> ( x|(c^d) == c )
+(OrB (Eq(64|32|16|8) x cv:(Const(64|32|16|8) [c])) (Eq(64|32|16|8) x (Const(64|32|16|8) [d]))) && c|d == c && oneBit(c^d) => (Eq(64|32|16|8) (Or(64|32|16|8) x (Const(64|32|16|8) [c^d])) cv)
+
// NaN check: ( x != x || x (>|>=|<|<=) c ) -> ( !(c (>=|>|<=|<) x) )
(OrB (Neq64F x x) ((Less|Leq)64F x y:(Const64F [c]))) => (Not ((Leq|Less)64F y x))
(OrB (Neq64F x x) ((Less|Leq)64F y:(Const64F [c]) x)) => (Not ((Leq|Less)64F x y))
diff --git a/src/cmd/compile/internal/ssa/fuse.go b/src/cmd/compile/internal/ssa/fuse.go
index 0cee91b532..e95064c1df 100644
--- a/src/cmd/compile/internal/ssa/fuse.go
+++ b/src/cmd/compile/internal/ssa/fuse.go
@@ -10,7 +10,9 @@ import (
)
// fuseEarly runs fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeNanCheck).
-func fuseEarly(f *Func) { fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeNanCheck) }
+func fuseEarly(f *Func) {
+ fuse(f, fuseTypePlain|fuseTypeIntInRange|fuseTypeSingleBitDifference|fuseTypeNanCheck)
+}
// fuseLate runs fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect).
func fuseLate(f *Func) { fuse(f, fuseTypePlain|fuseTypeIf|fuseTypeBranchRedirect) }
@@ -21,6 +23,7 @@ const (
fuseTypePlain fuseType = 1 << iota
fuseTypeIf
fuseTypeIntInRange
+ fuseTypeSingleBitDifference
fuseTypeNanCheck
fuseTypeBranchRedirect
fuseTypeShortCircuit
@@ -41,6 +44,9 @@ func fuse(f *Func, typ fuseType) {
if typ&fuseTypeIntInRange != 0 {
changed = fuseIntInRange(b) || changed
}
+ if typ&fuseTypeSingleBitDifference != 0 {
+ changed = fuseSingleBitDifference(b) || changed
+ }
if typ&fuseTypeNanCheck != 0 {
changed = fuseNanCheck(b) || changed
}
diff --git a/src/cmd/compile/internal/ssa/fuse_comparisons.go b/src/cmd/compile/internal/ssa/fuse_comparisons.go
index b6eb8fcb90..898c034485 100644
--- a/src/cmd/compile/internal/ssa/fuse_comparisons.go
+++ b/src/cmd/compile/internal/ssa/fuse_comparisons.go
@@ -19,6 +19,14 @@ func fuseNanCheck(b *Block) bool {
return fuseComparisons(b, canOptNanCheck)
}
+// fuseSingleBitDifference replaces the short-circuit operators between equality checks with
+// constants that only differ by a single bit. For example, it would convert
+// `if x == 4 || x == 6 { ... }` into `if (x == 4) | (x == 6) { ... }`. Rewrite rules can
+// then optimize these using a bitwise operation, in this case generating `if x|2 == 6 { ... }`.
+func fuseSingleBitDifference(b *Block) bool {
+ return fuseComparisons(b, canOptSingleBitDifference)
+}
+
// fuseComparisons looks for control graphs that match this pattern:
//
// p - predecessor
@@ -229,3 +237,40 @@ func canOptNanCheck(x, y *Value, op Op) bool {
}
return false
}
+
+// canOptSingleBitDifference returns true if x op y matches either:
+//
+// v == c || v == d
+// v != c && v != d
+//
+// Where c and d are constant values that differ by a single bit.
+func canOptSingleBitDifference(x, y *Value, op Op) bool {
+ if x.Op != y.Op {
+ return false
+ }
+ switch x.Op {
+ case OpEq64, OpEq32, OpEq16, OpEq8:
+ if op != OpOrB {
+ return false
+ }
+ case OpNeq64, OpNeq32, OpNeq16, OpNeq8:
+ if op != OpAndB {
+ return false
+ }
+ default:
+ return false
+ }
+
+ xi := getConstIntArgIndex(x)
+ if xi < 0 {
+ return false
+ }
+ yi := getConstIntArgIndex(y)
+ if yi < 0 {
+ return false
+ }
+ if x.Args[xi^1] != y.Args[yi^1] {
+ return false
+ }
+ return oneBit(x.Args[xi].AuxInt ^ y.Args[yi].AuxInt)
+}
diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go
index fd5139c0bb..2428f17947 100644
--- a/src/cmd/compile/internal/ssa/rewritegeneric.go
+++ b/src/cmd/compile/internal/ssa/rewritegeneric.go
@@ -5332,6 +5332,182 @@ func rewriteValuegeneric_OpAndB(v *Value) bool {
}
break
}
+ // match: (AndB (Neq64 x cv:(Const64 [c])) (Neq64 x (Const64 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Neq64 (Or64 x (Const64 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeq64 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst64 {
+ continue
+ }
+ c := auxIntToInt64(cv.AuxInt)
+ if v_1.Op != OpNeq64 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst64 {
+ continue
+ }
+ d := auxIntToInt64(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpNeq64)
+ v0 := b.NewValue0(v.Pos, OpOr64, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst64, x.Type)
+ v1.AuxInt = int64ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (AndB (Neq32 x cv:(Const32 [c])) (Neq32 x (Const32 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Neq32 (Or32 x (Const32 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeq32 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst32 {
+ continue
+ }
+ c := auxIntToInt32(cv.AuxInt)
+ if v_1.Op != OpNeq32 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst32 {
+ continue
+ }
+ d := auxIntToInt32(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpNeq32)
+ v0 := b.NewValue0(v.Pos, OpOr32, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst32, x.Type)
+ v1.AuxInt = int32ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (AndB (Neq16 x cv:(Const16 [c])) (Neq16 x (Const16 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Neq16 (Or16 x (Const16 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeq16 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst16 {
+ continue
+ }
+ c := auxIntToInt16(cv.AuxInt)
+ if v_1.Op != OpNeq16 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst16 {
+ continue
+ }
+ d := auxIntToInt16(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpNeq16)
+ v0 := b.NewValue0(v.Pos, OpOr16, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst16, x.Type)
+ v1.AuxInt = int16ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (AndB (Neq8 x cv:(Const8 [c])) (Neq8 x (Const8 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Neq8 (Or8 x (Const8 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeq8 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst8 {
+ continue
+ }
+ c := auxIntToInt8(cv.AuxInt)
+ if v_1.Op != OpNeq8 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst8 {
+ continue
+ }
+ d := auxIntToInt8(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpNeq8)
+ v0 := b.NewValue0(v.Pos, OpOr8, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst8, x.Type)
+ v1.AuxInt = int8ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
return false
}
func rewriteValuegeneric_OpArraySelect(v *Value) bool {
@@ -23242,6 +23418,182 @@ func rewriteValuegeneric_OpOrB(v *Value) bool {
}
break
}
+ // match: (OrB (Eq64 x cv:(Const64 [c])) (Eq64 x (Const64 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Eq64 (Or64 x (Const64 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpEq64 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst64 {
+ continue
+ }
+ c := auxIntToInt64(cv.AuxInt)
+ if v_1.Op != OpEq64 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst64 {
+ continue
+ }
+ d := auxIntToInt64(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpEq64)
+ v0 := b.NewValue0(v.Pos, OpOr64, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst64, x.Type)
+ v1.AuxInt = int64ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (OrB (Eq32 x cv:(Const32 [c])) (Eq32 x (Const32 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Eq32 (Or32 x (Const32 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpEq32 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst32 {
+ continue
+ }
+ c := auxIntToInt32(cv.AuxInt)
+ if v_1.Op != OpEq32 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst32 {
+ continue
+ }
+ d := auxIntToInt32(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpEq32)
+ v0 := b.NewValue0(v.Pos, OpOr32, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst32, x.Type)
+ v1.AuxInt = int32ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (OrB (Eq16 x cv:(Const16 [c])) (Eq16 x (Const16 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Eq16 (Or16 x (Const16 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpEq16 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst16 {
+ continue
+ }
+ c := auxIntToInt16(cv.AuxInt)
+ if v_1.Op != OpEq16 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst16 {
+ continue
+ }
+ d := auxIntToInt16(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpEq16)
+ v0 := b.NewValue0(v.Pos, OpOr16, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst16, x.Type)
+ v1.AuxInt = int16ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
+ // match: (OrB (Eq8 x cv:(Const8 [c])) (Eq8 x (Const8 [d])))
+ // cond: c|d == c && oneBit(c^d)
+ // result: (Eq8 (Or8 x (Const8 [c^d])) cv)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpEq8 {
+ continue
+ }
+ _ = v_0.Args[1]
+ v_0_0 := v_0.Args[0]
+ v_0_1 := v_0.Args[1]
+ for _i1 := 0; _i1 <= 1; _i1, v_0_0, v_0_1 = _i1+1, v_0_1, v_0_0 {
+ x := v_0_0
+ cv := v_0_1
+ if cv.Op != OpConst8 {
+ continue
+ }
+ c := auxIntToInt8(cv.AuxInt)
+ if v_1.Op != OpEq8 {
+ continue
+ }
+ _ = v_1.Args[1]
+ v_1_0 := v_1.Args[0]
+ v_1_1 := v_1.Args[1]
+ for _i2 := 0; _i2 <= 1; _i2, v_1_0, v_1_1 = _i2+1, v_1_1, v_1_0 {
+ if x != v_1_0 || v_1_1.Op != OpConst8 {
+ continue
+ }
+ d := auxIntToInt8(v_1_1.AuxInt)
+ if !(c|d == c && oneBit(c^d)) {
+ continue
+ }
+ v.reset(OpEq8)
+ v0 := b.NewValue0(v.Pos, OpOr8, x.Type)
+ v1 := b.NewValue0(v.Pos, OpConst8, x.Type)
+ v1.AuxInt = int8ToAuxInt(c ^ d)
+ v0.AddArg2(x, v1)
+ v.AddArg2(v0, cv)
+ return true
+ }
+ }
+ }
+ break
+ }
// match: (OrB (Neq64F x x) (Less64F x y:(Const64F [c])))
// result: (Not (Leq64F y x))
for {
diff --git a/test/codegen/fuse.go b/test/codegen/fuse.go
index 4fbb03bef8..e5a28549dc 100644
--- a/test/codegen/fuse.go
+++ b/test/codegen/fuse.go
@@ -198,6 +198,126 @@ func ui4d(c <-chan uint8) {
}
}
+// ------------------------------------ //
+// single bit difference (conjunction) //
+// ------------------------------------ //
+
+func sisbc64(c <-chan int64) {
+ // amd64: "ORQ [$]2,"
+ // riscv64: "ORI [$]2,"
+ for x := <-c; x != 4 && x != 6; x = <-c {
+ }
+}
+
+func sisbc32(c <-chan int32) {
+ // amd64: "ORL [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x != -1 && x != -5; x = <-c {
+ }
+}
+
+func sisbc16(c <-chan int16) {
+ // amd64: "ORL [$]32,"
+ // riscv64: "ORI [$]32,"
+ for x := <-c; x != 16 && x != 48; x = <-c {
+ }
+}
+
+func sisbc8(c <-chan int8) {
+ // amd64: "ORL [$]16,"
+ // riscv64: "ORI [$]16,"
+ for x := <-c; x != -15 && x != -31; x = <-c {
+ }
+}
+
+func uisbc64(c <-chan uint64) {
+ // amd64: "ORQ [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x != 1 && x != 5; x = <-c {
+ }
+}
+
+func uisbc32(c <-chan uint32) {
+ // amd64: "ORL [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x != 2 && x != 6; x = <-c {
+ }
+}
+
+func uisbc16(c <-chan uint16) {
+ // amd64: "ORL [$]32,"
+ // riscv64: "ORI [$]32,"
+ for x := <-c; x != 16 && x != 48; x = <-c {
+ }
+}
+
+func uisbc8(c <-chan uint8) {
+ // amd64: "ORL [$]64,"
+ // riscv64: "ORI [$]64,"
+ for x := <-c; x != 64 && x != 0; x = <-c {
+ }
+}
+
+// ------------------------------------ //
+// single bit difference (disjunction) //
+// ------------------------------------ //
+
+func sisbd64(c <-chan int64) {
+ // amd64: "ORQ [$]2,"
+ // riscv64: "ORI [$]2,"
+ for x := <-c; x == 4 || x == 6; x = <-c {
+ }
+}
+
+func sisbd32(c <-chan int32) {
+ // amd64: "ORL [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x == -1 || x == -5; x = <-c {
+ }
+}
+
+func sisbd16(c <-chan int16) {
+ // amd64: "ORL [$]32,"
+ // riscv64: "ORI [$]32,"
+ for x := <-c; x == 16 || x == 48; x = <-c {
+ }
+}
+
+func sisbd8(c <-chan int8) {
+ // amd64: "ORL [$]16,"
+ // riscv64: "ORI [$]16,"
+ for x := <-c; x == -15 || x == -31; x = <-c {
+ }
+}
+
+func uisbd64(c <-chan uint64) {
+ // amd64: "ORQ [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x == 1 || x == 5; x = <-c {
+ }
+}
+
+func uisbd32(c <-chan uint32) {
+ // amd64: "ORL [$]4,"
+ // riscv64: "ORI [$]4,"
+ for x := <-c; x == 2 || x == 6; x = <-c {
+ }
+}
+
+func uisbd16(c <-chan uint16) {
+ // amd64: "ORL [$]32,"
+ // riscv64: "ORI [$]32,"
+ for x := <-c; x == 16 || x == 48; x = <-c {
+ }
+}
+
+func uisbd8(c <-chan uint8) {
+ // amd64: "ORL [$]64,"
+ // riscv64: "ORI [$]64,"
+ for x := <-c; x == 64 || x == 0; x = <-c {
+ }
+}
+
// -------------------------------------//
// merge NaN checks //
// ------------------------------------ //
--
cgit v1.3
From b57962b7c7de2b70fa943e66cd26b2cce631b7f8 Mon Sep 17 00:00:00 2001
From: Aaron Chen
Date: Thu, 13 Nov 2025 01:43:03 +0000
Subject: bytes: fix panic in bytes.Buffer.Peek
This change fixed the overlooked offset in bytes.Buffer.Peek.
Otherwise, it will either return wrong result or panic with
"runtime error: slice bounds out of range".
Change-Id: Ic42fd8a27fb9703c51430f298933b91cf0d45451
GitHub-Last-Rev: fb97ebc3b188959835706626f66898d6306c16fb
GitHub-Pull-Request: golang/go#76165
Reviewed-on: https://go-review.googlesource.com/c/go/+/717640
Reviewed-by: Michael Pratt
Auto-Submit: Michael Pratt
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
---
src/bytes/buffer.go | 2 +-
src/bytes/buffer_test.go | 18 ++++++++++++------
2 files changed, 13 insertions(+), 7 deletions(-)
(limited to 'src')
diff --git a/src/bytes/buffer.go b/src/bytes/buffer.go
index 3eb5b350c3..6cb4d6a8f6 100644
--- a/src/bytes/buffer.go
+++ b/src/bytes/buffer.go
@@ -86,7 +86,7 @@ func (b *Buffer) Peek(n int) ([]byte, error) {
if b.Len() < n {
return b.buf[b.off:], io.EOF
}
- return b.buf[b.off:n], nil
+ return b.buf[b.off : b.off+n], nil
}
// empty reports whether the unread portion of the buffer is empty.
diff --git a/src/bytes/buffer_test.go b/src/bytes/buffer_test.go
index 5f5cc483b0..9c1ba0a838 100644
--- a/src/bytes/buffer_test.go
+++ b/src/bytes/buffer_test.go
@@ -533,19 +533,25 @@ func TestReadString(t *testing.T) {
var peekTests = []struct {
buffer string
+ skip int
n int
expected string
err error
}{
- {"", 0, "", nil},
- {"aaa", 3, "aaa", nil},
- {"foobar", 2, "fo", nil},
- {"a", 2, "a", io.EOF},
+ {"", 0, 0, "", nil},
+ {"aaa", 0, 3, "aaa", nil},
+ {"foobar", 0, 2, "fo", nil},
+ {"a", 0, 2, "a", io.EOF},
+ {"helloworld", 4, 3, "owo", nil},
+ {"helloworld", 5, 5, "world", nil},
+ {"helloworld", 5, 6, "world", io.EOF},
+ {"helloworld", 10, 1, "", io.EOF},
}
func TestPeek(t *testing.T) {
for _, test := range peekTests {
buf := NewBufferString(test.buffer)
+ buf.Next(test.skip)
bytes, err := buf.Peek(test.n)
if string(bytes) != test.expected {
t.Errorf("expected %q, got %q", test.expected, bytes)
@@ -553,8 +559,8 @@ func TestPeek(t *testing.T) {
if err != test.err {
t.Errorf("expected error %v, got %v", test.err, err)
}
- if buf.Len() != len(test.buffer) {
- t.Errorf("bad length after peek: %d, want %d", buf.Len(), len(test.buffer))
+ if buf.Len() != len(test.buffer)-test.skip {
+ t.Errorf("bad length after peek: %d, want %d", buf.Len(), len(test.buffer)-test.skip)
}
}
}
--
cgit v1.3
From 2cdcc4150bc577e0b40a9cedaaa7c8301f2860cd Mon Sep 17 00:00:00 2001
From: Meng Zhuo
Date: Fri, 14 Nov 2025 12:47:35 +0800
Subject: cmd/compile: fold negation into multiplication
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
goos: linux
goarch: riscv64
pkg: cmd/compile/internal/test
cpu: Spacemit(R) X60
│ /root/mul.base.log │ /root/mul.new.log │
│ sec/op │ sec/op vs base │
MulNeg 6.426µ ± 0% 4.501µ ± 0% -29.96% (p=0.000 n=10)
Mul2Neg 9.000µ ± 0% 6.431µ ± 0% -28.54% (p=0.000 n=10)
Mul2 1.263µ ± 0% 1.263µ ± 0% ~ (p=1.000 n=10)
MulNeg2 1.577µ ± 0% 1.577µ ± 0% ~ (p=0.211 n=10)
geomean 3.276µ 2.756µ -15.89%
goos: linux
goarch: amd64
pkg: cmd/compile/internal/test
cpu: AMD EPYC 7532 32-Core Processor
│ /root/base │ /root/new │
│ sec/op │ sec/op vs base │
MulNeg 691.9n ± 1% 319.4n ± 0% -53.83% (p=0.000 n=10)
Mul2Neg 630.0n ± 0% 629.6n ± 0% -0.07% (p=0.000 n=10)
Mul2 438.1n ± 0% 438.1n ± 0% ~ (p=0.728 n=10)
MulNeg2 439.3n ± 0% 439.4n ± 0% ~ (p=0.656 n=10)
geomean 538.2n 443.6n -17.58%
Change-Id: Ice8e6c8d1e8e3009ba8a0b1b689205174e199019
Reviewed-on: https://go-review.googlesource.com/c/go/+/720180
Reviewed-by: abner chenc
Reviewed-by: Keith Randall
Reviewed-by: Junyang Shao
Reviewed-by: Joel Sing
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/LOONG64.rules | 3 -
src/cmd/compile/internal/ssa/_gen/generic.rules | 7 +-
src/cmd/compile/internal/ssa/rewriteLOONG64.go | 39 ------
src/cmd/compile/internal/ssa/rewritegeneric.go | 156 ++++++++++++++++++++++++
test/codegen/arithmetic.go | 12 +-
5 files changed, 171 insertions(+), 46 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules
index 9691296043..2beba0b1c5 100644
--- a/src/cmd/compile/internal/ssa/_gen/LOONG64.rules
+++ b/src/cmd/compile/internal/ssa/_gen/LOONG64.rules
@@ -743,9 +743,6 @@
(MULV x (MOVVconst [c])) && canMulStrengthReduce(config, c) => {mulStrengthReduce(v, x, c)}
-(MULV (NEGV x) (MOVVconst [c])) => (MULV x (MOVVconst [-c]))
-(MULV (NEGV x) (NEGV y)) => (MULV x y)
-
(ADDV x0 x1:(SLLVconst [c] y)) && x1.Uses == 1 && c > 0 && c <= 4 => (ADDshiftLLV x0 y [c])
// fold constant in ADDshift op
diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules
index 6efead03ad..e09cd31c31 100644
--- a/src/cmd/compile/internal/ssa/_gen/generic.rules
+++ b/src/cmd/compile/internal/ssa/_gen/generic.rules
@@ -195,6 +195,11 @@
// Convert x * -1 to -x.
(Mul(8|16|32|64) (Const(8|16|32|64) [-1]) x) => (Neg(8|16|32|64) x)
+// Convert -x * c to x * -c
+(Mul(8|16|32|64) (Const(8|16|32|64) [c]) (Neg(8|16|32|64) x)) => (Mul(8|16|32|64) x (Const(8|16|32|64) [-c]))
+
+(Mul(8|16|32|64) (Neg(8|16|32|64) x) (Neg(8|16|32|64) y)) => (Mul(8|16|32|64) x y)
+
// DeMorgan's Laws
(And(8|16|32|64) (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (Or(8|16|32|64) x y))
(Or(8|16|32|64) (Com(8|16|32|64) x) (Com(8|16|32|64) y)) => (Com(8|16|32|64) (And(8|16|32|64) x y))
@@ -2228,4 +2233,4 @@
(Neq(64|32|16) (SignExt8to(64|32|16) (CvtBoolToUint8 x)) (Const(64|32|16) [0])) => x
(Neq(64|32|16) (SignExt8to(64|32|16) (CvtBoolToUint8 x)) (Const(64|32|16) [1])) => (Not x)
(Eq(64|32|16) (SignExt8to(64|32|16) (CvtBoolToUint8 x)) (Const(64|32|16) [1])) => x
-(Eq(64|32|16) (SignExt8to(64|32|16) (CvtBoolToUint8 x)) (Const(64|32|16) [0])) => (Not x)
\ No newline at end of file
+(Eq(64|32|16) (SignExt8to(64|32|16) (CvtBoolToUint8 x)) (Const(64|32|16) [0])) => (Not x)
diff --git a/src/cmd/compile/internal/ssa/rewriteLOONG64.go b/src/cmd/compile/internal/ssa/rewriteLOONG64.go
index 4262d4e0fb..bf2dd114a9 100644
--- a/src/cmd/compile/internal/ssa/rewriteLOONG64.go
+++ b/src/cmd/compile/internal/ssa/rewriteLOONG64.go
@@ -5866,7 +5866,6 @@ func rewriteValueLOONG64_OpLOONG64MULV(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
config := b.Func.Config
- typ := &b.Func.Config.Types
// match: (MULV _ (MOVVconst [0]))
// result: (MOVVconst [0])
for {
@@ -5911,44 +5910,6 @@ func rewriteValueLOONG64_OpLOONG64MULV(v *Value) bool {
}
break
}
- // match: (MULV (NEGV x) (MOVVconst [c]))
- // result: (MULV x (MOVVconst [-c]))
- for {
- for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
- if v_0.Op != OpLOONG64NEGV {
- continue
- }
- x := v_0.Args[0]
- if v_1.Op != OpLOONG64MOVVconst {
- continue
- }
- c := auxIntToInt64(v_1.AuxInt)
- v.reset(OpLOONG64MULV)
- v0 := b.NewValue0(v.Pos, OpLOONG64MOVVconst, typ.UInt64)
- v0.AuxInt = int64ToAuxInt(-c)
- v.AddArg2(x, v0)
- return true
- }
- break
- }
- // match: (MULV (NEGV x) (NEGV y))
- // result: (MULV x y)
- for {
- for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
- if v_0.Op != OpLOONG64NEGV {
- continue
- }
- x := v_0.Args[0]
- if v_1.Op != OpLOONG64NEGV {
- continue
- }
- y := v_1.Args[0]
- v.reset(OpLOONG64MULV)
- v.AddArg2(x, y)
- return true
- }
- break
- }
// match: (MULV (MOVVconst [c]) (MOVVconst [d]))
// result: (MOVVconst [c*d])
for {
diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go
index 2428f17947..1621153b43 100644
--- a/src/cmd/compile/internal/ssa/rewritegeneric.go
+++ b/src/cmd/compile/internal/ssa/rewritegeneric.go
@@ -16786,6 +16786,45 @@ func rewriteValuegeneric_OpMul16(v *Value) bool {
}
break
}
+ // match: (Mul16 (Const16 [c]) (Neg16 x))
+ // result: (Mul16 x (Const16 [-c]))
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpConst16 {
+ continue
+ }
+ t := v_0.Type
+ c := auxIntToInt16(v_0.AuxInt)
+ if v_1.Op != OpNeg16 {
+ continue
+ }
+ x := v_1.Args[0]
+ v.reset(OpMul16)
+ v0 := b.NewValue0(v.Pos, OpConst16, t)
+ v0.AuxInt = int16ToAuxInt(-c)
+ v.AddArg2(x, v0)
+ return true
+ }
+ break
+ }
+ // match: (Mul16 (Neg16 x) (Neg16 y))
+ // result: (Mul16 x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeg16 {
+ continue
+ }
+ x := v_0.Args[0]
+ if v_1.Op != OpNeg16 {
+ continue
+ }
+ y := v_1.Args[0]
+ v.reset(OpMul16)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
// match: (Mul16 (Const16 [c]) (Add16 (Const16 [d]) x))
// cond: !isPowerOfTwo(c)
// result: (Add16 (Const16 [c*d]) (Mul16 (Const16 [c]) x))
@@ -16997,6 +17036,45 @@ func rewriteValuegeneric_OpMul32(v *Value) bool {
}
break
}
+ // match: (Mul32 (Const32 [c]) (Neg32 x))
+ // result: (Mul32 x (Const32 [-c]))
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpConst32 {
+ continue
+ }
+ t := v_0.Type
+ c := auxIntToInt32(v_0.AuxInt)
+ if v_1.Op != OpNeg32 {
+ continue
+ }
+ x := v_1.Args[0]
+ v.reset(OpMul32)
+ v0 := b.NewValue0(v.Pos, OpConst32, t)
+ v0.AuxInt = int32ToAuxInt(-c)
+ v.AddArg2(x, v0)
+ return true
+ }
+ break
+ }
+ // match: (Mul32 (Neg32 x) (Neg32 y))
+ // result: (Mul32 x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeg32 {
+ continue
+ }
+ x := v_0.Args[0]
+ if v_1.Op != OpNeg32 {
+ continue
+ }
+ y := v_1.Args[0]
+ v.reset(OpMul32)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
// match: (Mul32 (Const32 [c]) (Add32 (Const32 [d]) x))
// cond: !isPowerOfTwo(c)
// result: (Add32 (Const32 [c*d]) (Mul32 (Const32 [c]) x))
@@ -17369,6 +17447,45 @@ func rewriteValuegeneric_OpMul64(v *Value) bool {
}
break
}
+ // match: (Mul64 (Const64 [c]) (Neg64 x))
+ // result: (Mul64 x (Const64 [-c]))
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpConst64 {
+ continue
+ }
+ t := v_0.Type
+ c := auxIntToInt64(v_0.AuxInt)
+ if v_1.Op != OpNeg64 {
+ continue
+ }
+ x := v_1.Args[0]
+ v.reset(OpMul64)
+ v0 := b.NewValue0(v.Pos, OpConst64, t)
+ v0.AuxInt = int64ToAuxInt(-c)
+ v.AddArg2(x, v0)
+ return true
+ }
+ break
+ }
+ // match: (Mul64 (Neg64 x) (Neg64 y))
+ // result: (Mul64 x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeg64 {
+ continue
+ }
+ x := v_0.Args[0]
+ if v_1.Op != OpNeg64 {
+ continue
+ }
+ y := v_1.Args[0]
+ v.reset(OpMul64)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
// match: (Mul64 (Const64 [c]) (Add64 (Const64 [d]) x))
// cond: !isPowerOfTwo(c)
// result: (Add64 (Const64 [c*d]) (Mul64 (Const64 [c]) x))
@@ -17741,6 +17858,45 @@ func rewriteValuegeneric_OpMul8(v *Value) bool {
}
break
}
+ // match: (Mul8 (Const8 [c]) (Neg8 x))
+ // result: (Mul8 x (Const8 [-c]))
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpConst8 {
+ continue
+ }
+ t := v_0.Type
+ c := auxIntToInt8(v_0.AuxInt)
+ if v_1.Op != OpNeg8 {
+ continue
+ }
+ x := v_1.Args[0]
+ v.reset(OpMul8)
+ v0 := b.NewValue0(v.Pos, OpConst8, t)
+ v0.AuxInt = int8ToAuxInt(-c)
+ v.AddArg2(x, v0)
+ return true
+ }
+ break
+ }
+ // match: (Mul8 (Neg8 x) (Neg8 y))
+ // result: (Mul8 x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ if v_0.Op != OpNeg8 {
+ continue
+ }
+ x := v_0.Args[0]
+ if v_1.Op != OpNeg8 {
+ continue
+ }
+ y := v_1.Args[0]
+ v.reset(OpMul8)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
// match: (Mul8 (Const8 [c]) (Add8 (Const8 [d]) x))
// cond: !isPowerOfTwo(c)
// result: (Add8 (Const8 [c*d]) (Mul8 (Const8 [c]) x))
diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go
index 42d5d2ef65..6b2c5529e1 100644
--- a/test/codegen/arithmetic.go
+++ b/test/codegen/arithmetic.go
@@ -318,13 +318,19 @@ func MergeMuls5(a, n int) int {
// Multiplications folded negation
func FoldNegMul(a int) int {
- // loong64:"SUBVU" "ALSLV [$]2" "ALSLV [$]1"
- return (-a) * 11
+ // amd64:"IMUL3Q [$]-11" -"NEGQ"
+ // arm64:"MOVD [$]-11" "MUL" -"NEG"
+ // loong64:"ALSLV [$]2" "SUBVU" "ALSLV [$]4"
+ // riscv64:"MOV [$]-11" "MUL" -"NEG"
+ return -a * 11
}
func Fold2NegMul(a, b int) int {
+ // amd64:"IMULQ" -"NEGQ"
+ // arm64:"MUL" -"NEG"
// loong64:"MULV" -"SUBVU R[0-9], R0,"
- return (-a) * (-b)
+ // riscv64:"MUL" -"NEG"
+ return -a * -b
}
// -------------- //
--
cgit v1.3
From a0e738c657d33e2a648838546812fb50cf42e41d Mon Sep 17 00:00:00 2001
From: Mark Ryan
Date: Thu, 13 Nov 2025 12:28:29 +0100
Subject: cmd/compile/internal: remove incorrect riscv64 SLTI rule
The rule
(SLTI [x] (ORI [y] _)) && y >= 0 && int64(y) >= int64(x) => (MOVDconst [0])
is incorrect as it only generates correct code if the unknown value
being compared is >= 0. If the unknown value is < 0 the rule will
incorrectly produce a constant value of 0, whereas the code optimized
away by the rule would have produced a value of 1.
A new test that causes the faulty rule to generate incorrect code
is also added to ensure that the error does not return.
Change-Id: I69224e0776596f1b9538acf9dacf9009d305f966
Reviewed-on: https://go-review.googlesource.com/c/go/+/720220
Reviewed-by: Meng Zhuo
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-by: Joel Sing
Auto-Submit: Keith Randall
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/RISCV64.rules | 1 -
src/cmd/compile/internal/ssa/rewriteRISCV64.go | 16 ----------------
src/cmd/compile/internal/test/testdata/arith_test.go | 14 ++++++++++++++
3 files changed, 14 insertions(+), 17 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
index 6166bd5584..13a8cab3b5 100644
--- a/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
+++ b/src/cmd/compile/internal/ssa/_gen/RISCV64.rules
@@ -792,7 +792,6 @@
// SLTI/SLTIU with known outcomes.
(SLTI [x] (ANDI [y] _)) && y >= 0 && int64(y) < int64(x) => (MOVDconst [1])
(SLTIU [x] (ANDI [y] _)) && y >= 0 && uint64(y) < uint64(x) => (MOVDconst [1])
-(SLTI [x] (ORI [y] _)) && y >= 0 && int64(y) >= int64(x) => (MOVDconst [0])
(SLTIU [x] (ORI [y] _)) && y >= 0 && uint64(y) >= uint64(x) => (MOVDconst [0])
// SLT/SLTU with known outcomes.
diff --git a/src/cmd/compile/internal/ssa/rewriteRISCV64.go b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
index 24fef3fe72..284d88967b 100644
--- a/src/cmd/compile/internal/ssa/rewriteRISCV64.go
+++ b/src/cmd/compile/internal/ssa/rewriteRISCV64.go
@@ -7362,22 +7362,6 @@ func rewriteValueRISCV64_OpRISCV64SLTI(v *Value) bool {
v.AuxInt = int64ToAuxInt(1)
return true
}
- // match: (SLTI [x] (ORI [y] _))
- // cond: y >= 0 && int64(y) >= int64(x)
- // result: (MOVDconst [0])
- for {
- x := auxIntToInt64(v.AuxInt)
- if v_0.Op != OpRISCV64ORI {
- break
- }
- y := auxIntToInt64(v_0.AuxInt)
- if !(y >= 0 && int64(y) >= int64(x)) {
- break
- }
- v.reset(OpRISCV64MOVDconst)
- v.AuxInt = int64ToAuxInt(0)
- return true
- }
return false
}
func rewriteValueRISCV64_OpRISCV64SLTIU(v *Value) bool {
diff --git a/src/cmd/compile/internal/test/testdata/arith_test.go b/src/cmd/compile/internal/test/testdata/arith_test.go
index 8984cd3e26..34ac73c068 100644
--- a/src/cmd/compile/internal/test/testdata/arith_test.go
+++ b/src/cmd/compile/internal/test/testdata/arith_test.go
@@ -444,6 +444,19 @@ func testBitwiseRshU_ssa(a uint32, b, c uint32) uint32 {
return a >> b >> c
}
+//go:noinline
+func orLt_ssa(x int) bool {
+ y := x - x
+ return (x | 2) < y
+}
+
+// test riscv64 SLTI rules
+func testSetIfLessThan(t *testing.T) {
+ if want, got := true, orLt_ssa(-7); got != want {
+ t.Errorf("orLt_ssa(-7) = %t want %t", got, want)
+ }
+}
+
//go:noinline
func testShiftCX_ssa() int {
v1 := uint8(3)
@@ -977,6 +990,7 @@ func TestArithmetic(t *testing.T) {
testRegallocCVSpill(t)
testSubqToNegq(t)
testBitwiseLogic(t)
+ testSetIfLessThan(t)
testOcom(t)
testLrot(t)
testShiftCX(t)
--
cgit v1.3
From b24aec598b9777de6044cf4f52042d3b3acc0f35 Mon Sep 17 00:00:00 2001
From: Mark Ryan
Date: Wed, 26 Feb 2025 08:57:30 +0100
Subject: doc, cmd/internal/obj/riscv: document the riscv64 assembler
Add documentation for the riscv64 assembler with a link to the
documentation from asm.html. Architecture specific assembler
documentation is provided for the other architectures but has
been missing for riscv64 until now.
Change-Id: I62ed7e6a2a4b52e0720d869e964b29e2a980223a
Reviewed-on: https://go-review.googlesource.com/c/go/+/652717
Reviewed-by: Joel Sing
Reviewed-by: Michael Pratt
Reviewed-by: Meng Zhuo
Auto-Submit: Joel Sing
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
---
doc/asm.html | 6 +
src/cmd/internal/obj/riscv/doc.go | 297 ++++++++++++++++++++++++++++++++++++++
2 files changed, 303 insertions(+)
create mode 100644 src/cmd/internal/obj/riscv/doc.go
(limited to 'src')
diff --git a/doc/asm.html b/doc/asm.html
index dd395ec833..5db13905e3 100644
--- a/doc/asm.html
+++ b/doc/asm.html
@@ -1039,6 +1039,12 @@ The value of GOMIPS64 environment variable (hardfloat
GOMIPS64_hardfloat or GOMIPS64_softfloat.
+RISCV64
+
+
+Reference: Go RISCV64 Assembly Instructions Reference Manual
+
+
Unsupported opcodes
diff --git a/src/cmd/internal/obj/riscv/doc.go b/src/cmd/internal/obj/riscv/doc.go
new file mode 100644
index 0000000000..365bedd299
--- /dev/null
+++ b/src/cmd/internal/obj/riscv/doc.go
@@ -0,0 +1,297 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package riscv implements the riscv64 assembler.
+
+# Register naming
+
+The integer registers are named X0 through to X31, however X4 must be accessed
+through its RISC-V ABI name, TP, and X27, which holds a pointer to the Go
+routine structure, must be referred to as g. Additionally, when building in
+shared mode, X3 is unavailable and must be accessed via its RISC-V ABI name,
+GP.
+
+The floating-point registers are named F0 through to F31.
+
+The vector registers are named V0 through to V31.
+
+Both integer and floating-point registers can be referred to by their RISC-V
+ABI names, e.g., A0 or FT0, with the exception that X27 cannot be referred to
+by its RISC-V ABI name, S11. It must be referred to as g.
+
+Some of the integer registers are used by the Go runtime and assembler - X26 is
+the closure pointer, X27 points to the Go routine structure and X31 is a
+temporary register used by the Go assembler. Use of X31 should be avoided in
+hand written assembly code as its value could be altered by the instruction
+sequences emitted by the assembler.
+
+# Instruction naming
+
+Many RISC-V instructions contain one or more suffixes in their names. In the
+[RISC-V ISA Manual] these suffixes are separated from themselves and the
+name of the instruction mnemonic with a dot ('.'). In the Go assembler, the
+separators are omitted and the suffixes are written in upper case.
+
+Example:
+
+ FMVWX <=> fmv.w.x
+
+# Rounding modes
+
+The Go toolchain does not set the FCSR register and requires the desired
+rounding mode to be explicitly encoded within floating-point instructions.
+The syntax the Go assembler uses to specify the rounding modes differs
+from the syntax in the RISC-V specifications. In the [RISC-V ISA Manual]
+the rounding mode is given as an extra operand at the end of an
+assembly language instruction. In the Go assembler, the rounding modes are
+converted to uppercase and follow the instruction mnemonic from which they
+are separated with a dot ('.').
+
+Example:
+
+ FCVTLUS.RNE F0, X5 <=> fcvt.lu.s x5, f0, rne
+
+RTZ is assumed if the rounding mode is omitted.
+
+# RISC-V extensions
+
+By default the Go compiler targets the [rva20u64] profile. This profile mandates
+all the general RISC-V instructions, allowing Go to use integer, multiplication,
+division, floating-point and atomic instructions without having to
+perform compile time or runtime checks to verify that their use is appropriate
+for the target hardware. All widely available riscv64 devices support at least
+[rva20u64]. The Go toolchain can be instructed to target later RISC-V profiles,
+including, [rva22u64] and [rva23u64], via the GORISCV64 environment variable.
+Instructions that are provided by newer profiles cannot typically be used in
+handwritten assembly code without compile time guards (or runtime checks)
+that ensure they are hardware supported.
+
+The file asm_riscv64.h defines macros for each RISC-V extension that is enabled
+by setting the GORISCV64 environment variable to a value other than [rva20u64].
+For example, if GORISCV64=rva22u64 the macros hasZba, hasZbb and hasZbs will be
+defined. If GORISCV64=rva23u64 hasV will be defined in addition to hasZba,
+hasZbb and hasZbs. These macros can be used to determine whether it's safe
+to use an instruction in hand-written assembly.
+
+It is not always necessary to include asm_riscv64.h and use #ifdefs in your
+code to safely take advantage of instructions present in the [rva22u64]
+profile. In some cases the assembler can generate [rva20u64] compatible code
+even when an [rva22u64] instruction is used in an assembly source file. When
+GORISCV64=rva20u64 the assembler will synthesize certain [rva22u64]
+instructions, e.g., ANDN, using multiple [rva20u64] instructions. Instructions
+such as ANDN can then be freely used in assembly code without checking to see
+whether the instruction is supported by the target profile. When building a
+source file containing the ANDN instruction with GORISCV64=rva22u64 the
+assembler will emit the Zbb ANDN instruction directly. When building the same
+source file with GORISCV64=rva20u64 the assembler will emit multiple [rva20u64]
+instructions to synthesize ANDN.
+
+The assembler will also use [rva22u64] instructions to implement the zero and
+sign extension instructions, e.g., MOVB and MOVHU, when GORISCV64=rva22u64 or
+greater.
+
+The instructions not implemented in the default profile ([rva20u64]) that can
+be safely used in assembly code without compile time checks are:
+
+ - ANDN
+ - MAX
+ - MAXU
+ - MIN
+ - MINU
+ - MOVB
+ - MOVH
+ - MOVHU
+ - MOVWU
+ - ORN
+ - ROL
+ - ROLW
+ - ROR
+ - RORI
+ - RORIW
+ - RORW
+ - XNOR
+
+# Operand ordering
+
+The ordering used for instruction operands in the Go assembler differs from the
+ordering defined in the [RISC-V ISA Manual].
+
+1. R-Type instructions
+
+R-Type instructions are written in the reverse order to that given in the
+[RISC-V ISA Manual], with the register order being rs2, rs1, rd.
+
+Examples:
+
+ ADD X10, X11, X12 <=> add x12, x11, x10
+ FADDD F10, F11, F12 <=> fadd.d f12, f11, f10
+
+2. I-Type arithmetic instructions
+
+I-Type arithmetic instructions (not loads, fences, ebreak, ecall) use the same
+ordering as the R-Type instructions, typically, imm12, rs1, rd.
+
+Examples:
+
+ ADDI $1, X11, X12 <=> add x12, x11, 1
+ SLTI $1, X11, X12 <=> slti x12, x11, 1
+
+3. Loads and Stores
+
+Load instructions are written with the source operand (whether it be a register
+or a memory address), first followed by the destination operand.
+
+Examples:
+
+ MOV 16(X2), X10 <=> ld x10, 16(x2)
+ MOV X10, (X2) <=> sd x10, 0(x2)
+
+4. Branch instructions
+
+The branch instructions use the same operand ordering as is given in the
+[RISC-V ISA Manual], e.g., rs1, rs2, label.
+
+Example:
+
+ BLT X12, X23, loop1 <=> blt x12, x23, loop1
+
+BLT X12, X23, label will jump to label if X12 < X23. Note this is not the
+same ordering as is used for the SLT instructions.
+
+5. FMA instructions
+
+The Go assembler uses a different ordering for the RISC-V FMA operands to
+the ordering given in the [RISC-V ISA Manual]. The operands are rotated one
+place to the left, so that the destination operand comes last.
+
+Example:
+
+ FMADDS F1, F2, F3, F4 <=> fmadd.s f4, f1, f2, f3
+
+6. AMO instructions
+
+The ordering used for the AMO operations is rs2, rs1, rd, i.e., the operands
+as specified in the [RISC-V ISA Manual] are rotated one place to the left.
+
+Example:
+
+ AMOSWAPW X5, (X6), X7 <=> amoswap.w x7, x5, (x6)
+
+7. Vector instructions
+
+The VSETVLI instruction uses the same symbolic names as the [RISC-V ISA Manual]
+to represent the components of vtype, with the exception
+that they are written in upper case. The ordering of the operands in the Go
+assembler differs from the [RISC-V ISA Manual] in that the operands are
+rotated one place to the left so that the destination register, the register
+that holds the new vl, is the last operand.
+
+Example:
+
+ VSETVLI X10, E8, M1, TU, MU, X12 <=> vsetvli x12, x10, e8, m1, tu, mu
+
+Vector load and store instructions follow the pattern set by scalar loads and
+stores, i.e., the source is always the first operand and the destination the
+last. However, the ordering of the operands of these instructions is
+complicated by the optional mask register and, in some cases, the use of an
+additional stride or index register. In the Go assembler the index and stride
+registers appear as the second operand in indexed or strided loads and stores,
+while the mask register, if present, is always the penultimate operand.
+
+Examples:
+
+ VLE8V (X10), V3 <=> vle8.v v3, (x10)
+ VSE8V V3, (X10) <=> vse8.v v3, (x10)
+ VLE8V (X10), V0, V3 <=> vle8.v v3, (x10), v0.t
+ VSE8V V3, V0, (X10) <=> vse8.v v3, (x10), v0.t
+ VLSE8V (X10), X11, V3 <=> vlse8.v v3, (x10), x11
+ VSSE8V V3, X11, (X10) <=> vsse8.v v3, (x10), x11
+ VLSE8V (X10), X11, V0, V3 <=> vlse8.v v3, (x10), x11, v0.t
+ VSSE8V V3, X11, V0, (X10) <=> vsse8.v v3, (x10), x11, v0.t
+ VLUXEI8V (X10), V2, V3 <=> vluxei8.v v3, (x10), v2
+ VSUXEI8V V3, V2, (X10) <=> vsuxei8.v v3, (x10), v2
+ VLUXEI8V (X10), V2, V0, V3 <=> vluxei8.v v3, (x10), v2, v0.t
+ VSUXEI8V V3, V2, V0, (X10) <=> vsuxei8.v v3, (x10), v2, v0.t
+ VL1RE8V (X10), V3 <=> vl1re8.v v3, (x10)
+ VS1RV V3, (X11) <=> vs1r.v v3, (x11)
+
+The ordering of operands for two and three argument vector arithmetic instructions is
+reversed in the Go assembler.
+
+Examples:
+
+ VMVVV V2, V3 <=> vmv.v.v v3, v2
+ VADDVV V1, V2, V3 <=> vadd.vv v3, v2, v1
+ VADDVX X10, V2, V3 <=> vadd.vx v3, v2, x10
+ VMADCVI $15, V2, V3 <=> vmadc.vi v3, v2, 15
+
+The mask register, when specified, is always the penultimate operand in a vector
+arithmetic instruction, appearing before the destination register.
+
+Examples:
+
+ VANDVV V1, V2, V0, V3 <=> vand.vv v3, v2, v1, v0.t
+
+# Ternary instructions
+
+The Go assembler allows the second operand to be omitted from most ternary
+instructions if it matches the third (destination) operand.
+
+Examples:
+
+ ADD X10, X12, X12 <=> ADD X10, X12
+ ANDI $3, X12, X12 <=> ANDI $3, X12
+
+The use of this abbreviated syntax is encouraged.
+
+# Ordering of atomic instructions
+
+It is not possible to specify the ordering bits in the FENCE, LR, SC or AMO
+instructions. The FENCE instruction is always emitted as a full fence, the
+acquire and release bits are always set for the AMO instructions, the acquire
+bit is always set for the LR instructions while the release bit is set for
+the SC instructions.
+
+# Immediate operands
+
+In many cases, where an R-Type instruction has a corresponding I-Type
+instruction, the R-Type mnemonic can be used in place of the I-Type mnemonic.
+The assembler assumes that the immediate form of the instruction was intended
+when the first operand is given as an immediate value rather than a register.
+
+Example:
+
+ AND $3, X12, X13 <=> ANDI $3, X12, X13
+
+# Integer constant materialization
+
+The MOV instruction can be used to set a register to the value of any 64 bit
+constant literal. The way this is achieved by the assembler varies depending
+on the value of the constant. Where possible the assembler will synthesize the
+constant using one or more RISC-V arithmetic instructions. If it is unable
+to easily materialize the constant it will load the 64 bit literal from memory.
+
+A 32 bit constant literal can be specified as an argument to ADDI, ANDI, ORI and
+XORI. If the specified literal does not fit into 12 bits the assembler will
+generate extra instructions to synthesize it.
+
+Integer constants provided as operands to all other instructions must fit into
+the number of bits allowed by the instructions' encodings for immediate values.
+Otherwise, an error will be generated.
+
+# Floating point constant materialization
+
+The MOVF and MOVD instructions can be used to set a register to the value
+of any 32 bit or 64 bit floating point constant literal, respectively. Unless
+the constant literal is 0.0, MOVF and MOVD will be encoded as FLW and FLD
+instructions that load the constant from a location within the program's
+binary.
+
+[RISC-V ISA Manual]: https://github.com/riscv/riscv-isa-manual
+[rva20u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#51-rva20u64-profile
+[rva22u64]: https://github.com/riscv/riscv-profiles/blob/main/src/profiles.adoc#rva22u64-profile
+[rva23u64]: https://github.com/riscv/riscv-profiles/blob/main/src/rva23-profile.adoc#rva23u64-profile
+*/
+package riscv
--
cgit v1.3
From 710abf74da2a017423e35e416ab3fd05e6053bf3 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Thu, 16 Oct 2025 00:58:20 +0000
Subject: internal/runtime/cgobench: add Go function call benchmark for
comparison
Change-Id: I0ada7fa02eb5f18a78da17bdcfc63333abbd8450
Reviewed-on: https://go-review.googlesource.com/c/go/+/713284
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Knyszek
---
src/internal/runtime/cgobench/bench_test.go | 22 ++++++++++++++++++----
src/internal/runtime/cgobench/funcs.go | 8 ++++++--
2 files changed, 24 insertions(+), 6 deletions(-)
(limited to 'src')
diff --git a/src/internal/runtime/cgobench/bench_test.go b/src/internal/runtime/cgobench/bench_test.go
index 3b8f9a8ca3..0348ee0f41 100644
--- a/src/internal/runtime/cgobench/bench_test.go
+++ b/src/internal/runtime/cgobench/bench_test.go
@@ -11,13 +11,13 @@ import (
"testing"
)
-func BenchmarkCgoCall(b *testing.B) {
+func BenchmarkCall(b *testing.B) {
for b.Loop() {
cgobench.Empty()
}
}
-func BenchmarkCgoCallParallel(b *testing.B) {
+func BenchmarkCallParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
cgobench.Empty()
@@ -25,16 +25,30 @@ func BenchmarkCgoCallParallel(b *testing.B) {
})
}
+func BenchmarkCgoCall(b *testing.B) {
+ for b.Loop() {
+ cgobench.EmptyC()
+ }
+}
+
+func BenchmarkCgoCallParallel(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ cgobench.EmptyC()
+ }
+ })
+}
+
func BenchmarkCgoCallWithCallback(b *testing.B) {
for b.Loop() {
- cgobench.Callback()
+ cgobench.CallbackC()
}
}
func BenchmarkCgoCallParallelWithCallback(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
- cgobench.Callback()
+ cgobench.CallbackC()
}
})
}
diff --git a/src/internal/runtime/cgobench/funcs.go b/src/internal/runtime/cgobench/funcs.go
index 91efa51278..b60f6f58fd 100644
--- a/src/internal/runtime/cgobench/funcs.go
+++ b/src/internal/runtime/cgobench/funcs.go
@@ -19,14 +19,18 @@ static void callback() {
*/
import "C"
-func Empty() {
+func EmptyC() {
C.empty()
}
-func Callback() {
+func CallbackC() {
C.callback()
}
//export go_empty_callback
func go_empty_callback() {
}
+
+//go:noinline
+func Empty() {
+}
--
cgit v1.3
From 7a8d0b5d53dc7be331016b6903707aa256e22c2a Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Thu, 30 Oct 2025 17:24:47 +0000
Subject: runtime: add debug mode to extend _Grunning-without-P windows
This was suggested in CL 646198, and I tested with it, I forgot to push
it so it wasn't merged. I think it might be worth keeping.
Change-Id: Ibf97d969fe7d0eeb365f5f7b1fbeadea3a1076ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/716580
Reviewed-by: Cherry Mui
Auto-Submit: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/proc.go | 11 +++++++++++
1 file changed, 11 insertions(+)
(limited to 'src')
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 44b64913a5..44dbb2fd44 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -4658,6 +4658,11 @@ func reentersyscall(pc, sp, bp uintptr) {
gp.m.locks--
}
+// debugExtendGrunningNoP is a debug mode that extends the windows in which
+// we're _Grunning without a P in order to try to shake out bugs with code
+// assuming this state is impossible.
+const debugExtendGrunningNoP = false
+
// Standard syscall entry used by the go syscall library and normal cgo calls.
//
// This is exported via linkname to assembly in the syscall package and x/sys.
@@ -4770,6 +4775,9 @@ func entersyscallblock() {
// <--
// Caution: we're in a small window where we are in _Grunning without a P.
// -->
+ if debugExtendGrunningNoP {
+ usleep(10)
+ }
casgstatus(gp, _Grunning, _Gsyscall)
if gp.syscallsp < gp.stack.lo || gp.stack.hi < gp.syscallsp {
systemstack(func() {
@@ -4852,6 +4860,9 @@ func exitsyscall() {
// Caution: we're in a window where we may be in _Grunning without a P.
// Either we will grab a P or call exitsyscall0, where we'll switch to
// _Grunnable.
+ if debugExtendGrunningNoP {
+ usleep(10)
+ }
// Grab and clear our old P.
oldp := gp.m.oldp.ptr()
--
cgit v1.3
From 80c91eedbbf3c041e9b0c03c28fae2c73dbb7df4 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Fri, 7 Nov 2025 18:22:29 +0000
Subject: runtime: ensure weak handles end up in their own allocation
Currently weak handles are atomic.Uintptr values that may end up in
a tiny block which can cause all sorts of surprising leaks. See #76007
for one example.
This change pads out the underlying allocation of the atomic.Uintptr to
16 bytes to ensure we bypass the tiny allocator, and it gets its own
block. This wastes 8 bytes per weak handle. We could potentially do
better by using the 8 byte noscan size class, but this can be a
follow-up change.
For #76007.
Change-Id: I3ab74dda9bf312ea0007f167093052de28134944
Reviewed-on: https://go-review.googlesource.com/c/go/+/719960
Reviewed-by: Cherry Mui
Auto-Submit: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/mheap.go | 10 +++++++++-
src/weak/pointer_test.go | 40 ++++++++++++++++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 3 deletions(-)
(limited to 'src')
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index 711c7790eb..3334099092 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -2534,7 +2534,15 @@ func getOrAddWeakHandle(p unsafe.Pointer) *atomic.Uintptr {
s := (*specialWeakHandle)(mheap_.specialWeakHandleAlloc.alloc())
unlock(&mheap_.speciallock)
- handle := new(atomic.Uintptr)
+ // N.B. Pad the weak handle to ensure it doesn't share a tiny
+ // block with any other allocations. This can lead to leaks, such
+ // as in go.dev/issue/76007. As an alternative, we could consider
+ // using the currently-unused 8-byte noscan size class.
+ type weakHandleBox struct {
+ h atomic.Uintptr
+ _ [maxTinySize - unsafe.Sizeof(atomic.Uintptr{})]byte
+ }
+ handle := &(new(weakHandleBox).h)
s.special.kind = _KindSpecialWeakHandle
s.handle = handle
handle.Store(uintptr(p))
diff --git a/src/weak/pointer_test.go b/src/weak/pointer_test.go
index da464a8d01..5e8b9bef58 100644
--- a/src/weak/pointer_test.go
+++ b/src/weak/pointer_test.go
@@ -16,8 +16,10 @@ import (
)
type T struct {
- // N.B. This must contain a pointer, otherwise the weak handle might get placed
- // in a tiny block making the tests in this package flaky.
+ // N.B. T is what it is to avoid having test values get tiny-allocated
+ // in the same block as the weak handle, but since the fix to
+ // go.dev/issue/76007, this should no longer be possible.
+ // TODO(mknyszek): Consider using tiny-allocated values for all the tests.
t *T
a int
b int
@@ -327,3 +329,37 @@ func TestImmortalPointer(t *testing.T) {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
}
+
+func TestPointerTiny(t *testing.T) {
+ runtime.GC() // Clear tiny-alloc caches.
+
+ const N = 1000
+ wps := make([]weak.Pointer[int], N)
+ for i := range N {
+ // N.B. *x is just an int, so the value is very likely
+ // tiny-allocated alongside the weak handle, assuming bug
+ // from go.dev/issue/76007 exists.
+ x := new(int)
+ *x = i
+ wps[i] = weak.Make(x)
+ }
+
+ // Get the cleanups to start running.
+ runtime.GC()
+
+ // Expect at least 3/4ths of the weak pointers to have gone nil.
+ //
+ // Note that we provide some leeway since it's possible our allocation
+ // gets grouped with some other long-lived tiny allocation, but this
+ // shouldn't be the case for the vast majority of allocations.
+ n := 0
+ for _, wp := range wps {
+ if wp.Value() == nil {
+ n++
+ }
+ }
+ const want = 3 * N / 4
+ if n < want {
+ t.Fatalf("not enough weak pointers are nil: expected at least %v, got %v", want, n)
+ }
+}
--
cgit v1.3
From 9fd2e4443955127ed360338cb19a5aeba6be1a8c Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Thu, 13 Nov 2025 17:17:35 +0000
Subject: runtime: add AddCleanup benchmark
Change-Id: Ia463a9b3b5980670bcf9297b4bddb60980ebfde5
Reviewed-on: https://go-review.googlesource.com/c/go/+/720320
Auto-Submit: Michael Knyszek
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Carlos Amedee
---
src/runtime/mcleanup_test.go | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
(limited to 'src')
diff --git a/src/runtime/mcleanup_test.go b/src/runtime/mcleanup_test.go
index 22b9eccd20..341d30afa7 100644
--- a/src/runtime/mcleanup_test.go
+++ b/src/runtime/mcleanup_test.go
@@ -336,3 +336,31 @@ func TestCleanupLost(t *testing.T) {
t.Errorf("expected %d cleanups to be executed, got %d", got, want)
}
}
+
+// BenchmarkAddCleanupAndStop benchmarks adding and removing a cleanup
+// from the same allocation.
+//
+// At face value, this benchmark is unrealistic, since no program would
+// do this in practice. However, adding cleanups to new allocations in a
+// loop is also unrealistic. It adds additional unused allocations,
+// exercises uncommon performance pitfalls in AddCleanup (traversing the
+// specials list, which should just be its own benchmark), and executing
+// cleanups at a frequency that is unlikely to appear in real programs.
+//
+// This benchmark is still useful however, since we can get a low-noise
+// measurement of the cost of AddCleanup and Stop all in one without the
+// above pitfalls: we can measure the pure overhead. We can then separate
+// out the cost of each in CPU profiles if we so choose (they're not so
+// inexpensive as to make this infeasible).
+func BenchmarkAddCleanupAndStop(b *testing.B) {
+ b.ReportAllocs()
+
+ type T struct {
+ v int
+ p unsafe.Pointer
+ }
+ x := new(T)
+ for b.Loop() {
+ runtime.AddCleanup(x, func(int) {}, 14).Stop()
+ }
+}
--
cgit v1.3
From 1bb1f2bf0c07bbc583063a21b324407f7041e316 Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Thu, 13 Nov 2025 18:29:23 +0000
Subject: runtime: put AddCleanup cleanup arguments in their own allocation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently, AddCleanup just creates a simple closure that calls
`cleanup(arg)` as the actual cleanup function tracked internally.
However, the argument ends up getting its own allocation. If it's tiny,
then it can also end up sharing a tiny allocation slot with the object
we're adding the cleanup to. Since the closure is a GC root, we can end
up with cleanups that never fire.
This change refactors the AddCleanup machinery to make the storage for
the argument separate and explicit. With that in place, it explicitly
allocates 16 bytes of storage for tiny arguments to side-step the tiny
allocator.
One would think this would cause an increase in memory use and more
bytes allocated, but that's actually wrong! It turns out that the
current "simple closure" actually creates _two_ closures. By making the
argument passing explicit, we eliminate one layer of closures, so this
actually results in a slightly faster AddCleanup overall and 16 bytes
less memory allocated.
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ before.bench │ after.bench │
│ sec/op │ sec/op vs base │
AddCleanupAndStop-64 124.5n ± 2% 103.7n ± 2% -16.71% (p=0.002 n=6)
│ before.bench │ after.bench │
│ B/op │ B/op vs base │
AddCleanupAndStop-64 48.00 ± 0% 32.00 ± 0% -33.33% (p=0.002 n=6)
│ before.bench │ after.bench │
│ allocs/op │ allocs/op vs base │
AddCleanupAndStop-64 3.000 ± 0% 2.000 ± 0% -33.33% (p=0.002 n=6)
This change does, however, does add 16 bytes of overhead to the cleanup
special itself, and makes each cleanup block entry 24 bytes instead of 8
bytes. This means the overall memory overhead delta with this change is
neutral, and we just have a faster cleanup. (Cleanup block entries are
transient, so I suspect any increase in memory overhead there is
negligible.)
Together with CL 719960, fixes #76007.
Change-Id: I81bf3e44339e71c016c30d80bb4ee151c8263d5c
Reviewed-on: https://go-review.googlesource.com/c/go/+/720321
Reviewed-by: Cherry Mui
Auto-Submit: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/mcleanup.go | 86 +++++++++++++++++++++++++++++++++++--------------
src/runtime/mgcmark.go | 6 ++--
src/runtime/mheap.go | 23 +++++++------
3 files changed, 78 insertions(+), 37 deletions(-)
(limited to 'src')
diff --git a/src/runtime/mcleanup.go b/src/runtime/mcleanup.go
index 383217aa05..fc71af9f3f 100644
--- a/src/runtime/mcleanup.go
+++ b/src/runtime/mcleanup.go
@@ -72,8 +72,9 @@ import (
// pass the object to the [KeepAlive] function after the last point
// where the object must remain reachable.
func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
- // Explicitly force ptr to escape to the heap.
+ // Explicitly force ptr and cleanup to escape to the heap.
ptr = abi.Escape(ptr)
+ cleanup = abi.Escape(cleanup)
// The pointer to the object must be valid.
if ptr == nil {
@@ -82,7 +83,8 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
usptr := uintptr(unsafe.Pointer(ptr))
// Check that arg is not equal to ptr.
- if kind := abi.TypeOf(arg).Kind(); kind == abi.Pointer || kind == abi.UnsafePointer {
+ argType := abi.TypeOf(arg)
+ if kind := argType.Kind(); kind == abi.Pointer || kind == abi.UnsafePointer {
if unsafe.Pointer(ptr) == *((*unsafe.Pointer)(unsafe.Pointer(&arg))) {
panic("runtime.AddCleanup: ptr is equal to arg, cleanup will never run")
}
@@ -98,12 +100,23 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
return Cleanup{}
}
- fn := func() {
- cleanup(arg)
+ // Create new storage for the argument.
+ var argv *S
+ if size := unsafe.Sizeof(arg); size < maxTinySize && argType.PtrBytes == 0 {
+ // Side-step the tiny allocator to avoid liveness issues, since this box
+ // will be treated like a root by the GC. We model the box as an array of
+ // uintptrs to guarantee maximum allocator alignment.
+ //
+ // TODO(mknyszek): Consider just making space in cleanupFn for this. The
+ // unfortunate part of this is it would grow specialCleanup by 16 bytes, so
+ // while there wouldn't be an allocation, *every* cleanup would take the
+ // memory overhead hit.
+ box := new([maxTinySize / goarch.PtrSize]uintptr)
+ argv = (*S)(unsafe.Pointer(box))
+ } else {
+ argv = new(S)
}
- // Closure must escape.
- fv := *(**funcval)(unsafe.Pointer(&fn))
- fv = abi.Escape(fv)
+ *argv = arg
// Find the containing object.
base, _, _ := findObject(usptr, 0, 0)
@@ -120,7 +133,16 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
gcCleanups.createGs()
}
- id := addCleanup(unsafe.Pointer(ptr), fv)
+ id := addCleanup(unsafe.Pointer(ptr), cleanupFn{
+ // Instantiate a caller function to call the cleanup, that is cleanup(*argv).
+ //
+ // TODO(mknyszek): This allocates because the generic dictionary argument
+ // gets closed over, but callCleanup doesn't even use the dictionary argument,
+ // so theoretically that could be removed, eliminating an allocation.
+ call: callCleanup[S],
+ fn: *(**funcval)(unsafe.Pointer(&cleanup)),
+ arg: unsafe.Pointer(argv),
+ })
if debug.checkfinalizers != 0 {
cleanupFn := *(**funcval)(unsafe.Pointer(&cleanup))
setCleanupContext(unsafe.Pointer(ptr), abi.TypeFor[T](), sys.GetCallerPC(), cleanupFn.fn, id)
@@ -131,6 +153,16 @@ func AddCleanup[T, S any](ptr *T, cleanup func(S), arg S) Cleanup {
}
}
+// callCleanup is a helper for calling cleanups in a polymorphic way.
+//
+// In practice, all it does is call fn(*arg). arg must be a *T.
+//
+//go:noinline
+func callCleanup[T any](fn *funcval, arg unsafe.Pointer) {
+ cleanup := *(*func(T))(unsafe.Pointer(&fn))
+ cleanup(*(*T)(arg))
+}
+
// Cleanup is a handle to a cleanup call for a specific object.
type Cleanup struct {
// id is the unique identifier for the cleanup within the arena.
@@ -216,7 +248,17 @@ const cleanupBlockSize = 512
// that the cleanup queue does not grow during marking (but it can shrink).
type cleanupBlock struct {
cleanupBlockHeader
- cleanups [(cleanupBlockSize - unsafe.Sizeof(cleanupBlockHeader{})) / goarch.PtrSize]*funcval
+ cleanups [(cleanupBlockSize - unsafe.Sizeof(cleanupBlockHeader{})) / unsafe.Sizeof(cleanupFn{})]cleanupFn
+}
+
+var cleanupFnPtrMask = [...]uint8{0b111}
+
+// cleanupFn represents a cleanup function with it's argument, yet to be called.
+type cleanupFn struct {
+ // call is an adapter function that understands how to safely call fn(*arg).
+ call func(*funcval, unsafe.Pointer)
+ fn *funcval // cleanup function passed to AddCleanup.
+ arg unsafe.Pointer // pointer to argument to pass to cleanup function.
}
var cleanupBlockPtrMask [cleanupBlockSize / goarch.PtrSize / 8]byte
@@ -245,8 +287,8 @@ type cleanupBlockHeader struct {
//
// Must only be called if the GC is in the sweep phase (gcphase == _GCoff),
// because it does not synchronize with the garbage collector.
-func (b *cleanupBlock) enqueue(fn *funcval) bool {
- b.cleanups[b.n] = fn
+func (b *cleanupBlock) enqueue(c cleanupFn) bool {
+ b.cleanups[b.n] = c
b.n++
return b.full()
}
@@ -375,7 +417,7 @@ func (q *cleanupQueue) tryTakeWork() bool {
// enqueue queues a single cleanup for execution.
//
// Called by the sweeper, and only the sweeper.
-func (q *cleanupQueue) enqueue(fn *funcval) {
+func (q *cleanupQueue) enqueue(c cleanupFn) {
mp := acquirem()
pp := mp.p.ptr()
b := pp.cleanups
@@ -396,7 +438,7 @@ func (q *cleanupQueue) enqueue(fn *funcval) {
}
pp.cleanups = b
}
- if full := b.enqueue(fn); full {
+ if full := b.enqueue(c); full {
q.full.push(&b.lfnode)
pp.cleanups = nil
q.addWork(1)
@@ -641,7 +683,8 @@ func runCleanups() {
gcCleanups.beginRunningCleanups()
for i := 0; i < int(b.n); i++ {
- fn := b.cleanups[i]
+ c := b.cleanups[i]
+ b.cleanups[i] = cleanupFn{}
var racectx uintptr
if raceenabled {
@@ -650,20 +693,15 @@ func runCleanups() {
// the same goroutine.
//
// Synchronize on fn. This would fail to find races on the
- // closed-over values in fn (suppose fn is passed to multiple
- // AddCleanup calls) if fn was not unique, but it is. Update
- // the synchronization on fn if you intend to optimize it
- // and store the cleanup function and cleanup argument on the
- // queue directly.
- racerelease(unsafe.Pointer(fn))
+ // closed-over values in fn (suppose arg is passed to multiple
+ // AddCleanup calls) if arg was not unique, but it is.
+ racerelease(unsafe.Pointer(c.arg))
racectx = raceEnterNewCtx()
- raceacquire(unsafe.Pointer(fn))
+ raceacquire(unsafe.Pointer(c.arg))
}
// Execute the next cleanup.
- cleanup := *(*func())(unsafe.Pointer(&fn))
- cleanup()
- b.cleanups[i] = nil
+ c.call(c.fn, c.arg)
if raceenabled {
// Restore the old context.
diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go
index dd76973c62..c9234c5084 100644
--- a/src/runtime/mgcmark.go
+++ b/src/runtime/mgcmark.go
@@ -204,7 +204,7 @@ func gcMarkRootCheck() {
})
}
-// ptrmask for an allocation containing a single pointer.
+// oneptrmask for an allocation containing a single pointer.
var oneptrmask = [...]uint8{1}
// markroot scans the i'th root.
@@ -251,7 +251,7 @@ func markroot(gcw *gcWork, i uint32, flushBgCredit bool) int64 {
// N.B. This only needs to synchronize with cleanup execution, which only resets these blocks.
// All cleanup queueing happens during sweep.
n := uintptr(atomic.Load(&cb.n))
- scanblock(uintptr(unsafe.Pointer(&cb.cleanups[0])), n*goarch.PtrSize, &cleanupBlockPtrMask[0], gcw, nil)
+ scanblock(uintptr(unsafe.Pointer(&cb.cleanups[0])), n*unsafe.Sizeof(cleanupFn{}), &cleanupBlockPtrMask[0], gcw, nil)
}
case work.baseSpans <= i && i < work.baseStacks:
@@ -489,7 +489,7 @@ func gcScanFinalizer(spf *specialfinalizer, s *mspan, gcw *gcWork) {
// gcScanCleanup scans the relevant parts of a cleanup special as a root.
func gcScanCleanup(spc *specialCleanup, gcw *gcWork) {
// The special itself is a root.
- scanblock(uintptr(unsafe.Pointer(&spc.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
+ scanblock(uintptr(unsafe.Pointer(&spc.cleanup)), unsafe.Sizeof(cleanupFn{}), &cleanupFnPtrMask[0], gcw, nil)
}
// gcAssistAlloc performs GC work to make gp's assist debt positive.
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index 3334099092..08a0057be7 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -2161,7 +2161,7 @@ func removefinalizer(p unsafe.Pointer) {
type specialCleanup struct {
_ sys.NotInHeap
special special
- fn *funcval
+ cleanup cleanupFn
// Globally unique ID for the cleanup, obtained from mheap_.cleanupID.
id uint64
}
@@ -2170,14 +2170,18 @@ type specialCleanup struct {
// cleanups are allowed on an object, and even the same pointer.
// A cleanup id is returned which can be used to uniquely identify
// the cleanup.
-func addCleanup(p unsafe.Pointer, f *funcval) uint64 {
+func addCleanup(p unsafe.Pointer, c cleanupFn) uint64 {
+ // TODO(mknyszek): Consider pooling specialCleanups on the P
+ // so we don't have to take the lock every time. Just locking
+ // is a considerable part of the cost of AddCleanup. This
+ // would also require reserving some cleanup IDs on the P.
lock(&mheap_.speciallock)
s := (*specialCleanup)(mheap_.specialCleanupAlloc.alloc())
mheap_.cleanupID++ // Increment first. ID 0 is reserved.
id := mheap_.cleanupID
unlock(&mheap_.speciallock)
s.special.kind = _KindSpecialCleanup
- s.fn = f
+ s.cleanup = c
s.id = id
mp := acquirem()
@@ -2187,17 +2191,16 @@ func addCleanup(p unsafe.Pointer, f *funcval) uint64 {
// situation where it's possible that markrootSpans
// has already run but mark termination hasn't yet.
if gcphase != _GCoff {
- gcw := &mp.p.ptr().gcw
// Mark the cleanup itself, since the
// special isn't part of the GC'd heap.
- scanblock(uintptr(unsafe.Pointer(&s.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
+ gcScanCleanup(s, &mp.p.ptr().gcw)
}
releasem(mp)
- // Keep f alive. There's a window in this function where it's
- // only reachable via the special while the special hasn't been
- // added to the specials list yet. This is similar to a bug
+ // Keep c and its referents alive. There's a window in this function
+ // where it's only reachable via the special while the special hasn't
+ // been added to the specials list yet. This is similar to a bug
// discovered for weak handles, see #70455.
- KeepAlive(f)
+ KeepAlive(c)
return id
}
@@ -2800,7 +2803,7 @@ func freeSpecial(s *special, p unsafe.Pointer, size uintptr) {
// Cleanups, unlike finalizers, do not resurrect the objects
// they're attached to, so we only need to pass the cleanup
// function, not the object.
- gcCleanups.enqueue(sc.fn)
+ gcCleanups.enqueue(sc.cleanup)
lock(&mheap_.speciallock)
mheap_.specialCleanupAlloc.free(unsafe.Pointer(sc))
unlock(&mheap_.speciallock)
--
cgit v1.3
From 03ed43988ff7f7671094d8c455532de7f2242e70 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Sat, 14 Jun 2025 20:10:50 -0700
Subject: cmd/compile: allow multi-field structs to be stored directly in
interfaces
If the struct is a bunch of 0-sized fields and one pointer field.
Merged revert-of-revert for 4 CLs.
original revert
681937 695016
693415 694996
693615 695015
694195 694995
Fixes #74092
Update #74888
Update #74908
Update #74935
(updated issues are bugs in the last attempt at this)
Change-Id: I32246d49b8bac3bb080972dc06ab432a5480d560
Reviewed-on: https://go-review.googlesource.com/c/go/+/714421
Auto-Submit: Keith Randall
Reviewed-by: David Chase
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/dec.rules | 9 +++--
src/cmd/compile/internal/ssa/_gen/generic.rules | 6 ++-
src/cmd/compile/internal/ssa/expand_calls.go | 9 ++++-
src/cmd/compile/internal/ssa/rewrite.go | 14 +++++++
src/cmd/compile/internal/ssa/rewritedec.go | 52 +++++++++++++++++++++----
src/cmd/compile/internal/ssa/rewritegeneric.go | 45 ++++++++++++++++-----
src/cmd/compile/internal/types/type.go | 21 +---------
src/internal/abi/type.go | 4 +-
src/reflect/type.go | 6 +--
src/reflect/value.go | 11 ++++++
test/fixedbugs/issue74888.go | 20 ++++++++++
test/fixedbugs/issue74908.go | 24 ++++++++++++
test/fixedbugs/issue74935.go | 19 +++++++++
13 files changed, 191 insertions(+), 49 deletions(-)
create mode 100644 test/fixedbugs/issue74888.go
create mode 100644 test/fixedbugs/issue74908.go
create mode 100644 test/fixedbugs/issue74935.go
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/dec.rules b/src/cmd/compile/internal/ssa/_gen/dec.rules
index 9f6dc36975..fce0026211 100644
--- a/src/cmd/compile/internal/ssa/_gen/dec.rules
+++ b/src/cmd/compile/internal/ssa/_gen/dec.rules
@@ -97,8 +97,10 @@
// Helpers for expand calls
// Some of these are copied from generic.rules
-(IMake _typ (StructMake val)) => (IMake _typ val)
-(StructSelect [0] (IData x)) => (IData x)
+(IMake _typ (StructMake ___)) => imakeOfStructMake(v)
+(StructSelect (IData x)) && v.Type.Size() > 0 => (IData x)
+(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake)
+(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0)
(StructSelect [i] x:(StructMake ___)) => x.Args[i]
@@ -109,7 +111,7 @@
// More annoying case: (ArraySelect[0] (StructSelect[0] isAPtr))
// There, result of the StructSelect is an Array (not a pointer) and
// the pre-rewrite input to the ArraySelect is a struct, not a pointer.
-(StructSelect [0] x) && x.Type.IsPtrShaped() => x
+(StructSelect x) && x.Type.IsPtrShaped() => x
(ArraySelect [0] x) && x.Type.IsPtrShaped() => x
// These, too. Bits is bits.
@@ -119,6 +121,7 @@
(Store _ (StructMake ___) _) => rewriteStructStore(v)
+(IMake _typ (ArrayMake1 val)) => (IMake _typ val)
(ArraySelect (ArrayMake1 x)) => x
(ArraySelect [0] (IData x)) => (IData x)
diff --git a/src/cmd/compile/internal/ssa/_gen/generic.rules b/src/cmd/compile/internal/ssa/_gen/generic.rules
index e09cd31c31..372e89863d 100644
--- a/src/cmd/compile/internal/ssa/_gen/generic.rules
+++ b/src/cmd/compile/internal/ssa/_gen/generic.rules
@@ -944,8 +944,10 @@
@x.Block (Load (OffPtr [t.FieldOff(int(i))] ptr) mem)
// Putting struct{*byte} and similar into direct interfaces.
-(IMake _typ (StructMake val)) => (IMake _typ val)
-(StructSelect [0] (IData x)) => (IData x)
+(IMake _typ (StructMake ___)) => imakeOfStructMake(v)
+(StructSelect (IData x)) && v.Type.Size() > 0 => (IData x)
+(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsStruct() => (StructMake)
+(StructSelect (IData x)) && v.Type.Size() == 0 && v.Type.IsArray() => (ArrayMake0)
// un-SSAable values use mem->mem copies
(Store {t} dst (Load src mem) mem) && !CanSSA(t) =>
diff --git a/src/cmd/compile/internal/ssa/expand_calls.go b/src/cmd/compile/internal/ssa/expand_calls.go
index 8a5b364c2f..6afce759b2 100644
--- a/src/cmd/compile/internal/ssa/expand_calls.go
+++ b/src/cmd/compile/internal/ssa/expand_calls.go
@@ -423,7 +423,14 @@ func (x *expandState) decomposeAsNecessary(pos src.XPos, b *Block, a, m0 *Value,
if a.Op == OpIMake {
data := a.Args[1]
for data.Op == OpStructMake || data.Op == OpArrayMake1 {
- data = data.Args[0]
+ // A struct make might have a few zero-sized fields.
+ // Use the pointer-y one we know is there.
+ for _, a := range data.Args {
+ if a.Type.Size() > 0 {
+ data = a
+ break
+ }
+ }
}
return x.decomposeAsNecessary(pos, b, data, mem, rc.next(data.Type))
}
diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go
index 07308973b1..af2568ae89 100644
--- a/src/cmd/compile/internal/ssa/rewrite.go
+++ b/src/cmd/compile/internal/ssa/rewrite.go
@@ -2772,3 +2772,17 @@ func panicBoundsCCToAux(p PanicBoundsCC) Aux {
func isDictArgSym(sym Sym) bool {
return sym.(*ir.Name).Sym().Name == typecheck.LocalDictName
}
+
+// When v is (IMake typ (StructMake ...)), convert to
+// (IMake typ arg) where arg is the pointer-y argument to
+// the StructMake (there must be exactly one).
+func imakeOfStructMake(v *Value) *Value {
+ var arg *Value
+ for _, a := range v.Args[1].Args {
+ if a.Type.Size() > 0 {
+ arg = a
+ break
+ }
+ }
+ return v.Block.NewValue2(v.Pos, OpIMake, v.Type, v.Args[0], arg)
+}
diff --git a/src/cmd/compile/internal/ssa/rewritedec.go b/src/cmd/compile/internal/ssa/rewritedec.go
index 16d0269210..c45034ead0 100644
--- a/src/cmd/compile/internal/ssa/rewritedec.go
+++ b/src/cmd/compile/internal/ssa/rewritedec.go
@@ -279,11 +279,20 @@ func rewriteValuedec_OpIData(v *Value) bool {
func rewriteValuedec_OpIMake(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
- // match: (IMake _typ (StructMake val))
+ // match: (IMake _typ (StructMake ___))
+ // result: imakeOfStructMake(v)
+ for {
+ if v_1.Op != OpStructMake {
+ break
+ }
+ v.copyOf(imakeOfStructMake(v))
+ return true
+ }
+ // match: (IMake _typ (ArrayMake1 val))
// result: (IMake _typ val)
for {
_typ := v_0
- if v_1.Op != OpStructMake || len(v_1.Args) != 1 {
+ if v_1.Op != OpArrayMake1 {
break
}
val := v_1.Args[0]
@@ -839,17 +848,47 @@ func rewriteValuedec_OpStructMake(v *Value) bool {
func rewriteValuedec_OpStructSelect(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
- // match: (StructSelect [0] (IData x))
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() > 0
// result: (IData x)
for {
- if auxIntToInt64(v.AuxInt) != 0 || v_0.Op != OpIData {
+ if v_0.Op != OpIData {
break
}
x := v_0.Args[0]
+ if !(v.Type.Size() > 0) {
+ break
+ }
v.reset(OpIData)
v.AddArg(x)
return true
}
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() == 0 && v.Type.IsStruct()
+ // result: (StructMake)
+ for {
+ if v_0.Op != OpIData {
+ break
+ }
+ if !(v.Type.Size() == 0 && v.Type.IsStruct()) {
+ break
+ }
+ v.reset(OpStructMake)
+ return true
+ }
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() == 0 && v.Type.IsArray()
+ // result: (ArrayMake0)
+ for {
+ if v_0.Op != OpIData {
+ break
+ }
+ if !(v.Type.Size() == 0 && v.Type.IsArray()) {
+ break
+ }
+ v.reset(OpArrayMake0)
+ return true
+ }
// match: (StructSelect [i] x:(StructMake ___))
// result: x.Args[i]
for {
@@ -861,13 +900,10 @@ func rewriteValuedec_OpStructSelect(v *Value) bool {
v.copyOf(x.Args[i])
return true
}
- // match: (StructSelect [0] x)
+ // match: (StructSelect x)
// cond: x.Type.IsPtrShaped()
// result: x
for {
- if auxIntToInt64(v.AuxInt) != 0 {
- break
- }
x := v_0
if !(x.Type.IsPtrShaped()) {
break
diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go
index 1621153b43..f0560671ac 100644
--- a/src/cmd/compile/internal/ssa/rewritegeneric.go
+++ b/src/cmd/compile/internal/ssa/rewritegeneric.go
@@ -8985,16 +8985,13 @@ func rewriteValuegeneric_OpFloor(v *Value) bool {
func rewriteValuegeneric_OpIMake(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
- // match: (IMake _typ (StructMake val))
- // result: (IMake _typ val)
+ // match: (IMake _typ (StructMake ___))
+ // result: imakeOfStructMake(v)
for {
- _typ := v_0
- if v_1.Op != OpStructMake || len(v_1.Args) != 1 {
+ if v_1.Op != OpStructMake {
break
}
- val := v_1.Args[0]
- v.reset(OpIMake)
- v.AddArg2(_typ, val)
+ v.copyOf(imakeOfStructMake(v))
return true
}
// match: (IMake _typ (ArrayMake1 val))
@@ -32109,17 +32106,47 @@ func rewriteValuegeneric_OpStructSelect(v *Value) bool {
v0.AddArg2(v1, mem)
return true
}
- // match: (StructSelect [0] (IData x))
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() > 0
// result: (IData x)
for {
- if auxIntToInt64(v.AuxInt) != 0 || v_0.Op != OpIData {
+ if v_0.Op != OpIData {
break
}
x := v_0.Args[0]
+ if !(v.Type.Size() > 0) {
+ break
+ }
v.reset(OpIData)
v.AddArg(x)
return true
}
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() == 0 && v.Type.IsStruct()
+ // result: (StructMake)
+ for {
+ if v_0.Op != OpIData {
+ break
+ }
+ if !(v.Type.Size() == 0 && v.Type.IsStruct()) {
+ break
+ }
+ v.reset(OpStructMake)
+ return true
+ }
+ // match: (StructSelect (IData x))
+ // cond: v.Type.Size() == 0 && v.Type.IsArray()
+ // result: (ArrayMake0)
+ for {
+ if v_0.Op != OpIData {
+ break
+ }
+ if !(v.Type.Size() == 0 && v.Type.IsArray()) {
+ break
+ }
+ v.reset(OpArrayMake0)
+ return true
+ }
return false
}
func rewriteValuegeneric_OpSub16(v *Value) bool {
diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go
index 8de589bae3..2f5de288c2 100644
--- a/src/cmd/compile/internal/types/type.go
+++ b/src/cmd/compile/internal/types/type.go
@@ -1822,26 +1822,7 @@ func IsReflexive(t *Type) bool {
// Can this type be stored directly in an interface word?
// Yes, if the representation is a single pointer.
func IsDirectIface(t *Type) bool {
- switch t.Kind() {
- case TPTR:
- // Pointers to notinheap types must be stored indirectly. See issue 42076.
- return !t.Elem().NotInHeap()
- case TCHAN,
- TMAP,
- TFUNC,
- TUNSAFEPTR:
- return true
-
- case TARRAY:
- // Array of 1 direct iface type can be direct.
- return t.NumElem() == 1 && IsDirectIface(t.Elem())
-
- case TSTRUCT:
- // Struct with 1 field of direct iface type can be direct.
- return t.NumFields() == 1 && IsDirectIface(t.Field(0).Type)
- }
-
- return false
+ return t.Size() == int64(PtrSize) && PtrDataSize(t) == int64(PtrSize)
}
// IsInterfaceMethod reports whether (field) m is
diff --git a/src/internal/abi/type.go b/src/internal/abi/type.go
index 7f44a9de56..243b787cfc 100644
--- a/src/internal/abi/type.go
+++ b/src/internal/abi/type.go
@@ -121,8 +121,8 @@ const (
TFlagGCMaskOnDemand TFlag = 1 << 4
// TFlagDirectIface means that a value of this type is stored directly
- // in the data field of an interface, instead of indirectly. Normally
- // this means the type is pointer-ish.
+ // in the data field of an interface, instead of indirectly.
+ // This flag is just a cached computation of Size_ == PtrBytes == goarch.PtrSize.
TFlagDirectIface TFlag = 1 << 5
// Leaving this breadcrumb behind for dlv. It should not be used, and no
diff --git a/src/reflect/type.go b/src/reflect/type.go
index 9b8726824e..914b5443f3 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -2528,8 +2528,7 @@ func StructOf(fields []StructField) Type {
}
switch {
- case len(fs) == 1 && fs[0].Typ.IsDirectIface():
- // structs of 1 direct iface type can be direct
+ case typ.Size_ == goarch.PtrSize && typ.PtrBytes == goarch.PtrSize:
typ.TFlag |= abi.TFlagDirectIface
default:
typ.TFlag &^= abi.TFlagDirectIface
@@ -2698,8 +2697,7 @@ func ArrayOf(length int, elem Type) Type {
}
switch {
- case length == 1 && typ.IsDirectIface():
- // array of 1 direct iface type can be direct
+ case array.Size_ == goarch.PtrSize && array.PtrBytes == goarch.PtrSize:
array.TFlag |= abi.TFlagDirectIface
default:
array.TFlag &^= abi.TFlagDirectIface
diff --git a/src/reflect/value.go b/src/reflect/value.go
index b5d5aa8bf2..a82d976c47 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -1279,6 +1279,17 @@ func (v Value) Field(i int) Value {
fl |= flagStickyRO
}
}
+ if fl&flagIndir == 0 && typ.Size() == 0 {
+ // Special case for picking a field out of a direct struct.
+ // A direct struct must have a pointer field and possibly a
+ // bunch of zero-sized fields. We must return the zero-sized
+ // fields indirectly, as only ptr-shaped things can be direct.
+ // See issue 74935.
+ // We use nil instead of v.ptr as it doesn't matter and
+ // we can avoid pinning a possibly now-unused object.
+ return Value{typ, nil, fl | flagIndir}
+ }
+
// Either flagIndir is set and v.ptr points at struct,
// or flagIndir is not set and v.ptr is the actual struct data.
// In the former case, we want v.ptr + offset.
diff --git a/test/fixedbugs/issue74888.go b/test/fixedbugs/issue74888.go
new file mode 100644
index 0000000000..a0083adb9c
--- /dev/null
+++ b/test/fixedbugs/issue74888.go
@@ -0,0 +1,20 @@
+// compile
+
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+type P struct {
+ q struct{}
+ p *int
+}
+
+func f(x any) {
+ h(x.(P))
+}
+
+//go:noinline
+func h(P) {
+}
diff --git a/test/fixedbugs/issue74908.go b/test/fixedbugs/issue74908.go
new file mode 100644
index 0000000000..cb2e951768
--- /dev/null
+++ b/test/fixedbugs/issue74908.go
@@ -0,0 +1,24 @@
+// compile
+
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+type Type struct {
+ any
+}
+
+type typeObject struct {
+ e struct{}
+ b *byte
+}
+
+func f(b *byte) Type {
+ return Type{
+ typeObject{
+ b: b,
+ },
+ }
+}
diff --git a/test/fixedbugs/issue74935.go b/test/fixedbugs/issue74935.go
new file mode 100644
index 0000000000..1f6f718b02
--- /dev/null
+++ b/test/fixedbugs/issue74935.go
@@ -0,0 +1,19 @@
+// run
+
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import "reflect"
+
+type W struct {
+ E struct{}
+ X *byte
+}
+
+func main() {
+ w := reflect.ValueOf(W{})
+ _ = w.Field(0).Interface()
+}
--
cgit v1.3
From cb0d9980f5721715ebb73dd2e580eaa11c2ddee2 Mon Sep 17 00:00:00 2001
From: "Nicholas S. Husin"
Date: Fri, 14 Nov 2025 16:11:23 -0500
Subject: net/http: do not discard body content when closing it within request
handlers
(*body).Close() internally tries to discard the content of a request
body up to 256 KB. We rely on this behavior to allow connection re-use,
by calling (*body).Close() when our request handler exits.
Unfortunately, this causes an unfortunate side-effect where we would
prematurely try to discard a body content when (*body).Close() is called
from within a request handler.
There should not be a good reason for (*body).Close() to do this when
called from within a request handler. As such, this CL modifies
(*body).Close() to not discard body contents when called from within a
request handler. Note that when a request handler exits, it will still
try to discard the body content for connection re-use.
For #75933
Change-Id: I71d2431a540579184066dd35d3da49d6c85c3daf
Reviewed-on: https://go-review.googlesource.com/c/go/+/720380
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Nicholas Husin
Reviewed-by: Damien Neil
---
src/net/http/serve_test.go | 188 ++++++++++++++++++++++++---------------------
src/net/http/server.go | 31 +++++---
src/net/http/transfer.go | 109 ++++++++++++++------------
3 files changed, 185 insertions(+), 143 deletions(-)
(limited to 'src')
diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go
index 4a16ba02af..af15593c35 100644
--- a/src/net/http/serve_test.go
+++ b/src/net/http/serve_test.go
@@ -2150,118 +2150,137 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) {
}
}
-type handlerBodyCloseTest struct {
+type bodyDiscardTest struct {
bodySize int
bodyChunked bool
reqConnClose bool
- wantEOFSearch bool // should Handler's Body.Close do Reads, looking for EOF?
- wantNextReq bool // should it find the next request on the same conn?
+ shouldDiscardBody bool // should the handler discard body after it exits?
}
-func (t handlerBodyCloseTest) connectionHeader() string {
+func (t bodyDiscardTest) connectionHeader() string {
if t.reqConnClose {
return "Connection: close\r\n"
}
return ""
}
-var handlerBodyCloseTests = [...]handlerBodyCloseTest{
- // Small enough to slurp past to the next request +
- // has Content-Length.
+var bodyDiscardTests = [...]bodyDiscardTest{
+ // Have:
+ // - Small body.
+ // - Content-Length defined.
+ // Should:
+ // - Discard remaining body.
0: {
- bodySize: 20 << 10,
- bodyChunked: false,
- reqConnClose: false,
- wantEOFSearch: true,
- wantNextReq: true,
+ bodySize: 20 << 10,
+ bodyChunked: false,
+ reqConnClose: false,
+ shouldDiscardBody: true,
},
- // Small enough to slurp past to the next request +
- // is chunked.
+ // Have:
+ // - Small body.
+ // - Chunked (no Content-Length defined).
+ // Should:
+ // - Discard remaining body.
1: {
- bodySize: 20 << 10,
- bodyChunked: true,
- reqConnClose: false,
- wantEOFSearch: true,
- wantNextReq: true,
+ bodySize: 20 << 10,
+ bodyChunked: true,
+ reqConnClose: false,
+ shouldDiscardBody: true,
},
- // Small enough to slurp past to the next request +
- // has Content-Length +
- // declares Connection: close (so pointless to read more).
+ // Have:
+ // - Small body.
+ // - Content-Length defined.
+ // - Connection: close.
+ // Should:
+ // - Not discard remaining body (no point as Connection: close).
2: {
- bodySize: 20 << 10,
- bodyChunked: false,
- reqConnClose: true,
- wantEOFSearch: false,
- wantNextReq: false,
+ bodySize: 20 << 10,
+ bodyChunked: false,
+ reqConnClose: true,
+ shouldDiscardBody: false,
},
- // Small enough to slurp past to the next request +
- // declares Connection: close,
- // but chunked, so it might have trailers.
- // TODO: maybe skip this search if no trailers were declared
- // in the headers.
+ // Have:
+ // - Small body.
+ // - Chunked (no Content-Length defined).
+ // - Connection: close.
+ // Should:
+ // - Discard remaining body (chunked, so it might have trailers).
+ //
+ // TODO: maybe skip this if no trailers were declared in the headers.
3: {
- bodySize: 20 << 10,
- bodyChunked: true,
- reqConnClose: true,
- wantEOFSearch: true,
- wantNextReq: false,
+ bodySize: 20 << 10,
+ bodyChunked: true,
+ reqConnClose: true,
+ shouldDiscardBody: true,
},
- // Big with Content-Length, so give up immediately if we know it's too big.
+ // Have:
+ // - Large body.
+ // - Content-Length defined.
+ // Should:
+ // - Not discard remaining body (we know it is too large from Content-Length).
4: {
- bodySize: 1 << 20,
- bodyChunked: false, // has a Content-Length
- reqConnClose: false,
- wantEOFSearch: false,
- wantNextReq: false,
+ bodySize: 1 << 20,
+ bodyChunked: false,
+ reqConnClose: false,
+ shouldDiscardBody: false,
},
- // Big chunked, so read a bit before giving up.
+ // Have:
+ // - Large body.
+ // - Chunked (no Content-Length defined).
+ // Should:
+ // - Discard remaining body (chunked, so we try up to a limit before giving up).
5: {
- bodySize: 1 << 20,
- bodyChunked: true,
- reqConnClose: false,
- wantEOFSearch: true,
- wantNextReq: false,
+ bodySize: 1 << 20,
+ bodyChunked: true,
+ reqConnClose: false,
+ shouldDiscardBody: true,
},
- // Big with Connection: close, but chunked, so search for trailers.
- // TODO: maybe skip this search if no trailers were declared
- // in the headers.
+ // Have:
+ // - Large body.
+ // - Content-Length defined.
+ // - Connection: close.
+ // Should:
+ // - Not discard remaining body (Connection: Close, and Content-Length is too large).
6: {
- bodySize: 1 << 20,
- bodyChunked: true,
- reqConnClose: true,
- wantEOFSearch: true,
- wantNextReq: false,
+ bodySize: 1 << 20,
+ bodyChunked: false,
+ reqConnClose: true,
+ shouldDiscardBody: false,
},
-
- // Big with Connection: close, so don't do any reads on Close.
- // With Content-Length.
+ // Have:
+ // - Large body.
+ // - Chunked (no Content-Length defined).
+ // - Connection: close.
+ // Should:
+ // - Discard remaining body (chunked, so it might have trailers).
+ //
+ // TODO: maybe skip this if no trailers were declared in the headers.
7: {
- bodySize: 1 << 20,
- bodyChunked: false,
- reqConnClose: true,
- wantEOFSearch: false,
- wantNextReq: false,
+ bodySize: 1 << 20,
+ bodyChunked: true,
+ reqConnClose: true,
+ shouldDiscardBody: true,
},
}
-func TestHandlerBodyClose(t *testing.T) {
+func TestBodyDiscard(t *testing.T) {
setParallel(t)
if testing.Short() && testenv.Builder() == "" {
t.Skip("skipping in -short mode")
}
- for i, tt := range handlerBodyCloseTests {
- testHandlerBodyClose(t, i, tt)
+ for i, tt := range bodyDiscardTests {
+ testBodyDiscard(t, i, tt)
}
}
-func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
+func testBodyDiscard(t *testing.T, i int, tt bodyDiscardTest) {
conn := new(testConn)
body := strings.Repeat("x", tt.bodySize)
if tt.bodyChunked {
@@ -2275,12 +2294,12 @@ func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
cw.Close()
conn.readBuf.WriteString("\r\n")
} else {
- conn.readBuf.Write([]byte(fmt.Sprintf(
+ conn.readBuf.Write(fmt.Appendf(nil,
"POST / HTTP/1.1\r\n"+
"Host: test\r\n"+
tt.connectionHeader()+
"Content-Length: %d\r\n"+
- "\r\n", len(body))))
+ "\r\n", len(body)))
conn.readBuf.Write([]byte(body))
}
if !tt.reqConnClose {
@@ -2295,26 +2314,23 @@ func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
}
ls := &oneConnListener{conn}
- var numReqs int
- var size0, size1 int
+ var initialSize, closedSize, exitSize int
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
- numReqs++
- if numReqs == 1 {
- size0 = readBufLen()
- req.Body.Close()
- size1 = readBufLen()
- }
+ initialSize = readBufLen()
+ req.Body.Close()
+ closedSize = readBufLen()
}))
<-conn.closec
- if numReqs < 1 || numReqs > 2 {
- t.Fatalf("%d. bug in test. unexpected number of requests = %d", i, numReqs)
+ exitSize = readBufLen()
+
+ if initialSize != closedSize {
+ t.Errorf("%d. Close() within request handler should be a no-op, but body size went from %d to %d", i, initialSize, closedSize)
}
- didSearch := size0 != size1
- if didSearch != tt.wantEOFSearch {
- t.Errorf("%d. did EOF search = %v; want %v (size went from %d to %d)", i, didSearch, !didSearch, size0, size1)
+ if tt.shouldDiscardBody && closedSize <= exitSize {
+ t.Errorf("%d. want body content to be discarded upon request handler exit, but size went from %d to %d", i, closedSize, exitSize)
}
- if tt.wantNextReq && numReqs != 2 {
- t.Errorf("%d. numReq = %d; want 2", i, numReqs)
+ if !tt.shouldDiscardBody && closedSize != exitSize {
+ t.Errorf("%d. want body content to not be discarded upon request handler exit, but size went from %d to %d", i, closedSize, exitSize)
}
}
diff --git a/src/net/http/server.go b/src/net/http/server.go
index 02554d1a20..5d8e576f71 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -1077,9 +1077,6 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
req.ctx = ctx
req.RemoteAddr = c.remoteAddr
req.TLS = c.tlsState
- if body, ok := req.Body.(*body); ok {
- body.doEarlyClose = true
- }
// Adjust the read deadline if necessary.
if !hdrDeadline.Equal(wholeReqDeadline) {
@@ -1711,9 +1708,11 @@ func (w *response) finishRequest() {
w.conn.r.abortPendingRead()
- // Close the body (regardless of w.closeAfterReply) so we can
- // re-use its bufio.Reader later safely.
- w.reqBody.Close()
+ // Try to discard the body (regardless of w.closeAfterReply), so we can
+ // potentially reuse it in the same connection.
+ if b, ok := w.reqBody.(*body); ok {
+ b.tryDiscardBody()
+ }
if w.req.MultipartForm != nil {
w.req.MultipartForm.RemoveAll()
@@ -1741,16 +1740,16 @@ func (w *response) shouldReuseConnection() bool {
return false
}
- if w.closedRequestBodyEarly() {
+ if w.didIncompleteDiscard() {
return false
}
return true
}
-func (w *response) closedRequestBodyEarly() bool {
+func (w *response) didIncompleteDiscard() bool {
body, ok := w.req.Body.(*body)
- return ok && body.didEarlyClose()
+ return ok && body.didIncompleteDiscard()
}
func (w *response) Flush() {
@@ -2106,6 +2105,18 @@ func (c *conn) serve(ctx context.Context) {
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
inFlightResponse = w
+ // Ensure that Close() invocations within request handlers do not
+ // discard the body.
+ if b, ok := w.reqBody.(*body); ok {
+ b.mu.Lock()
+ b.inRequestHandler = true
+ b.mu.Unlock()
+ defer func() {
+ b.mu.Lock()
+ b.inRequestHandler = false
+ b.mu.Unlock()
+ }()
+ }
serverHandler{c.server}.ServeHTTP(w, w.req)
inFlightResponse = nil
w.cancelCtx()
@@ -2116,7 +2127,7 @@ func (c *conn) serve(ctx context.Context) {
w.finishRequest()
c.rwc.SetWriteDeadline(time.Time{})
if !w.shouldReuseConnection() {
- if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
+ if w.requestBodyLimitHit || w.didIncompleteDiscard() {
c.closeWriteAndWait()
}
return
diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go
index 675551287f..9b972aaa44 100644
--- a/src/net/http/transfer.go
+++ b/src/net/http/transfer.go
@@ -809,17 +809,17 @@ func fixTrailer(header Header, chunked bool) (Header, error) {
// Close ensures that the body has been fully read
// and then reads the trailer if necessary.
type body struct {
- src io.Reader
- hdr any // non-nil (Response or Request) value means read trailer
- r *bufio.Reader // underlying wire-format reader for the trailer
- closing bool // is the connection to be closed after reading body?
- doEarlyClose bool // whether Close should stop early
-
- mu sync.Mutex // guards following, and calls to Read and Close
- sawEOF bool
- closed bool
- earlyClose bool // Close called and we didn't read to the end of src
- onHitEOF func() // if non-nil, func to call when EOF is Read
+ src io.Reader
+ hdr any // non-nil (Response or Request) value means read trailer
+ r *bufio.Reader // underlying wire-format reader for the trailer
+ closing bool // is the connection to be closed after reading body?
+
+ mu sync.Mutex // guards following, and calls to Read and Close
+ sawEOF bool
+ closed bool
+ incompleteDiscard bool // if true, we failed to discard the body content completely
+ inRequestHandler bool // used so Close() calls from within request handlers do not discard body
+ onHitEOF func() // if non-nil, func to call when EOF is Read
}
// ErrBodyReadAfterClose is returned when reading a [Request] or [Response]
@@ -968,51 +968,69 @@ func (b *body) unreadDataSizeLocked() int64 {
return -1
}
+// shouldDiscardBodyLocked determines whether a body needs to discard its
+// content or not.
+// b.mu must be held.
+func (b *body) shouldDiscardBodyLocked() bool {
+ // Already saw EOF. No point in discarding the body.
+ if b.sawEOF {
+ return false
+ }
+ // No trailer and closing the connection next. No point in discarding the
+ // body since it will not be reusable.
+ if b.hdr == nil && b.closing {
+ return false
+ }
+ return true
+}
+
+// tryDiscardBody attempts to discard the body content. If the body cannot be
+// discarded completely, b.incompleteDiscard will be set to true.
+func (b *body) tryDiscardBody() {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ if !b.shouldDiscardBodyLocked() {
+ return
+ }
+ // Read up to maxPostHandlerReadBytes bytes of the body, looking for EOF
+ // (and trailers), so we can re-use this connection.
+ if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes {
+ // There was a declared Content-Length, and we have more bytes
+ // remaining than our maxPostHandlerReadBytes tolerance. So, give up.
+ b.incompleteDiscard = true
+ return
+ }
+ // Consume the body, which will also lead to us reading the trailer headers
+ // after the body, if present.
+ n, err := io.CopyN(io.Discard, bodyLocked{b}, maxPostHandlerReadBytes)
+ if err == io.EOF {
+ err = nil
+ }
+ if n == maxPostHandlerReadBytes || err != nil {
+ b.incompleteDiscard = true
+ }
+}
+
func (b *body) Close() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return nil
}
- var err error
- switch {
- case b.sawEOF:
- // Already saw EOF, so no need going to look for it.
- case b.hdr == nil && b.closing:
- // no trailer and closing the connection next.
- // no point in reading to EOF.
- case b.doEarlyClose:
- // Read up to maxPostHandlerReadBytes bytes of the body, looking
- // for EOF (and trailers), so we can re-use this connection.
- if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes {
- // There was a declared Content-Length, and we have more bytes remaining
- // than our maxPostHandlerReadBytes tolerance. So, give up.
- b.earlyClose = true
- } else {
- var n int64
- // Consume the body, or, which will also lead to us reading
- // the trailer headers after the body, if present.
- n, err = io.CopyN(io.Discard, bodyLocked{b}, maxPostHandlerReadBytes)
- if err == io.EOF {
- err = nil
- }
- if n == maxPostHandlerReadBytes {
- b.earlyClose = true
- }
- }
- default:
- // Fully consume the body, which will also lead to us reading
- // the trailer headers after the body, if present.
- _, err = io.Copy(io.Discard, bodyLocked{b})
- }
b.closed = true
+ if !b.shouldDiscardBodyLocked() || b.inRequestHandler {
+ return nil
+ }
+ // Fully consume the body, which will also lead to us reading
+ // the trailer headers after the body, if present.
+ _, err := io.Copy(io.Discard, bodyLocked{b})
return err
}
-func (b *body) didEarlyClose() bool {
+func (b *body) didIncompleteDiscard() bool {
b.mu.Lock()
defer b.mu.Unlock()
- return b.earlyClose
+ return b.incompleteDiscard
}
// bodyRemains reports whether future Read calls might
@@ -1036,9 +1054,6 @@ type bodyLocked struct {
}
func (bl bodyLocked) Read(p []byte) (n int, err error) {
- if bl.b.closed {
- return 0, ErrBodyReadAfterClose
- }
return bl.b.readLocked(p)
}
--
cgit v1.3
From 1a03d0db3f36c1a81a184fa15a54f716569a9972 Mon Sep 17 00:00:00 2001
From: thepudds
Date: Wed, 5 Nov 2025 12:18:49 -0500
Subject: runtime: skip tests for GOEXPERIMENT=arenas that do not handle
clobberfree=1
When run with GODEBUG=clobberfree=1, three out of seven of the top-level
tests in runtime/arena_test.go fail with a SIGSEGV inside the
clobberfree function where it is overwriting freed memory
with 0xdeadbeef.
This is not a new problem. For example, this crashes in Go 1.20:
GODEBUG=clobberfree=1 go test runtime -run=TestUserArena
It would be nice for all.bash to pass with GODEBUG=clobberfree=1,
including it is useful for testing the automatic reclaiming of
dead memory via runtime.freegc in #74299.
Given the GOEXPERIMENT=arenas in #51317 is not planned to move forward
(and is hopefully slated to be replace by regions before too long),
for now we just skip those three tests in order to get all.bash
passing with GODEBUG=clobberfree=1.
Updates #74299
Change-Id: I384d96791157b30c73457d582a45dd74c5607ee0
Reviewed-on: https://go-review.googlesource.com/c/go/+/715080
Reviewed-by: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
---
src/runtime/arena_test.go | 15 +++++++++++++++
src/runtime/export_test.go | 6 ++++++
2 files changed, 21 insertions(+)
(limited to 'src')
diff --git a/src/runtime/arena_test.go b/src/runtime/arena_test.go
index ca5223b59c..0bb1950464 100644
--- a/src/runtime/arena_test.go
+++ b/src/runtime/arena_test.go
@@ -36,6 +36,11 @@ type largeScalar [UserArenaChunkBytes + 1]byte
type largePointer [UserArenaChunkBytes/unsafe.Sizeof(&smallPointer{}) + 1]*smallPointer
func TestUserArena(t *testing.T) {
+ if Clobberfree() {
+ // This test crashes with SEGV in clobberfree in mgcsweep.go with GODEBUG=clobberfree=1.
+ t.Skip("triggers SEGV with GODEBUG=clobberfree=1")
+ }
+
// Set GOMAXPROCS to 2 so we don't run too many of these
// tests in parallel.
defer GOMAXPROCS(GOMAXPROCS(2))
@@ -228,6 +233,11 @@ func runSubTestUserArenaSlice[S comparable](t *testing.T, value []S, parallel bo
}
func TestUserArenaLiveness(t *testing.T) {
+ if Clobberfree() {
+ // This test crashes with SEGV in clobberfree in mgcsweep.go with GODEBUG=clobberfree=1.
+ t.Skip("triggers SEGV with GODEBUG=clobberfree=1")
+ }
+
t.Run("Free", func(t *testing.T) {
testUserArenaLiveness(t, false)
})
@@ -320,6 +330,11 @@ func testUserArenaLiveness(t *testing.T, useArenaFinalizer bool) {
}
func TestUserArenaClearsPointerBits(t *testing.T) {
+ if Clobberfree() {
+ // This test crashes with SEGV in clobberfree in mgcsweep.go with GODEBUG=clobberfree=1.
+ t.Skip("triggers SEGV with GODEBUG=clobberfree=1")
+ }
+
// This is a regression test for a serious issue wherein if pointer bits
// aren't properly cleared, it's possible to allocate scalar data down
// into a previously pointer-ful area, causing misinterpretation by the GC.
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index d17984881d..731ba5d6b9 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -238,6 +238,12 @@ func SetEnvs(e []string) { envs = e }
const PtrSize = goarch.PtrSize
+const ClobberdeadPtr = clobberdeadPtr
+
+func Clobberfree() bool {
+ return debug.clobberfree != 0
+}
+
var ForceGCPeriod = &forcegcperiod
// SetTracebackEnv is like runtime/debug.SetTraceback, but it raises
--
cgit v1.3
From 5a347b775e051060f6f52ce25ec82a024a3a9365 Mon Sep 17 00:00:00 2001
From: thepudds
Date: Wed, 12 Nov 2025 20:50:39 -0500
Subject: runtime: set GOEXPERIMENT=runtimefreegc to disabled by default
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
The plan has been for GOEXPERIMENT=runtimefreegc to be disabled
by default for Go 1.26, so here we disable it.
Also, we update the name of the GOEXPERIMENT to reflect the latest name.
Updates #74299
Change-Id: I94a34784700152e13ca93ef6711ee9b7f1769d9a
Reviewed-on: https://go-review.googlesource.com/c/go/+/720120
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/internal/buildcfg/exp.go | 1 -
src/internal/goexperiment/exp_runtimefree_off.go | 8 --------
src/internal/goexperiment/exp_runtimefree_on.go | 8 --------
src/internal/goexperiment/exp_runtimefreegc_off.go | 8 ++++++++
src/internal/goexperiment/exp_runtimefreegc_on.go | 8 ++++++++
src/internal/goexperiment/flags.go | 4 ++--
6 files changed, 18 insertions(+), 19 deletions(-)
delete mode 100644 src/internal/goexperiment/exp_runtimefree_off.go
delete mode 100644 src/internal/goexperiment/exp_runtimefree_on.go
create mode 100644 src/internal/goexperiment/exp_runtimefreegc_off.go
create mode 100644 src/internal/goexperiment/exp_runtimefreegc_on.go
(limited to 'src')
diff --git a/src/internal/buildcfg/exp.go b/src/internal/buildcfg/exp.go
index 31195f94c0..f1a1d8632e 100644
--- a/src/internal/buildcfg/exp.go
+++ b/src/internal/buildcfg/exp.go
@@ -83,7 +83,6 @@ func ParseGOEXPERIMENT(goos, goarch, goexp string) (*ExperimentFlags, error) {
RegabiArgs: regabiSupported,
Dwarf5: dwarf5Supported,
RandomizedHeapBase64: true,
- RuntimeFree: true,
SizeSpecializedMalloc: true,
GreenTeaGC: true,
}
diff --git a/src/internal/goexperiment/exp_runtimefree_off.go b/src/internal/goexperiment/exp_runtimefree_off.go
deleted file mode 100644
index 3affe434f2..0000000000
--- a/src/internal/goexperiment/exp_runtimefree_off.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build !goexperiment.runtimefree
-
-package goexperiment
-
-const RuntimeFree = false
-const RuntimeFreeInt = 0
diff --git a/src/internal/goexperiment/exp_runtimefree_on.go b/src/internal/goexperiment/exp_runtimefree_on.go
deleted file mode 100644
index 176278b542..0000000000
--- a/src/internal/goexperiment/exp_runtimefree_on.go
+++ /dev/null
@@ -1,8 +0,0 @@
-// Code generated by mkconsts.go. DO NOT EDIT.
-
-//go:build goexperiment.runtimefree
-
-package goexperiment
-
-const RuntimeFree = true
-const RuntimeFreeInt = 1
diff --git a/src/internal/goexperiment/exp_runtimefreegc_off.go b/src/internal/goexperiment/exp_runtimefreegc_off.go
new file mode 100644
index 0000000000..195f031b0b
--- /dev/null
+++ b/src/internal/goexperiment/exp_runtimefreegc_off.go
@@ -0,0 +1,8 @@
+// Code generated by mkconsts.go. DO NOT EDIT.
+
+//go:build !goexperiment.runtimefreegc
+
+package goexperiment
+
+const RuntimeFreegc = false
+const RuntimeFreegcInt = 0
diff --git a/src/internal/goexperiment/exp_runtimefreegc_on.go b/src/internal/goexperiment/exp_runtimefreegc_on.go
new file mode 100644
index 0000000000..2afe0558ec
--- /dev/null
+++ b/src/internal/goexperiment/exp_runtimefreegc_on.go
@@ -0,0 +1,8 @@
+// Code generated by mkconsts.go. DO NOT EDIT.
+
+//go:build goexperiment.runtimefreegc
+
+package goexperiment
+
+const RuntimeFreegc = true
+const RuntimeFreegcInt = 1
diff --git a/src/internal/goexperiment/flags.go b/src/internal/goexperiment/flags.go
index b3a3c8a497..957c41c0f5 100644
--- a/src/internal/goexperiment/flags.go
+++ b/src/internal/goexperiment/flags.go
@@ -113,8 +113,8 @@ type Flags struct {
// platforms.
RandomizedHeapBase64 bool
- // RuntimeFree enables the runtime to free and reuse memory more eagerly in some circumstances with compiler help.
- RuntimeFree bool
+ // RuntimeFreegc enables the runtime to free and reuse memory more eagerly in some circumstances with compiler help.
+ RuntimeFreegc bool
// SizeSpecializedMalloc enables malloc implementations that are specialized per size class.
SizeSpecializedMalloc bool
--
cgit v1.3
From fecfcaa4f68a220f47e2c7c8b65d55906dbf8d46 Mon Sep 17 00:00:00 2001
From: thepudds
Date: Tue, 4 Nov 2025 09:33:17 -0500
Subject: runtime: add runtime.freegc to reduce GC work
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds runtime.freegc:
func freegc(ptr unsafe.Pointer, uintptr size, noscan bool)
Memory freed via runtime.freegc is made immediately reusable for
the next allocation in the same size class, without waiting for a
GC cycle, and hence can dramatically reduce pressure on the GC. A sample
microbenchmark included below shows strings.Builder operating roughly
2x faster.
An experimental modification to reflect to use runtime.freegc
and then using that reflect with json/v2 gave reported memory
allocation reductions of -43.7%, -32.9%, -21.9%, -22.0%, -1.0%
for the 5 official real-world unmarshalling benchmarks from
go-json-experiment/jsonbench by the authors of json/v2, covering
the CanadaGeometry through TwitterStatus datasets.
Note: there is no intent to modify the standard library to have
explicit calls to runtime.freegc, and of course such an ability
would never be exposed to end-user code.
Later CLs in this stack teach the compiler how to automatically
insert runtime.freegc calls when it can prove it is safe to do so.
(The reflect modification and other experimental changes to
the standard library were just that -- experiments. It was
very helpful while initially developing runtime.freegc to see
more complex uses and closer-to-real-world benchmark results
prior to updating the compiler.)
This CL only addresses noscan span classes (heap objects without
pointers), such as the backing memory for a []byte or string. A
follow-on CL adds support for heap objects with pointers.
If we update strings.Builder to explicitly call runtime.freegc on its
internal buf after a resize operation (but without freeing the usually
final incarnation of buf that will be returned to the user as a string),
we can see some nice benchmark results on the existing strings
benchmarks that call Builder.Write N times and then call Builder.String.
Here, the (uncommon) case of a single Builder.Write is not helped (given
it never resizes after first alloc if there is only one Write), but the
impact grows such that it is up to ~2x faster as there are more resize
operations due to more strings.Builder.Write calls:
│ disabled.out │ new-free-20.txt │
│ sec/op │ sec/op vs base │
BuildString_Builder/1Write_36Bytes_NoGrow-4 55.82n ± 2% 55.86n ± 2% ~ (p=0.794 n=20)
BuildString_Builder/2Write_36Bytes_NoGrow-4 125.2n ± 2% 115.4n ± 1% -7.86% (p=0.000 n=20)
BuildString_Builder/3Write_36Bytes_NoGrow-4 224.0n ± 1% 188.2n ± 2% -16.00% (p=0.000 n=20)
BuildString_Builder/5Write_36Bytes_NoGrow-4 239.1n ± 9% 205.1n ± 1% -14.20% (p=0.000 n=20)
BuildString_Builder/8Write_36Bytes_NoGrow-4 422.8n ± 3% 325.4n ± 1% -23.04% (p=0.000 n=20)
BuildString_Builder/10Write_36Bytes_NoGrow-4 436.9n ± 2% 342.3n ± 1% -21.64% (p=0.000 n=20)
BuildString_Builder/100Write_36Bytes_NoGrow-4 4.403µ ± 1% 2.381µ ± 2% -45.91% (p=0.000 n=20)
BuildString_Builder/1000Write_36Bytes_NoGrow-4 48.28µ ± 2% 21.38µ ± 2% -55.71% (p=0.000 n=20)
See the design document for more discussion of the strings.Builder case.
For testing, we add tests that attempt to exercise different aspects
of the underlying freegc and mallocgc behavior on the reuse path.
Validating the assist credit manipulations turned out to be subtle,
so a test for that is added in the next CL. There are also
invariant checks added, controlled by consts (primarily the
doubleCheckReusable const currently).
This CL also adds support in runtime.freegc for GODEBUG=clobberfree=1
to immediately overwrite freed memory with 0xdeadbeef, which
can help a higher-level test fail faster in the event of a bug,
and also the GC specifically looks for that pattern and throws
a fatal error if it unexpectedly finds it.
A later CL (currently experimental) adds GODEBUG=clobberfree=2,
which uses mprotect (or VirtualProtect on Windows) to set
freed memory to fault if read or written, until the runtime
later unprotects the memory on the mallocgc reuse path.
For the cases where a normal allocation is happening without any reuse,
some initial microbenchmarks suggest the impact of these changes could
be small to negligible (at least with GOAMD64=v3):
goos: linux
goarch: amd64
pkg: runtime
cpu: AMD EPYC 7B13
│ base-512M-v3.bench │ ps16-512M-goamd64-v3.bench │
│ sec/op │ sec/op vs base │
Malloc8-16 11.01n ± 1% 10.94n ± 1% -0.68% (p=0.038 n=20)
Malloc16-16 17.15n ± 1% 17.05n ± 0% -0.55% (p=0.007 n=20)
Malloc32-16 18.65n ± 1% 18.42n ± 0% -1.26% (p=0.000 n=20)
MallocTypeInfo8-16 18.63n ± 0% 18.36n ± 0% -1.45% (p=0.000 n=20)
MallocTypeInfo16-16 22.32n ± 0% 22.65n ± 0% +1.50% (p=0.000 n=20)
MallocTypeInfo32-16 23.37n ± 0% 23.89n ± 0% +2.23% (p=0.000 n=20)
geomean 18.02n 18.01n -0.05%
These last benchmark results include the runtime updates to support
span classes with pointers (which was originally part of this CL,
but later split out for ease of review).
Updates #74299
Change-Id: Icceaa0f79f85c70cd1a718f9a4e7f0cf3d77803c
Reviewed-on: https://go-review.googlesource.com/c/go/+/673695
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
Reviewed-by: Junyang Shao
---
src/cmd/link/internal/loader/loader.go | 3 +-
src/cmd/link/link_test.go | 1 +
src/cmd/link/testdata/linkname/freegc.go | 18 ++
src/runtime/export_test.go | 9 +
src/runtime/malloc.go | 329 ++++++++++++++++++++++++++++++-
src/runtime/malloc_test.go | 286 +++++++++++++++++++++++++++
src/runtime/mcache.go | 52 ++++-
src/runtime/mheap.go | 2 +-
8 files changed, 693 insertions(+), 7 deletions(-)
create mode 100644 src/cmd/link/testdata/linkname/freegc.go
(limited to 'src')
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index 2d386c0c65..9ab55643f6 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -2464,10 +2464,11 @@ var blockedLinknames = map[string][]string{
// Experimental features
"runtime.goroutineLeakGC": {"runtime/pprof"},
"runtime.goroutineleakcount": {"runtime/pprof"},
+ "runtime.freegc": {}, // disallow all packages
// Others
"net.newWindowsFile": {"net"}, // pushed from os
"testing/synctest.testingSynctestTest": {"testing/synctest"}, // pushed from testing
- "runtime.addmoduledata": {}, // disallow all package
+ "runtime.addmoduledata": {}, // disallow all packages
}
// check if a linkname reference to symbol s from pkg is allowed
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index 31822d21f3..6ab1246c81 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -1616,6 +1616,7 @@ func TestCheckLinkname(t *testing.T) {
// pull linkname of a builtin symbol is not ok
{"builtin.go", false},
{"addmoduledata.go", false},
+ {"freegc.go", false},
// legacy bad linkname is ok, for now
{"fastrand.go", true},
{"badlinkname.go", true},
diff --git a/src/cmd/link/testdata/linkname/freegc.go b/src/cmd/link/testdata/linkname/freegc.go
new file mode 100644
index 0000000000..390063f8e9
--- /dev/null
+++ b/src/cmd/link/testdata/linkname/freegc.go
@@ -0,0 +1,18 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Linkname runtime.freegc is not allowed.
+
+package main
+
+import (
+ _ "unsafe"
+)
+
+//go:linkname freegc runtime.freegc
+func freegc()
+
+func main() {
+ freegc()
+}
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index 731ba5d6b9..48dcf5aa39 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -639,6 +639,15 @@ func RunGetgThreadSwitchTest() {
}
}
+// Expose freegc for testing.
+func Freegc(p unsafe.Pointer, size uintptr, noscan bool) {
+ freegc(p, size, noscan)
+}
+
+const SizeSpecializedMallocEnabled = sizeSpecializedMallocEnabled
+
+const RuntimeFreegcEnabled = runtimeFreegcEnabled
+
const (
PageSize = pageSize
PallocChunkPages = pallocChunkPages
diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go
index fc4f21b532..13f5fc3081 100644
--- a/src/runtime/malloc.go
+++ b/src/runtime/malloc.go
@@ -1080,7 +1080,8 @@ func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, checkGCTrigger
//
// We might consider turning these on by default; many of them previously were.
// They account for a few % of mallocgc's cost though, which does matter somewhat
-// at scale.
+// at scale. (When testing changes to malloc, consider enabling this, and also
+// some function-local 'doubleCheck' consts such as in mbitmap.go currently.)
const doubleCheckMalloc = false
// sizeSpecializedMallocEnabled is the set of conditions where we enable the size-specialized
@@ -1089,6 +1090,12 @@ const doubleCheckMalloc = false
// properly on plan9, so size-specialized malloc is also disabled on plan9.
const sizeSpecializedMallocEnabled = goexperiment.SizeSpecializedMalloc && GOOS != "plan9" && !asanenabled && !raceenabled && !msanenabled && !valgrindenabled
+// runtimeFreegcEnabled is the set of conditions where we enable the runtime.freegc
+// implementation and the corresponding allocation-related changes: the experiment must be
+// enabled, and none of the memory sanitizers should be enabled. We allow the race detector,
+// in contrast to sizeSpecializedMallocEnabled.
+const runtimeFreegcEnabled = goexperiment.RuntimeFreegc && !asanenabled && !msanenabled && !valgrindenabled
+
// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
@@ -1150,7 +1157,8 @@ func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
size += asanRZ
}
- // Assist the GC if needed.
+ // Assist the GC if needed. (On the reuse path, we currently compensate for this;
+ // changes here might require changes there.)
if gcBlackenEnabled != 0 {
deductAssistCredit(size)
}
@@ -1413,6 +1421,16 @@ func mallocgcSmallNoscan(size uintptr, typ *_type, needzero bool) (unsafe.Pointe
size = uintptr(gc.SizeClassToSize[sizeclass])
spc := makeSpanClass(sizeclass, true)
span := c.alloc[spc]
+
+ // First, check for a reusable object.
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+ // We have a reusable object, use it.
+ x := mallocgcSmallNoscanReuse(c, span, spc, size, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ return x, size
+ }
+
v := nextFreeFast(span)
if v == 0 {
v, span, checkGCTrigger = c.nextFree(spc)
@@ -1472,6 +1490,55 @@ func mallocgcSmallNoscan(size uintptr, typ *_type, needzero bool) (unsafe.Pointe
return x, size
}
+// mallocgcSmallNoscanReuse returns a previously freed noscan object after preparing it for reuse.
+// It must only be called if hasReusableNoscan returned true.
+func mallocgcSmallNoscanReuse(c *mcache, span *mspan, spc spanClass, size uintptr, needzero bool) unsafe.Pointer {
+ // TODO(thepudds): could nextFreeFast, nextFree and nextReusable return unsafe.Pointer?
+ // Maybe doesn't matter. gclinkptr might be for historical reasons.
+ v, span := c.nextReusableNoScan(span, spc)
+ x := unsafe.Pointer(v)
+
+ // Compensate for the GC assist credit deducted in mallocgc (before calling us and
+ // after we return) because this is not a newly allocated object. We use the full slot
+ // size (elemsize) here because that's what mallocgc deducts overall. Note we only
+ // adjust this when gcBlackenEnabled is true, which follows mallocgc behavior.
+ // TODO(thepudds): a follow-up CL adds a more specific test of our assist credit
+ // handling, including for validating internal fragmentation handling.
+ if gcBlackenEnabled != 0 {
+ addAssistCredit(size)
+ }
+
+ // This is a previously used object, so only check needzero (and not span.needzero)
+ // for clearing.
+ if needzero {
+ memclrNoHeapPointers(x, size)
+ }
+
+ // See publicationBarrier comment in mallocgcSmallNoscan.
+ publicationBarrier()
+
+ // Finish and return. Note that we do not update span.freeIndexForScan, profiling info,
+ // nor do we check gcTrigger.
+ // TODO(thepudds): the current approach is viable for a GOEXPERIMENT, but
+ // means we do not profile reused heap objects. Ultimately, we will need a better
+ // approach for profiling, or at least ensure we are not introducing bias in the
+ // profiled allocations.
+ // TODO(thepudds): related, we probably want to adjust how allocs and frees are counted
+ // in the existing stats. Currently, reused objects are not counted as allocs nor
+ // frees, but instead roughly appear as if the original heap object lived on. We
+ // probably will also want some additional runtime/metrics, and generally think about
+ // user-facing observability & diagnostics, though all this likely can wait for an
+ // official proposal.
+ if writeBarrier.enabled {
+ // Allocate black during GC.
+ // All slots hold nil so no scanning is needed.
+ // This may be racing with GC so do it atomically if there can be
+ // a race marking the bit.
+ gcmarknewobject(span, uintptr(x))
+ }
+ return x
+}
+
func mallocgcSmallScanNoHeader(size uintptr, typ *_type) (unsafe.Pointer, uintptr) {
// Set mp.mallocing to keep from being preempted by GC.
mp := acquirem()
@@ -1816,8 +1883,6 @@ func postMallocgcDebug(x unsafe.Pointer, elemsize uintptr, typ *_type) {
// by size bytes, and assists the GC if necessary.
//
// Caller must be preemptible.
-//
-// Returns the G for which the assist credit was accounted.
func deductAssistCredit(size uintptr) {
// Charge the current user G for this allocation.
assistG := getg()
@@ -1836,6 +1901,262 @@ func deductAssistCredit(size uintptr) {
}
}
+// addAssistCredit is like deductAssistCredit,
+// but adds credit rather than removes,
+// and never calls gcAssistAlloc.
+func addAssistCredit(size uintptr) {
+ // Credit the current user G.
+ assistG := getg()
+ if assistG.m.curg != nil { // TODO(thepudds): do we need to do this?
+ assistG = assistG.m.curg
+ }
+ // Credit the size against the G.
+ assistG.gcAssistBytes += int64(size)
+}
+
+const (
+ // doubleCheckReusable enables some additional invariant checks for the
+ // runtime.freegc and reusable objects. Note that some of these checks alter timing,
+ // and it is good to test changes with and without this enabled.
+ doubleCheckReusable = false
+
+ // debugReusableLog enables some printlns for runtime.freegc and reusable objects.
+ debugReusableLog = false
+)
+
+// freegc records that a heap object is reusable and available for
+// immediate reuse in a subsequent mallocgc allocation, without
+// needing to wait for the GC cycle to progress.
+//
+// The information is recorded in a free list stored in the
+// current P's mcache. The caller must pass in the user size
+// and whether the object has pointers, which allows a faster free
+// operation.
+//
+// freegc must be called by the effective owner of ptr who knows
+// the pointer is logically dead, with no possible aliases that might
+// be used past that moment. In other words, ptr must be the
+// last and only pointer to its referent.
+//
+// The intended caller is the compiler.
+//
+// Note: please do not send changes that attempt to add freegc calls
+// to the standard library.
+//
+// ptr must point to a heap object or into the current g's stack,
+// in which case freegc is a no-op. In particular, ptr must not point
+// to memory in the data or bss sections, which is partially enforced.
+// For objects with a malloc header, ptr should point mallocHeaderSize bytes
+// past the base; otherwise, ptr should point to the base of the heap object.
+// In other words, ptr should be the same pointer that was returned by mallocgc.
+//
+// In addition, the caller must know that ptr's object has no specials, such
+// as might have been created by a call to SetFinalizer or AddCleanup.
+// (Internally, the runtime deals appropriately with internally-created
+// specials, such as specials for memory profiling).
+//
+// If the size of ptr's object is less than 16 bytes or greater than
+// 32KiB - gc.MallocHeaderSize bytes, freegc is currently a no-op. It must only
+// be called in alloc-safe places. It currently throws if noscan is false
+// (support for which is implemented in a later CL in our stack).
+//
+// Note that freegc accepts an unsafe.Pointer and hence keeps the pointer
+// alive. It therefore could be a pessimization in some cases (such
+// as a long-lived function) if the caller does not call freegc before
+// or roughly when the liveness analysis of the compiler
+// would otherwise have determined ptr's object is reclaimable by the GC.
+func freegc(ptr unsafe.Pointer, size uintptr, noscan bool) bool {
+ if !runtimeFreegcEnabled || sizeSpecializedMallocEnabled || !reusableSize(size) {
+ // TODO(thepudds): temporarily disable freegc with SizeSpecializedMalloc until we finish integrating.
+ return false
+ }
+ if ptr == nil {
+ throw("freegc nil")
+ }
+
+ // Set mp.mallocing to keep from being preempted by GC.
+ // Otherwise, the GC could flush our mcache or otherwise cause problems.
+ mp := acquirem()
+ if mp.mallocing != 0 {
+ throw("freegc deadlock")
+ }
+ if mp.gsignal == getg() {
+ throw("freegc during signal")
+ }
+ mp.mallocing = 1
+
+ if mp.curg.stack.lo <= uintptr(ptr) && uintptr(ptr) < mp.curg.stack.hi {
+ // This points into our stack, so free is a no-op.
+ mp.mallocing = 0
+ releasem(mp)
+ return false
+ }
+
+ if doubleCheckReusable {
+ // TODO(thepudds): we could enforce no free on globals in bss or data. Maybe by
+ // checking span via spanOf or spanOfHeap, or maybe walk from firstmoduledata
+ // like isGoPointerWithoutSpan, or activeModules, or something. If so, we might
+ // be able to delay checking until reuse (e.g., check span just before reusing,
+ // though currently we don't always need to lookup a span on reuse). If we think
+ // no usage patterns could result in globals, maybe enforcement for globals could
+ // be behind -d=checkptr=1 or similar. The compiler can have knowledge of where
+ // a variable is allocated, but stdlib does not, although there are certain
+ // usage patterns that cannot result in a global.
+ // TODO(thepudds): separately, consider a local debugReusableMcacheOnly here
+ // to ignore freed objects if not in mspan in mcache, maybe when freeing and reading,
+ // by checking something like s.base() <= uintptr(v) && uintptr(v) < s.limit. Or
+ // maybe a GODEBUG or compiler debug flag.
+ span := spanOf(uintptr(ptr))
+ if span == nil {
+ throw("nextReusable: nil span for pointer in free list")
+ }
+ if state := span.state.get(); state != mSpanInUse {
+ throw("nextReusable: span is not in use")
+ }
+ }
+
+ if debug.clobberfree != 0 {
+ clobberfree(ptr, size)
+ }
+
+ // We first check if p is still in our per-P cache.
+ // Get our per-P cache for small objects.
+ c := getMCache(mp)
+ if c == nil {
+ throw("freegc called without a P or outside bootstrapping")
+ }
+
+ v := uintptr(ptr)
+ if !noscan && !heapBitsInSpan(size) {
+ // mallocgcSmallScanHeader expects to get the base address of the object back
+ // from the findReusable funcs (as well as from nextFreeFast and nextFree), and
+ // not mallocHeaderSize bytes into a object, so adjust that here.
+ v -= mallocHeaderSize
+
+ // The size class lookup wants size to be adjusted by mallocHeaderSize.
+ size += mallocHeaderSize
+ }
+
+ // TODO(thepudds): should verify (behind doubleCheckReusable constant) that our calculated
+ // sizeclass here matches what's in span found via spanOf(ptr) or findObject(ptr).
+ var sizeclass uint8
+ if size <= gc.SmallSizeMax-8 {
+ sizeclass = gc.SizeToSizeClass8[divRoundUp(size, gc.SmallSizeDiv)]
+ } else {
+ sizeclass = gc.SizeToSizeClass128[divRoundUp(size-gc.SmallSizeMax, gc.LargeSizeDiv)]
+ }
+
+ spc := makeSpanClass(sizeclass, noscan)
+ s := c.alloc[spc]
+
+ if debugReusableLog {
+ if s.base() <= uintptr(v) && uintptr(v) < s.limit {
+ println("freegc [in mcache]:", hex(uintptr(v)), "sweepgen:", mheap_.sweepgen, "writeBarrier.enabled:", writeBarrier.enabled)
+ } else {
+ println("freegc [NOT in mcache]:", hex(uintptr(v)), "sweepgen:", mheap_.sweepgen, "writeBarrier.enabled:", writeBarrier.enabled)
+ }
+ }
+
+ if noscan {
+ c.addReusableNoscan(spc, uintptr(v))
+ } else {
+ // TODO(thepudds): implemented in later CL in our stack.
+ throw("freegc called for object with pointers, not yet implemented")
+ }
+
+ // For stats, for now we leave allocCount alone, roughly pretending to the rest
+ // of the system that this potential reuse never happened.
+
+ mp.mallocing = 0
+ releasem(mp)
+
+ return true
+}
+
+// nextReusableNoScan returns the next reusable object for a noscan span,
+// or 0 if no reusable object is found.
+func (c *mcache) nextReusableNoScan(s *mspan, spc spanClass) (gclinkptr, *mspan) {
+ if !runtimeFreegcEnabled {
+ return 0, s
+ }
+
+ // Pop a reusable pointer from the free list for this span class.
+ v := c.reusableNoscan[spc]
+ if v == 0 {
+ return 0, s
+ }
+ c.reusableNoscan[spc] = v.ptr().next
+
+ if debugReusableLog {
+ println("reusing from ptr free list:", hex(v), "sweepgen:", mheap_.sweepgen, "writeBarrier.enabled:", writeBarrier.enabled)
+ }
+ if doubleCheckReusable {
+ doubleCheckNextReusable(v) // debug only sanity check
+ }
+
+ // For noscan spans, we only need the span if the write barrier is enabled (so that our caller
+ // can call gcmarknewobject to allocate black). If the write barrier is enabled, we can skip
+ // looking up the span when the pointer is in a span in the mcache.
+ if !writeBarrier.enabled {
+ return v, nil
+ }
+ if s.base() <= uintptr(v) && uintptr(v) < s.limit {
+ // Return the original span.
+ return v, s
+ }
+
+ // We must find and return the span.
+ span := spanOf(uintptr(v))
+ if span == nil {
+ // TODO(thepudds): construct a test that triggers this throw.
+ throw("nextReusableNoScan: nil span for pointer in reusable object free list")
+ }
+
+ return v, span
+}
+
+// doubleCheckNextReusable checks some invariants.
+// TODO(thepudds): will probably delete some of this. Can mostly be ignored for review.
+func doubleCheckNextReusable(v gclinkptr) {
+ // TODO(thepudds): should probably take the spanClass as well to confirm expected
+ // sizeclass match.
+ _, span, objIndex := findObject(uintptr(v), 0, 0)
+ if span == nil {
+ throw("nextReusable: nil span for pointer in free list")
+ }
+ if state := span.state.get(); state != mSpanInUse {
+ throw("nextReusable: span is not in use")
+ }
+ if uintptr(v) < span.base() || uintptr(v) >= span.limit {
+ throw("nextReusable: span is not in range")
+ }
+ if span.objBase(uintptr(v)) != uintptr(v) {
+ print("nextReusable: v=", hex(v), " base=", hex(span.objBase(uintptr(v))), "\n")
+ throw("nextReusable: v is non-base-address for object found on pointer free list")
+ }
+ if span.isFree(objIndex) {
+ throw("nextReusable: pointer on free list is free")
+ }
+
+ const debugReusableEnsureSwept = false
+ if debugReusableEnsureSwept {
+ // Currently disabled.
+ // Note: ensureSwept here alters behavior (not just an invariant check).
+ span.ensureSwept()
+ if span.isFree(objIndex) {
+ throw("nextReusable: pointer on free list is free after ensureSwept")
+ }
+ }
+}
+
+// reusableSize reports if size is a currently supported size for a reusable object.
+func reusableSize(size uintptr) bool {
+ if size < maxTinySize || size > maxSmallSize-mallocHeaderSize {
+ return false
+ }
+ return true
+}
+
// memclrNoHeapPointersChunked repeatedly calls memclrNoHeapPointers
// on chunks of the buffer to be zeroed, with opportunities for preemption
// along the way. memclrNoHeapPointers contains no safepoints and also
diff --git a/src/runtime/malloc_test.go b/src/runtime/malloc_test.go
index bf58947bbc..6285cdaff7 100644
--- a/src/runtime/malloc_test.go
+++ b/src/runtime/malloc_test.go
@@ -16,6 +16,7 @@ import (
"runtime"
. "runtime"
"strings"
+ "sync"
"sync/atomic"
"testing"
"time"
@@ -234,6 +235,275 @@ func TestTinyAllocIssue37262(t *testing.T) {
runtime.Releasem()
}
+// TestFreegc does basic testing of explicit frees.
+func TestFreegc(t *testing.T) {
+ tests := []struct {
+ size string
+ f func(noscan bool) func(*testing.T)
+ noscan bool
+ }{
+ // Types without pointers.
+ {"size=16", testFreegc[[16]byte], true}, // smallest we support currently
+ {"size=17", testFreegc[[17]byte], true},
+ {"size=64", testFreegc[[64]byte], true},
+ {"size=500", testFreegc[[500]byte], true},
+ {"size=512", testFreegc[[512]byte], true},
+ {"size=4096", testFreegc[[4096]byte], true},
+ {"size=32KiB-8", testFreegc[[1<<15 - 8]byte], true}, // max noscan small object for 64-bit
+ }
+
+ // Run the tests twice if not in -short mode or not otherwise saving test time.
+ // First while manually calling runtime.GC to slightly increase isolation (perhaps making
+ // problems more reproducible).
+ for _, tt := range tests {
+ runtime.GC()
+ t.Run(fmt.Sprintf("gc=yes/ptrs=%v/%s", !tt.noscan, tt.size), tt.f(tt.noscan))
+ }
+ runtime.GC()
+
+ if testing.Short() || !RuntimeFreegcEnabled || runtime.Raceenabled {
+ return
+ }
+
+ // Again, but without manually calling runtime.GC in the loop (perhaps less isolation might
+ // trigger problems).
+ for _, tt := range tests {
+ t.Run(fmt.Sprintf("gc=no/ptrs=%v/%s", !tt.noscan, tt.size), tt.f(tt.noscan))
+ }
+ runtime.GC()
+}
+
+func testFreegc[T comparable](noscan bool) func(*testing.T) {
+ // We use stressMultiple to influence the duration of the tests.
+ // When testing freegc changes, stressMultiple can be increased locally
+ // to test longer or in some cases with more goroutines.
+ // It can also be helpful to test with GODEBUG=clobberfree=1 and
+ // with and without doubleCheckMalloc and doubleCheckReusable enabled.
+ stressMultiple := 10
+ if testing.Short() || !RuntimeFreegcEnabled || runtime.Raceenabled {
+ stressMultiple = 1
+ }
+
+ return func(t *testing.T) {
+ alloc := func() *T {
+ // Force heap alloc, plus some light validation of zeroed memory.
+ t.Helper()
+ p := Escape(new(T))
+ var zero T
+ if *p != zero {
+ t.Fatalf("allocator returned non-zero memory: %v", *p)
+ }
+ return p
+ }
+
+ free := func(p *T) {
+ t.Helper()
+ var zero T
+ if *p != zero {
+ t.Fatalf("found non-zero memory before freeing (tests do not modify memory): %v", *p)
+ }
+ runtime.Freegc(unsafe.Pointer(p), unsafe.Sizeof(*p), noscan)
+ }
+
+ t.Run("basic-free", func(t *testing.T) {
+ // Test that freeing a live heap object doesn't crash.
+ for range 100 {
+ p := alloc()
+ free(p)
+ }
+ })
+
+ t.Run("stack-free", func(t *testing.T) {
+ // Test that freeing a stack object doesn't crash.
+ for range 100 {
+ var x [32]byte
+ var y [32]*int
+ runtime.Freegc(unsafe.Pointer(&x), unsafe.Sizeof(x), true) // noscan
+ runtime.Freegc(unsafe.Pointer(&y), unsafe.Sizeof(y), false) // !noscan
+ }
+ })
+
+ // Check our allocations. These tests rely on the
+ // current implementation treating a re-used object
+ // as not adding to the allocation counts seen
+ // by testing.AllocsPerRun. (This is not the desired
+ // long-term behavior, but it is the current behavior and
+ // makes these tests convenient).
+
+ t.Run("allocs-baseline", func(t *testing.T) {
+ // Baseline result without any explicit free.
+ allocs := testing.AllocsPerRun(100, func() {
+ for range 100 {
+ p := alloc()
+ _ = p
+ }
+ })
+ if allocs < 100 {
+ // TODO(thepudds): we get exactly 100 for almost all the tests, but investigate why
+ // ~101 allocs for TestFreegc/ptrs=true/size=32KiB-8.
+ t.Fatalf("expected >=100 allocations, got %v", allocs)
+ }
+ })
+
+ t.Run("allocs-with-free", func(t *testing.T) {
+ // Same allocations, but now using explicit free so that
+ // no allocs get reported. (Again, not the desired long-term behavior).
+ if SizeSpecializedMallocEnabled {
+ t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc")
+ }
+ if !RuntimeFreegcEnabled {
+ t.Skip("skipping alloc tests with runtime.freegc disabled")
+ }
+ allocs := testing.AllocsPerRun(100, func() {
+ for range 100 {
+ p := alloc()
+ free(p)
+ }
+ })
+ if allocs != 0 {
+ t.Fatalf("expected 0 allocations, got %v", allocs)
+ }
+ })
+
+ t.Run("free-multiple", func(t *testing.T) {
+ // Multiple allocations outstanding before explicitly freeing,
+ // but still within the limit of our smallest free list size
+ // so that no allocs are reported. (Again, not long-term behavior).
+ if SizeSpecializedMallocEnabled {
+ t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc")
+ }
+ if !RuntimeFreegcEnabled {
+ t.Skip("skipping alloc tests with runtime.freegc disabled")
+ }
+ const maxOutstanding = 20
+ s := make([]*T, 0, maxOutstanding)
+ allocs := testing.AllocsPerRun(100*stressMultiple, func() {
+ s = s[:0]
+ for range maxOutstanding {
+ p := alloc()
+ s = append(s, p)
+ }
+ for _, p := range s {
+ free(p)
+ }
+ })
+ if allocs != 0 {
+ t.Fatalf("expected 0 allocations, got %v", allocs)
+ }
+ })
+
+ if runtime.GOARCH == "wasm" {
+ // TODO(thepudds): for wasm, double-check if just slow, vs. some test logic problem,
+ // vs. something else. It might have been wasm was slowest with tests that spawn
+ // many goroutines, which might be expected for wasm. This skip might no longer be
+ // needed now that we have tuned test execution time more, or perhaps wasm should just
+ // always run in short mode, which might also let us remove this skip.
+ t.Skip("skipping remaining freegc tests, was timing out on wasm")
+ }
+
+ t.Run("free-many", func(t *testing.T) {
+ // Confirm we are graceful if we have more freed elements at once
+ // than the max free list size.
+ s := make([]*T, 0, 1000)
+ iterations := stressMultiple * stressMultiple // currently 1 or 100 depending on -short
+ for range iterations {
+ s = s[:0]
+ for range 1000 {
+ p := alloc()
+ s = append(s, p)
+ }
+ for _, p := range s {
+ free(p)
+ }
+ }
+ })
+
+ t.Run("duplicate-check", func(t *testing.T) {
+ // A simple duplicate allocation test. We track what should be the set
+ // of live pointers in a map across a series of allocs and frees,
+ // and fail if a live pointer value is returned by an allocation.
+ // TODO: maybe add randomness? allow more live pointers? do across goroutines?
+ live := make(map[uintptr]bool)
+ for i := range 100 * stressMultiple {
+ var s []*T
+ // Alloc 10 times, tracking the live pointer values.
+ for j := range 10 {
+ p := alloc()
+ uptr := uintptr(unsafe.Pointer(p))
+ if live[uptr] {
+ t.Fatalf("TestFreeLive: found duplicate pointer (0x%x). i: %d j: %d", uptr, i, j)
+ }
+ live[uptr] = true
+ s = append(s, p)
+ }
+ // Explicitly free those pointers, removing them from the live map.
+ for k := range s {
+ p := s[k]
+ s[k] = nil
+ uptr := uintptr(unsafe.Pointer(p))
+ free(p)
+ delete(live, uptr)
+ }
+ }
+ })
+
+ t.Run("free-other-goroutine", func(t *testing.T) {
+ // Use explicit free, but the free happens on a different goroutine than the alloc.
+ // This also lightly simulates how the free code sees P migration or flushing
+ // the mcache, assuming we have > 1 P. (Not using testing.AllocsPerRun here).
+ iterations := 10 * stressMultiple * stressMultiple // currently 10 or 1000 depending on -short
+ for _, capacity := range []int{2} {
+ for range iterations {
+ ch := make(chan *T, capacity)
+ var wg sync.WaitGroup
+ for range 2 {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for p := range ch {
+ free(p)
+ }
+ }()
+ }
+ for range 100 {
+ p := alloc()
+ ch <- p
+ }
+ close(ch)
+ wg.Wait()
+ }
+ }
+ })
+
+ t.Run("many-goroutines", func(t *testing.T) {
+ // Allocate across multiple goroutines, freeing on the same goroutine.
+ // TODO: probably remove the duplicate checking here; not that useful.
+ counts := []int{1, 2, 4, 8, 10 * stressMultiple}
+ for _, goroutines := range counts {
+ var wg sync.WaitGroup
+ for range goroutines {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ live := make(map[uintptr]bool)
+ for range 100 * stressMultiple {
+ p := alloc()
+ uptr := uintptr(unsafe.Pointer(p))
+ if live[uptr] {
+ panic("TestFreeLive: found duplicate pointer")
+ }
+ live[uptr] = true
+ free(p)
+ delete(live, uptr)
+ }
+ }()
+ }
+ wg.Wait()
+ }
+ })
+ }
+}
+
func TestPageCacheLeak(t *testing.T) {
defer GOMAXPROCS(GOMAXPROCS(1))
leaked := PageCachePagesLeaked()
@@ -337,6 +607,13 @@ func BenchmarkMalloc16(b *testing.B) {
}
}
+func BenchmarkMalloc32(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ p := new([4]int64)
+ Escape(p)
+ }
+}
+
func BenchmarkMallocTypeInfo8(b *testing.B) {
for i := 0; i < b.N; i++ {
p := new(struct {
@@ -355,6 +632,15 @@ func BenchmarkMallocTypeInfo16(b *testing.B) {
}
}
+func BenchmarkMallocTypeInfo32(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ p := new(struct {
+ p [32 / unsafe.Sizeof(uintptr(0))]*int
+ })
+ Escape(p)
+ }
+}
+
type LargeStruct struct {
x [16][]byte
}
diff --git a/src/runtime/mcache.go b/src/runtime/mcache.go
index cade81031d..82872f1454 100644
--- a/src/runtime/mcache.go
+++ b/src/runtime/mcache.go
@@ -44,7 +44,17 @@ type mcache struct {
// The rest is not accessed on every malloc.
- alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
+ // alloc contains spans to allocate from, indexed by spanClass.
+ alloc [numSpanClasses]*mspan
+
+ // TODO(thepudds): better to interleave alloc and reusableScan/reusableNoscan so that
+ // a single malloc call can often access both in the same cache line for a given spanClass.
+ // It's not interleaved right now in part to have slightly smaller diff, and might be
+ // negligible effect on current microbenchmarks.
+
+ // reusableNoscan contains linked lists of reusable noscan heap objects, indexed by spanClass.
+ // The next pointers are stored in the first word of the heap objects.
+ reusableNoscan [numSpanClasses]gclinkptr
stackcache [_NumStackOrders]stackfreelist
@@ -96,6 +106,7 @@ func allocmcache() *mcache {
c.alloc[i] = &emptymspan
}
c.nextSample = nextSample()
+
return c
}
@@ -153,6 +164,16 @@ func (c *mcache) refill(spc spanClass) {
if s.allocCount != s.nelems {
throw("refill of span with free space remaining")
}
+
+ // TODO(thepudds): we might be able to allow mallocgcTiny to reuse 16 byte objects from spc==5,
+ // but for now, just clear our reusable objects for tinySpanClass.
+ if spc == tinySpanClass {
+ c.reusableNoscan[spc] = 0
+ }
+ if c.reusableNoscan[spc] != 0 {
+ throw("refill of span with reusable pointers remaining on pointer free list")
+ }
+
if s != &emptymspan {
// Mark this span as no longer cached.
if s.sweepgen != mheap_.sweepgen+3 {
@@ -312,6 +333,13 @@ func (c *mcache) releaseAll() {
c.tinyAllocs = 0
memstats.heapStats.release()
+ // Clear the reusable linked lists.
+ // For noscan objects, the nodes of the linked lists are the reusable heap objects themselves,
+ // so we can simply clear the linked list head pointers.
+ // TODO(thepudds): consider having debug logging of a non-empty reusable lists getting cleared,
+ // maybe based on the existing debugReusableLog.
+ clear(c.reusableNoscan[:])
+
// Update heapLive and heapScan.
gcController.update(dHeapLive, scanAlloc)
}
@@ -339,3 +367,25 @@ func (c *mcache) prepareForSweep() {
stackcache_clear(c)
c.flushGen.Store(mheap_.sweepgen) // Synchronizes with gcStart
}
+
+// addReusableNoscan adds a noscan object pointer to the reusable pointer free list
+// for a span class.
+func (c *mcache) addReusableNoscan(spc spanClass, ptr uintptr) {
+ if !runtimeFreegcEnabled {
+ return
+ }
+
+ // Add to the reusable pointers free list.
+ v := gclinkptr(ptr)
+ v.ptr().next = c.reusableNoscan[spc]
+ c.reusableNoscan[spc] = v
+}
+
+// hasReusableNoscan reports whether there is a reusable object available for
+// a noscan spc.
+func (c *mcache) hasReusableNoscan(spc spanClass) bool {
+ if !runtimeFreegcEnabled {
+ return false
+ }
+ return c.reusableNoscan[spc] != 0
+}
diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go
index 08a0057be7..d2ff063b00 100644
--- a/src/runtime/mheap.go
+++ b/src/runtime/mheap.go
@@ -435,7 +435,7 @@ type mspan struct {
// indicating a free object. freeindex is then adjusted so that subsequent scans begin
// just past the newly discovered free object.
//
- // If freeindex == nelems, this span has no free objects.
+ // If freeindex == nelems, this span has no free objects, though might have reusable objects.
//
// allocBits is a bitmap of objects in this span.
// If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
--
cgit v1.3
From 120f1874ef380362cf8b8c4775a327bcd417ff70 Mon Sep 17 00:00:00 2001
From: thepudds
Date: Mon, 3 Nov 2025 16:40:40 -0500
Subject: runtime: add more precise test of assist credit handling for
runtime.freegc
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL adds a better test of assist credit handling when heap objects
are being reused after a runtime.freegc call.
The main approach is bracketing alloc/free pairs with measurements
of the assist credit before and after, and hoping to see a net zero
change in the assist credit.
However, validating the desired behavior is perhaps a bit subtle.
To help stabilize the measurements, we do acquirem in the test code
to avoid being preempted during the measurements to reduce other code's
ability to adjust the assist credit while we are measuring, and
we also reduce GOMAXPROCS to 1.
This test currently does fail if we deliberately introduce bugs
in the runtime.freegc implementation such as if we:
- never adjust the assist credit when reusing an object, or
- always adjust the assist credit when reusing an object, or
- deliberately mishandle internal fragmentation.
The two main cases of current interest for testing runtime.freegc
are when over the course of our bracketed measurements gcBlackenEnable
is either true or false. The test attempts to exercise both of those
case by running the GC continually in the background (which we can see
seems effective based on logging and by how our deliberate bugs fail).
This passes ~10K test executions locally via stress.
A small note to the future: a previous incarnation of this test (circa
patchset 11 of this CL) did not do acquirem but had an approach of
ignoring certain measurements, which also was able to pass ~10K runs
via stress. The current version in this CL is simpler, but
recording the existence of the prior version here in case it is
useful in the future. (Hopefully not.)
Updates #74299
Change-Id: I46c7e0295d125f5884fee0cc3d3d31aedc7e5ff4
Reviewed-on: https://go-review.googlesource.com/c/go/+/717520
Reviewed-by: Michael Knyszek
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
---
src/runtime/export_test.go | 28 ++++++++++++++
src/runtime/malloc_test.go | 93 ++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 117 insertions(+), 4 deletions(-)
(limited to 'src')
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index 48dcf5aa39..8438603b9e 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -644,6 +644,25 @@ func Freegc(p unsafe.Pointer, size uintptr, noscan bool) {
freegc(p, size, noscan)
}
+// Expose gcAssistBytes for the current g for testing.
+func AssistCredit() int64 {
+ assistG := getg()
+ if assistG.m.curg != nil {
+ assistG = assistG.m.curg
+ }
+ return assistG.gcAssistBytes
+}
+
+// Expose gcBlackenEnabled for testing.
+func GcBlackenEnable() bool {
+ // Note we do a non-atomic load here.
+ // Some checks against gcBlackenEnabled (e.g., in mallocgc)
+ // are currently done via non-atomic load for performance reasons,
+ // but other checks are done via atomic load (e.g., in mgcmark.go),
+ // so interpreting this value in a test may be subtle.
+ return gcBlackenEnabled != 0
+}
+
const SizeSpecializedMallocEnabled = sizeSpecializedMallocEnabled
const RuntimeFreegcEnabled = runtimeFreegcEnabled
@@ -1487,6 +1506,15 @@ func Releasem() {
releasem(getg().m)
}
+// GoschedIfBusy is an explicit preemption check to call back
+// into the scheduler. This is useful for tests that run code
+// which spend most of their time as non-preemptible, as it
+// can be placed right after becoming preemptible again to ensure
+// that the scheduler gets a chance to preempt the goroutine.
+func GoschedIfBusy() {
+ goschedIfBusy()
+}
+
type PIController struct {
piController
}
diff --git a/src/runtime/malloc_test.go b/src/runtime/malloc_test.go
index 6285cdaff7..10c20e6c23 100644
--- a/src/runtime/malloc_test.go
+++ b/src/runtime/malloc_test.go
@@ -249,6 +249,7 @@ func TestFreegc(t *testing.T) {
{"size=500", testFreegc[[500]byte], true},
{"size=512", testFreegc[[512]byte], true},
{"size=4096", testFreegc[[4096]byte], true},
+ {"size=20000", testFreegc[[20000]byte], true}, // not power of 2 or spc boundary
{"size=32KiB-8", testFreegc[[1<<15 - 8]byte], true}, // max noscan small object for 64-bit
}
@@ -300,7 +301,7 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
t.Helper()
var zero T
if *p != zero {
- t.Fatalf("found non-zero memory before freeing (tests do not modify memory): %v", *p)
+ t.Fatalf("found non-zero memory before freegc (tests do not modify memory): %v", *p)
}
runtime.Freegc(unsafe.Pointer(p), unsafe.Sizeof(*p), noscan)
}
@@ -405,7 +406,7 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
// Confirm we are graceful if we have more freed elements at once
// than the max free list size.
s := make([]*T, 0, 1000)
- iterations := stressMultiple * stressMultiple // currently 1 or 100 depending on -short
+ iterations := stressMultiple * stressMultiple // currently 1 (-short) or 100
for range iterations {
s = s[:0]
for range 1000 {
@@ -431,7 +432,7 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
p := alloc()
uptr := uintptr(unsafe.Pointer(p))
if live[uptr] {
- t.Fatalf("TestFreeLive: found duplicate pointer (0x%x). i: %d j: %d", uptr, i, j)
+ t.Fatalf("found duplicate pointer (0x%x). i: %d j: %d", uptr, i, j)
}
live[uptr] = true
s = append(s, p)
@@ -451,7 +452,7 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
// Use explicit free, but the free happens on a different goroutine than the alloc.
// This also lightly simulates how the free code sees P migration or flushing
// the mcache, assuming we have > 1 P. (Not using testing.AllocsPerRun here).
- iterations := 10 * stressMultiple * stressMultiple // currently 10 or 1000 depending on -short
+ iterations := 10 * stressMultiple * stressMultiple // currently 10 (-short) or 1000
for _, capacity := range []int{2} {
for range iterations {
ch := make(chan *T, capacity)
@@ -501,6 +502,90 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
wg.Wait()
}
})
+
+ t.Run("assist-credit", func(t *testing.T) {
+ // Allocate and free using the same span class repeatedly while
+ // verifying it results in a net zero change in assist credit.
+ // This helps double-check our manipulation of the assist credit
+ // during mallocgc/freegc, including in cases when there is
+ // internal fragmentation when the requested mallocgc size is
+ // smaller than the size class.
+ //
+ // See https://go.dev/cl/717520 for some additional discussion,
+ // including how we can deliberately cause the test to fail currently
+ // if we purposefully introduce some assist credit bugs.
+ if SizeSpecializedMallocEnabled {
+ // TODO(thepudds): skip this test at this point in the stack; later CL has
+ // integration with sizespecializedmalloc.
+ t.Skip("temporarily skip assist credit test for GOEXPERIMENT=sizespecializedmalloc")
+ }
+ if !RuntimeFreegcEnabled {
+ t.Skip("skipping assist credit test with runtime.freegc disabled")
+ }
+
+ // Use a background goroutine to continuously run the GC.
+ done := make(chan struct{})
+ defer close(done)
+ go func() {
+ for {
+ select {
+ case <-done:
+ return
+ default:
+ runtime.GC()
+ }
+ }
+ }()
+
+ // If making changes related to this test, consider testing locally with
+ // larger counts, like 100K or 1M.
+ counts := []int{1, 2, 10, 100 * stressMultiple}
+ // Dropping down to GOMAXPROCS=1 might help reduce noise.
+ defer GOMAXPROCS(GOMAXPROCS(1))
+ size := int64(unsafe.Sizeof(*new(T)))
+ for _, count := range counts {
+ // Start by forcing a GC to reset this g's assist credit
+ // and perhaps help us get a cleaner measurement of GC cycle count.
+ runtime.GC()
+ for i := range count {
+ // We disable preemption to reduce other code's ability to adjust this g's
+ // assist credit or otherwise change things while we are measuring.
+ Acquirem()
+
+ // We do two allocations per loop, with the second allocation being
+ // the one we measure. The first allocation tries to ensure at least one
+ // reusable object on the mspan's free list when we do our measured allocation.
+ p := alloc()
+ free(p)
+
+ // Now do our primary allocation of interest, bracketed by measurements.
+ // We measure more than we strictly need (to log details in case of a failure).
+ creditStart := AssistCredit()
+ blackenStart := GcBlackenEnable()
+ p = alloc()
+ blackenAfterAlloc := GcBlackenEnable()
+ creditAfterAlloc := AssistCredit()
+ free(p)
+ blackenEnd := GcBlackenEnable()
+ creditEnd := AssistCredit()
+
+ Releasem()
+ GoschedIfBusy()
+
+ delta := creditEnd - creditStart
+ if delta != 0 {
+ t.Logf("assist credit non-zero delta: %d", delta)
+ t.Logf("\t| size: %d i: %d count: %d", size, i, count)
+ t.Logf("\t| credit before: %d credit after: %d", creditStart, creditEnd)
+ t.Logf("\t| alloc delta: %d free delta: %d",
+ creditAfterAlloc-creditStart, creditEnd-creditAfterAlloc)
+ t.Logf("\t| gcBlackenEnable (start / after alloc / end): %v/%v/%v",
+ blackenStart, blackenAfterAlloc, blackenEnd)
+ t.FailNow()
+ }
+ }
+ }
+ })
}
}
--
cgit v1.3
From aea881230dcc640ad730d3759423104074577756 Mon Sep 17 00:00:00 2001
From: Alan Donovan
Date: Fri, 14 Nov 2025 14:59:36 -0500
Subject: std: fix printf("%q", int) mistakes
For #72850
Change-Id: I07e64f05c82a34b1dadb9a72e16f5045e68cbd24
Reviewed-on: https://go-review.googlesource.com/c/go/+/720642
Auto-Submit: Alan Donovan
Reviewed-by: Dmitri Shuralyov
Reviewed-by: Dmitri Shuralyov
LUCI-TryBot-Result: Go LUCI
---
src/cmd/compile/internal/ir/fmt.go | 2 +-
src/cmd/vet/testdata/print/print.go | 2 +-
src/crypto/rsa/equal_test.go | 2 +-
src/crypto/tls/bogo_shim_test.go | 2 +-
src/database/sql/fakedb_test.go | 2 +-
src/fmt/scan_test.go | 2 +-
src/internal/pkgbits/pkgbits_test.go | 2 +-
src/os/os_test.go | 2 +-
src/reflect/all_test.go | 2 +-
9 files changed, 9 insertions(+), 9 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ir/fmt.go b/src/cmd/compile/internal/ir/fmt.go
index ae4ff62652..eb64cce47b 100644
--- a/src/cmd/compile/internal/ir/fmt.go
+++ b/src/cmd/compile/internal/ir/fmt.go
@@ -574,7 +574,7 @@ func exprFmt(n Node, s fmt.State, prec int) {
// Special case for rune constants.
if typ == types.RuneType || typ == types.UntypedRune {
if x, ok := constant.Uint64Val(val); ok && x <= utf8.MaxRune {
- fmt.Fprintf(s, "%q", x)
+ fmt.Fprintf(s, "%q", rune(x))
return
}
}
diff --git a/src/cmd/vet/testdata/print/print.go b/src/cmd/vet/testdata/print/print.go
index 3761da420b..9145c12fb8 100644
--- a/src/cmd/vet/testdata/print/print.go
+++ b/src/cmd/vet/testdata/print/print.go
@@ -75,7 +75,7 @@ func PrintfTests() {
fmt.Printf("%b %b %b %b", 3e9, x, fslice, c)
fmt.Printf("%o %o", 3, i)
fmt.Printf("%p", p)
- fmt.Printf("%q %q %q %q", 3, i, 'x', r)
+ fmt.Printf("%q %q %q", rune(3), 'x', r)
fmt.Printf("%s %s %s", "hi", s, []byte{65})
fmt.Printf("%t %t", true, b)
fmt.Printf("%T %T", 3, i)
diff --git a/src/crypto/rsa/equal_test.go b/src/crypto/rsa/equal_test.go
index 435429c3d1..39a9cdc86c 100644
--- a/src/crypto/rsa/equal_test.go
+++ b/src/crypto/rsa/equal_test.go
@@ -21,7 +21,7 @@ func TestEqual(t *testing.T) {
t.Errorf("public key is not equal to itself: %v", public)
}
if !public.Equal(crypto.Signer(private).Public().(*rsa.PublicKey)) {
- t.Errorf("private.Public() is not Equal to public: %q", public)
+ t.Errorf("private.Public() is not Equal to public: %v", public)
}
if !private.Equal(private) {
t.Errorf("private key is not equal to itself: %v", private)
diff --git a/src/crypto/tls/bogo_shim_test.go b/src/crypto/tls/bogo_shim_test.go
index 8f171d9259..02a943c13c 100644
--- a/src/crypto/tls/bogo_shim_test.go
+++ b/src/crypto/tls/bogo_shim_test.go
@@ -461,7 +461,7 @@ func bogoShim() {
}
if *expectVersion != 0 && cs.Version != uint16(*expectVersion) {
- log.Fatalf("expected ssl version %q, got %q", uint16(*expectVersion), cs.Version)
+ log.Fatalf("expected ssl version %d, got %d", *expectVersion, cs.Version)
}
if *declineALPN && cs.NegotiatedProtocol != "" {
log.Fatal("unexpected ALPN protocol")
diff --git a/src/database/sql/fakedb_test.go b/src/database/sql/fakedb_test.go
index 003e6c6298..e5f0459303 100644
--- a/src/database/sql/fakedb_test.go
+++ b/src/database/sql/fakedb_test.go
@@ -969,7 +969,7 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (
idx := t.columnIndex(wcol.Column)
if idx == -1 {
t.mu.Unlock()
- return nil, fmt.Errorf("fakedb: invalid where clause column %q", wcol)
+ return nil, fmt.Errorf("fakedb: invalid where clause column %v", wcol)
}
tcol := trow.cols[idx]
if bs, ok := tcol.([]byte); ok {
diff --git a/src/fmt/scan_test.go b/src/fmt/scan_test.go
index a4f80c23c2..ff9e4f30ca 100644
--- a/src/fmt/scan_test.go
+++ b/src/fmt/scan_test.go
@@ -998,7 +998,7 @@ func TestScanStateCount(t *testing.T) {
t.Errorf("bad scan rune: %q %q %q should be '1' '2' '➂'", a.rune, b.rune, c.rune)
}
if a.size != 1 || b.size != 1 || c.size != 3 {
- t.Errorf("bad scan size: %q %q %q should be 1 1 3", a.size, b.size, c.size)
+ t.Errorf("bad scan size: %d %d %d should be 1 1 3", a.size, b.size, c.size)
}
}
diff --git a/src/internal/pkgbits/pkgbits_test.go b/src/internal/pkgbits/pkgbits_test.go
index a4755bd35a..f67267189f 100644
--- a/src/internal/pkgbits/pkgbits_test.go
+++ b/src/internal/pkgbits/pkgbits_test.go
@@ -28,7 +28,7 @@ func TestRoundTrip(t *testing.T) {
r := pr.NewDecoder(pkgbits.SectionMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic)
if r.Version() != w.Version() {
- t.Errorf("Expected reader version %q to be the writer version %q", r.Version(), w.Version())
+ t.Errorf("Expected reader version %d to be the writer version %d", r.Version(), w.Version())
}
}
}
diff --git a/src/os/os_test.go b/src/os/os_test.go
index 536734901b..29f2e6d3b2 100644
--- a/src/os/os_test.go
+++ b/src/os/os_test.go
@@ -1192,7 +1192,7 @@ func TestRenameCaseDifference(pt *testing.T) {
}
if dirNamesLen := len(dirNames); dirNamesLen != 1 {
- t.Fatalf("unexpected dirNames len, got %q, want %q", dirNamesLen, 1)
+ t.Fatalf("unexpected dirNames len, got %d, want %d", dirNamesLen, 1)
}
if dirNames[0] != to {
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 8509f00a5e..30ec3fad51 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -6807,7 +6807,7 @@ func TestMakeFuncStackCopy(t *testing.T) {
ValueOf(&concrete).Elem().Set(fn)
x := concrete(nil, 7)
if x != 9 {
- t.Errorf("have %#q want 9", x)
+ t.Errorf("have %d want 9", x)
}
}
--
cgit v1.3
From c3708350a417a3149bf9191878c3ad945063d439 Mon Sep 17 00:00:00 2001
From: David Finkel
Date: Thu, 13 Nov 2025 20:43:11 -0500
Subject: cmd/go: tests: rename git-min-vers->git-sha256
Follow-up cleanup on go.dev/cl/698835. Clarify the tests by replacing
git-min-vers with a more-puposeful git-sha256 verb in the cmd tests.
Updates: #73704
Change-Id: I4361356431d567a6a3bb3a50eadfaa9e3ba37d90
Reviewed-on: https://go-review.googlesource.com/c/go/+/720481
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: Michael Matloob
Reviewed-by: Michael Matloob
---
src/cmd/go/internal/vcweb/script.go | 8 ++++++--
src/cmd/go/scriptconds_test.go | 8 ++++++--
src/cmd/go/testdata/script/README | 4 ++--
src/cmd/go/testdata/script/build_git_sha256_go_get_branch.txt | 2 +-
src/cmd/go/testdata/script/build_git_sha256_moddep.txt | 2 +-
.../go/testdata/script/mod_download_git_bareRepository_sha256.txt | 2 +-
src/cmd/go/testdata/vcstest/git/gitrepo-sha256.txt | 2 +-
7 files changed, 18 insertions(+), 10 deletions(-)
(limited to 'src')
diff --git a/src/cmd/go/internal/vcweb/script.go b/src/cmd/go/internal/vcweb/script.go
index 0856c40677..8fa00b2775 100644
--- a/src/cmd/go/internal/vcweb/script.go
+++ b/src/cmd/go/internal/vcweb/script.go
@@ -44,7 +44,7 @@ func newScriptEngine() *script.Engine {
return script.OnceCondition(summary, func() (bool, error) { return f(), nil })
}
add("bzr", lazyBool("the 'bzr' executable exists and provides the standard CLI", hasWorkingBzr))
- add("git-min-vers", script.PrefixCondition(" indicates a minimum git version", hasAtLeastGitVersion))
+ add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
interrupt := func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }
gracePeriod := 30 * time.Second // arbitrary
@@ -412,10 +412,14 @@ func gitVersion() (string, error) {
return "v" + string(matches[1]), nil
}
-func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) {
+func hasAtLeastGitVersion(minVers string) (bool, error) {
gitVers, gitVersErr := gitVersion()
if gitVersErr != nil {
return false, gitVersErr
}
return semver.Compare(minVers, gitVers) <= 0, nil
}
+
+func gitSupportsSHA256() (bool, error) {
+ return hasAtLeastGitVersion("v2.29")
+}
diff --git a/src/cmd/go/scriptconds_test.go b/src/cmd/go/scriptconds_test.go
index c87c60ad33..4d7a9ac54b 100644
--- a/src/cmd/go/scriptconds_test.go
+++ b/src/cmd/go/scriptconds_test.go
@@ -44,7 +44,7 @@ func scriptConditions(t *testing.T) map[string]script.Cond {
add("case-sensitive", script.OnceCondition("$WORK filesystem is case-sensitive", isCaseSensitive))
add("cc", script.PrefixCondition("go env CC = (ignoring the go/env file)", ccIs))
add("git", lazyBool("the 'git' executable exists and provides the standard CLI", hasWorkingGit))
- add("git-min-vers", script.PrefixCondition(" indicates a minimum git version", hasAtLeastGitVersion))
+ add("git-sha256", script.OnceCondition("the local 'git' version is recent enough to support sha256 object/commit hashes", gitSupportsSHA256))
add("net", script.PrefixCondition("can connect to external network host ", hasNet))
add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
@@ -171,7 +171,7 @@ func gitVersion() (string, error) {
return "v" + string(matches[1]), nil
}
-func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) {
+func hasAtLeastGitVersion(minVers string) (bool, error) {
gitVers, gitVersErr := gitVersion()
if gitVersErr != nil {
return false, gitVersErr
@@ -179,6 +179,10 @@ func hasAtLeastGitVersion(s *script.State, minVers string) (bool, error) {
return semver.Compare(minVers, gitVers) <= 0, nil
}
+func gitSupportsSHA256() (bool, error) {
+ return hasAtLeastGitVersion("v2.29")
+}
+
func hasWorkingBzr() bool {
bzr, err := exec.LookPath("bzr")
if err != nil {
diff --git a/src/cmd/go/testdata/script/README b/src/cmd/go/testdata/script/README
index d4f4c47af7..2b5ab6948b 100644
--- a/src/cmd/go/testdata/script/README
+++ b/src/cmd/go/testdata/script/README
@@ -399,8 +399,8 @@ The available conditions are:
GOOS/GOARCH supports -fuzz with instrumentation
[git]
the 'git' executable exists and provides the standard CLI
-[git-min-vers:*]
- indicates a minimum git version
+[git-sha256]
+ the local 'git' version is recent enough to support sha256 object/commit hashes
[go-builder]
GO_BUILDER_NAME is non-empty
[link]
diff --git a/src/cmd/go/testdata/script/build_git_sha256_go_get_branch.txt b/src/cmd/go/testdata/script/build_git_sha256_go_get_branch.txt
index 0773e08ea5..1e71e25f11 100644
--- a/src/cmd/go/testdata/script/build_git_sha256_go_get_branch.txt
+++ b/src/cmd/go/testdata/script/build_git_sha256_go_get_branch.txt
@@ -1,6 +1,6 @@
[short] skip
[!git] skip
-[!git-min-vers:v2.29] skip
+[!git-sha256] skip
env GOPRIVATE=vcs-test.golang.org
diff --git a/src/cmd/go/testdata/script/build_git_sha256_moddep.txt b/src/cmd/go/testdata/script/build_git_sha256_moddep.txt
index 21a296bd3d..7048e8f2e4 100644
--- a/src/cmd/go/testdata/script/build_git_sha256_moddep.txt
+++ b/src/cmd/go/testdata/script/build_git_sha256_moddep.txt
@@ -1,6 +1,6 @@
[short] skip
[!git] skip
-[!git-min-vers:v2.29] skip
+[!git-sha256] skip
env GOPRIVATE=vcs-test.golang.org
diff --git a/src/cmd/go/testdata/script/mod_download_git_bareRepository_sha256.txt b/src/cmd/go/testdata/script/mod_download_git_bareRepository_sha256.txt
index df772f5c4b..2f391e200e 100644
--- a/src/cmd/go/testdata/script/mod_download_git_bareRepository_sha256.txt
+++ b/src/cmd/go/testdata/script/mod_download_git_bareRepository_sha256.txt
@@ -1,6 +1,6 @@
[short] skip
[!git] skip
-[!git-min-vers:v2.29] skip
+[!git-sha256] skip
# This is a git sha256-mode copy of mod_download_git_bareRepository
diff --git a/src/cmd/go/testdata/vcstest/git/gitrepo-sha256.txt b/src/cmd/go/testdata/vcstest/git/gitrepo-sha256.txt
index 15068a249e..a08e2949d3 100644
--- a/src/cmd/go/testdata/vcstest/git/gitrepo-sha256.txt
+++ b/src/cmd/go/testdata/vcstest/git/gitrepo-sha256.txt
@@ -1,4 +1,4 @@
-[!git-min-vers:v2.29] skip
+[!git-sha256] skip
handle git
--
cgit v1.3
From 50128a21541e3fd712ad717a223aaa109cb86d43 Mon Sep 17 00:00:00 2001
From: thepudds
Date: Sun, 9 Nov 2025 09:24:22 -0500
Subject: runtime: support runtime.freegc in size-specialized mallocs for
noscan objects
This CL is part of a set of CLs that attempt to reduce how much work the
GC must do. See the design in https://go.dev/design/74299-runtime-freegc
This CL updates the smallNoScanStub stub in malloc_stubs.go to reuse
heap objects that have been freed by runtime.freegc calls, and generates
the corresponding size-specialized code in malloc_generated.go.
This CL only adds support in the specialized mallocs for noscan
heap objects (objects without pointers). A later CL handles objects
with pointers.
While we are here, we leave a couple of breadcrumbs in mkmalloc.go on
how to do the generation.
Updates #74299
Change-Id: I2657622601a27211554ee862fce057e101767a70
Reviewed-on: https://go-review.googlesource.com/c/go/+/715761
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
---
src/runtime/_mkmalloc/mkmalloc.go | 3 +-
src/runtime/malloc.go | 11 +-
src/runtime/malloc_generated.go | 651 ++++++++++++++++++++++++++++++++++++++
src/runtime/malloc_stubs.go | 22 +-
src/runtime/malloc_test.go | 16 +-
5 files changed, 693 insertions(+), 10 deletions(-)
(limited to 'src')
diff --git a/src/runtime/_mkmalloc/mkmalloc.go b/src/runtime/_mkmalloc/mkmalloc.go
index 986b0aa9f8..1f040c8861 100644
--- a/src/runtime/_mkmalloc/mkmalloc.go
+++ b/src/runtime/_mkmalloc/mkmalloc.go
@@ -254,7 +254,8 @@ func inline(config generatorConfig) []byte {
}
// Write out the package and import declarations.
- out.WriteString("// Code generated by mkmalloc.go; DO NOT EDIT.\n\n")
+ out.WriteString("// Code generated by mkmalloc.go; DO NOT EDIT.\n")
+ out.WriteString("// See overview in malloc_stubs.go.\n\n")
out.WriteString("package " + f.Name.Name + "\n\n")
for _, importDecl := range importDecls {
out.Write(mustFormatNode(fset, importDecl))
diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go
index 13f5fc3081..d49dacaf68 100644
--- a/src/runtime/malloc.go
+++ b/src/runtime/malloc.go
@@ -1094,6 +1094,8 @@ const sizeSpecializedMallocEnabled = goexperiment.SizeSpecializedMalloc && GOOS
// implementation and the corresponding allocation-related changes: the experiment must be
// enabled, and none of the memory sanitizers should be enabled. We allow the race detector,
// in contrast to sizeSpecializedMallocEnabled.
+// TODO(thepudds): it would be nice to check Valgrind integration, though there are some hints
+// there might not be any canned tests in tree for Go's integration with Valgrind.
const runtimeFreegcEnabled = goexperiment.RuntimeFreegc && !asanenabled && !msanenabled && !valgrindenabled
// Allocate an object of size bytes.
@@ -1966,10 +1968,15 @@ const (
// or roughly when the liveness analysis of the compiler
// would otherwise have determined ptr's object is reclaimable by the GC.
func freegc(ptr unsafe.Pointer, size uintptr, noscan bool) bool {
- if !runtimeFreegcEnabled || sizeSpecializedMallocEnabled || !reusableSize(size) {
- // TODO(thepudds): temporarily disable freegc with SizeSpecializedMalloc until we finish integrating.
+ if !runtimeFreegcEnabled || !reusableSize(size) {
return false
}
+ if sizeSpecializedMallocEnabled && !noscan {
+ // TODO(thepudds): temporarily disable freegc with SizeSpecializedMalloc for pointer types
+ // until we finish integrating.
+ return false
+ }
+
if ptr == nil {
throw("freegc nil")
}
diff --git a/src/runtime/malloc_generated.go b/src/runtime/malloc_generated.go
index 2215dbaddb..5abb61257a 100644
--- a/src/runtime/malloc_generated.go
+++ b/src/runtime/malloc_generated.go
@@ -1,4 +1,5 @@
// Code generated by mkmalloc.go; DO NOT EDIT.
+// See overview in malloc_stubs.go.
package runtime
@@ -6400,6 +6401,32 @@ func mallocgcSmallNoScanSC2(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6497,6 +6524,32 @@ func mallocgcSmallNoScanSC3(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6594,6 +6647,32 @@ func mallocgcSmallNoScanSC4(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6691,6 +6770,32 @@ func mallocgcSmallNoScanSC5(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6788,6 +6893,32 @@ func mallocgcSmallNoScanSC6(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6885,6 +7016,32 @@ func mallocgcSmallNoScanSC7(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -6982,6 +7139,32 @@ func mallocgcSmallNoScanSC8(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7079,6 +7262,32 @@ func mallocgcSmallNoScanSC9(size uintptr, typ *_type, needzero bool) unsafe.Poin
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7176,6 +7385,32 @@ func mallocgcSmallNoScanSC10(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7273,6 +7508,32 @@ func mallocgcSmallNoScanSC11(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7370,6 +7631,32 @@ func mallocgcSmallNoScanSC12(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7467,6 +7754,32 @@ func mallocgcSmallNoScanSC13(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7564,6 +7877,32 @@ func mallocgcSmallNoScanSC14(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7661,6 +8000,32 @@ func mallocgcSmallNoScanSC15(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7758,6 +8123,32 @@ func mallocgcSmallNoScanSC16(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7855,6 +8246,32 @@ func mallocgcSmallNoScanSC17(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -7952,6 +8369,32 @@ func mallocgcSmallNoScanSC18(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8049,6 +8492,32 @@ func mallocgcSmallNoScanSC19(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8146,6 +8615,32 @@ func mallocgcSmallNoScanSC20(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8243,6 +8738,32 @@ func mallocgcSmallNoScanSC21(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8340,6 +8861,32 @@ func mallocgcSmallNoScanSC22(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8437,6 +8984,32 @@ func mallocgcSmallNoScanSC23(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8534,6 +9107,32 @@ func mallocgcSmallNoScanSC24(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8631,6 +9230,32 @@ func mallocgcSmallNoScanSC25(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
@@ -8728,6 +9353,32 @@ func mallocgcSmallNoScanSC26(size uintptr, typ *_type, needzero bool) unsafe.Poi
const spc = spanClass(sizeclass<<1) | spanClass(1)
span := c.alloc[spc]
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+ x := v
+ {
+
+ if valgrindenabled {
+ valgrindMalloc(x, size)
+ }
+
+ if gcBlackenEnabled != 0 && elemsize != 0 {
+ if assistG := getg().m.curg; assistG != nil {
+ assistG.gcAssistBytes -= int64(elemsize - size)
+ }
+ }
+
+ if debug.malloc {
+ postMallocgcDebug(x, elemsize, typ)
+ }
+ return x
+ }
+
+ }
+
var nextFreeFastResult gclinkptr
if span.allocCache != 0 {
theBit := sys.TrailingZeros64(span.allocCache)
diff --git a/src/runtime/malloc_stubs.go b/src/runtime/malloc_stubs.go
index 224746f3d4..e9752956b8 100644
--- a/src/runtime/malloc_stubs.go
+++ b/src/runtime/malloc_stubs.go
@@ -7,6 +7,8 @@
// to produce a full mallocgc function that's specialized for a span class
// or specific size in the case of the tiny allocator.
//
+// To generate the specialized mallocgc functions, do 'go run .' inside runtime/_mkmalloc.
+//
// To assemble a mallocgc function, the mallocStub function is cloned, and the call to
// inlinedMalloc is replaced with the inlined body of smallScanNoHeaderStub,
// smallNoScanStub or tinyStub, depending on the parameters being specialized.
@@ -71,7 +73,8 @@ func mallocStub(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
}
}
- // Assist the GC if needed.
+ // Assist the GC if needed. (On the reuse path, we currently compensate for this;
+ // changes here might require changes there.)
if gcBlackenEnabled != 0 {
deductAssistCredit(size)
}
@@ -242,6 +245,23 @@ func smallNoScanStub(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, u
c := getMCache(mp)
const spc = spanClass(sizeclass<<1) | spanClass(noscanint_)
span := c.alloc[spc]
+
+ // First, check for a reusable object.
+ if runtimeFreegcEnabled && c.hasReusableNoscan(spc) {
+ // We have a reusable object, use it.
+ v := mallocgcSmallNoscanReuse(c, span, spc, elemsize, needzero)
+ mp.mallocing = 0
+ releasem(mp)
+
+ // TODO(thepudds): note that the generated return path is essentially duplicated
+ // by the generator. For example, see the two postMallocgcDebug calls and
+ // related duplicated code on the return path currently in the generated
+ // mallocgcSmallNoScanSC2 function. One set of those correspond to this
+ // return here. We might be able to de-duplicate the generated return path
+ // by updating the generator, perhaps by jumping to a shared return or similar.
+ return v, elemsize
+ }
+
v := nextFreeFastStub(span)
if v == 0 {
v, span, checkGCTrigger = c.nextFree(spc)
diff --git a/src/runtime/malloc_test.go b/src/runtime/malloc_test.go
index 10c20e6c23..97cf0eed54 100644
--- a/src/runtime/malloc_test.go
+++ b/src/runtime/malloc_test.go
@@ -349,8 +349,10 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
t.Run("allocs-with-free", func(t *testing.T) {
// Same allocations, but now using explicit free so that
// no allocs get reported. (Again, not the desired long-term behavior).
- if SizeSpecializedMallocEnabled {
- t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc")
+ if SizeSpecializedMallocEnabled && !noscan {
+ // TODO(thepudds): skip at this point in the stack for size-specialized malloc
+ // with !noscan. Additional integration with sizespecializedmalloc is in a later CL.
+ t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc for pointer types")
}
if !RuntimeFreegcEnabled {
t.Skip("skipping alloc tests with runtime.freegc disabled")
@@ -370,8 +372,10 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
// Multiple allocations outstanding before explicitly freeing,
// but still within the limit of our smallest free list size
// so that no allocs are reported. (Again, not long-term behavior).
- if SizeSpecializedMallocEnabled {
- t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc")
+ if SizeSpecializedMallocEnabled && !noscan {
+ // TODO(thepudds): skip at this point in the stack for size-specialized malloc
+ // with !noscan. Additional integration with sizespecializedmalloc is in a later CL.
+ t.Skip("temporarily skipping alloc tests for GOEXPERIMENT=sizespecializedmalloc for pointer types")
}
if !RuntimeFreegcEnabled {
t.Skip("skipping alloc tests with runtime.freegc disabled")
@@ -514,10 +518,10 @@ func testFreegc[T comparable](noscan bool) func(*testing.T) {
// See https://go.dev/cl/717520 for some additional discussion,
// including how we can deliberately cause the test to fail currently
// if we purposefully introduce some assist credit bugs.
- if SizeSpecializedMallocEnabled {
+ if SizeSpecializedMallocEnabled && !noscan {
// TODO(thepudds): skip this test at this point in the stack; later CL has
// integration with sizespecializedmalloc.
- t.Skip("temporarily skip assist credit test for GOEXPERIMENT=sizespecializedmalloc")
+ t.Skip("temporarily skip assist credit tests for GOEXPERIMENT=sizespecializedmalloc for pointer types")
}
if !RuntimeFreegcEnabled {
t.Skip("skipping assist credit test with runtime.freegc disabled")
--
cgit v1.3
From 410ef44f0054a9ab20a901895edb7db5a4d0aad7 Mon Sep 17 00:00:00 2001
From: Alan Donovan
Date: Tue, 11 Nov 2025 14:56:59 -0500
Subject: cmd: update x/tools to 59ff18c
$ go get golang.org/x/tools@59ff18c
$ GOWORK=off go mod tidy
$ GOWORK=off go mod vendor
This implies golang.org/x/sys@v0.38.0, for which I have also
updated src/go.mod for consistency.
I also upgraded x/mod@3f03020 to bring in some fixes to
code that go vet would otherwise have flagged in this CL.
This brings in a number of fixes and improvements to the analysis
tools within cmd/fix, which I will apply to std and cmd presently.
For golang/go#71859
Change-Id: I56c49e7ab28eb6ba55408a3721e9a898ee3067a1
Reviewed-on: https://go-review.googlesource.com/c/go/+/719760
Auto-Submit: Alan Donovan
Reviewed-by: Dmitri Shuralyov
Reviewed-by: Dmitri Shuralyov
LUCI-TryBot-Result: Go LUCI
---
src/cmd/go.mod | 10 +-
src/cmd/go.sum | 20 +-
src/cmd/vendor/golang.org/x/mod/modfile/print.go | 2 +-
src/cmd/vendor/golang.org/x/mod/modfile/read.go | 4 +-
src/cmd/vendor/golang.org/x/mod/modfile/rule.go | 8 +-
src/cmd/vendor/golang.org/x/mod/module/module.go | 6 +-
src/cmd/vendor/golang.org/x/mod/semver/semver.go | 4 +-
src/cmd/vendor/golang.org/x/mod/sumdb/cache.go | 6 +-
src/cmd/vendor/golang.org/x/mod/sumdb/client.go | 6 +-
src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go | 22 +-
src/cmd/vendor/golang.org/x/mod/sumdb/server.go | 3 +-
src/cmd/vendor/golang.org/x/mod/sumdb/test.go | 2 +-
src/cmd/vendor/golang.org/x/mod/sumdb/tlog/note.go | 4 +-
src/cmd/vendor/golang.org/x/mod/sumdb/tlog/tlog.go | 4 +-
src/cmd/vendor/golang.org/x/mod/zip/zip.go | 2 +-
.../vendor/golang.org/x/sync/errgroup/errgroup.go | 2 +-
src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh | 2 +
.../vendor/golang.org/x/sys/unix/syscall_linux.go | 6 +
.../vendor/golang.org/x/sys/unix/zerrors_linux.go | 359 ++++++++++
.../vendor/golang.org/x/sys/unix/zsyscall_linux.go | 10 +
.../vendor/golang.org/x/sys/unix/ztypes_linux.go | 31 +
.../golang.org/x/sys/windows/syscall_windows.go | 15 +
.../golang.org/x/sys/windows/types_windows.go | 76 ++
.../golang.org/x/sys/windows/zsyscall_windows.go | 37 +
.../vendor/golang.org/x/telemetry/codereview.cfg | 1 +
.../golang.org/x/tools/go/analysis/diagnostic.go | 5 +-
.../go/analysis/internal/analysisflags/fix.go | 284 --------
.../go/analysis/internal/analysisflags/flags.go | 159 +----
.../go/analysis/internal/analysisflags/url.go | 33 -
.../x/tools/go/analysis/passes/appends/appends.go | 4 +-
.../x/tools/go/analysis/passes/asmdecl/asmdecl.go | 4 +-
.../x/tools/go/analysis/passes/assign/assign.go | 4 +-
.../x/tools/go/analysis/passes/atomic/atomic.go | 4 +-
.../tools/go/analysis/passes/buildtag/buildtag.go | 4 +-
.../x/tools/go/analysis/passes/cgocall/cgocall.go | 4 +-
.../tools/go/analysis/passes/copylock/copylock.go | 4 +-
.../tools/go/analysis/passes/ctrlflow/ctrlflow.go | 90 +--
.../x/tools/go/analysis/passes/defers/defers.go | 4 +-
.../go/analysis/passes/directive/directive.go | 4 +-
.../tools/go/analysis/passes/errorsas/errorsas.go | 2 +-
.../analysis/passes/framepointer/framepointer.go | 4 +-
.../tools/go/analysis/passes/hostport/hostport.go | 2 +-
.../go/analysis/passes/ifaceassert/ifaceassert.go | 4 +-
.../x/tools/go/analysis/passes/inline/gofix.go | 537 --------------
.../x/tools/go/analysis/passes/inline/inline.go | 578 ++++++++++++++++
.../x/tools/go/analysis/passes/inspect/inspect.go | 2 +-
.../internal/ctrlflowinternal/ctrlflowinternal.go | 17 +
.../go/analysis/passes/loopclosure/loopclosure.go | 43 +-
.../go/analysis/passes/lostcancel/lostcancel.go | 8 +-
.../x/tools/go/analysis/passes/modernize/any.go | 24 +-
.../x/tools/go/analysis/passes/modernize/bloop.go | 36 +-
.../x/tools/go/analysis/passes/modernize/doc.go | 46 +-
.../go/analysis/passes/modernize/errorsastype.go | 16 +-
.../go/analysis/passes/modernize/fmtappendf.go | 13 +-
.../x/tools/go/analysis/passes/modernize/forvar.go | 99 +--
.../x/tools/go/analysis/passes/modernize/maps.go | 22 +-
.../x/tools/go/analysis/passes/modernize/minmax.go | 14 +-
.../go/analysis/passes/modernize/modernize.go | 49 +-
.../tools/go/analysis/passes/modernize/newexpr.go | 38 +-
.../tools/go/analysis/passes/modernize/omitzero.go | 49 +-
.../go/analysis/passes/modernize/plusbuild.go | 7 +-
.../tools/go/analysis/passes/modernize/rangeint.go | 23 +-
.../tools/go/analysis/passes/modernize/reflect.go | 12 +-
.../x/tools/go/analysis/passes/modernize/slices.go | 25 +-
.../go/analysis/passes/modernize/slicescontains.go | 52 +-
.../go/analysis/passes/modernize/slicesdelete.go | 25 +-
.../go/analysis/passes/modernize/sortslice.go | 13 +-
.../go/analysis/passes/modernize/stditerators.go | 67 +-
.../go/analysis/passes/modernize/stringsbuilder.go | 10 +-
.../go/analysis/passes/modernize/stringscut.go | 580 ++++++++++++++++
.../analysis/passes/modernize/stringscutprefix.go | 20 +-
.../go/analysis/passes/modernize/stringsseq.go | 19 +-
.../go/analysis/passes/modernize/testingcontext.go | 13 +-
.../go/analysis/passes/modernize/waitgroup.go | 13 +-
.../x/tools/go/analysis/passes/nilfunc/nilfunc.go | 4 +-
.../x/tools/go/analysis/passes/printf/printf.go | 24 +-
.../x/tools/go/analysis/passes/printf/types.go | 16 +-
.../go/analysis/passes/sigchanyzer/sigchanyzer.go | 4 +-
.../x/tools/go/analysis/passes/slog/slog.go | 8 +-
.../go/analysis/passes/stdmethods/stdmethods.go | 10 +-
.../go/analysis/passes/stringintconv/string.go | 4 +-
.../passes/testinggoroutine/testinggoroutine.go | 4 +-
.../go/analysis/passes/testinggoroutine/util.go | 4 +-
.../x/tools/go/analysis/passes/tests/tests.go | 7 +-
.../go/analysis/passes/timeformat/timeformat.go | 6 +-
.../go/analysis/passes/unmarshal/unmarshal.go | 8 +-
.../go/analysis/passes/unreachable/unreachable.go | 4 +-
.../go/analysis/passes/unsafeptr/unsafeptr.go | 4 +-
.../analysis/passes/unusedresult/unusedresult.go | 11 +-
.../go/analysis/passes/waitgroup/waitgroup.go | 4 +-
.../x/tools/go/analysis/unitchecker/unitchecker.go | 28 +-
.../golang.org/x/tools/go/ast/inspector/cursor.go | 17 +-
.../vendor/golang.org/x/tools/go/cfg/builder.go | 16 +-
src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go | 54 +-
.../x/tools/go/types/objectpath/objectpath.go | 4 +-
.../golang.org/x/tools/go/types/typeutil/map.go | 3 +-
.../x/tools/internal/analysis/analyzerutil/doc.go | 6 +
.../internal/analysis/analyzerutil/extractdoc.go | 113 +++
.../internal/analysis/analyzerutil/readfile.go | 30 +
.../internal/analysis/analyzerutil/version.go | 42 ++
.../x/tools/internal/analysis/driverutil/fix.go | 449 ++++++++++++
.../x/tools/internal/analysis/driverutil/print.go | 161 +++++
.../tools/internal/analysis/driverutil/readfile.go | 43 ++
.../x/tools/internal/analysis/driverutil/url.go | 33 +
.../internal/analysis/driverutil/validatefix.go | 118 ++++
.../tools/internal/analysis/typeindex/typeindex.go | 33 +
.../x/tools/internal/analysisinternal/analysis.go | 180 -----
.../tools/internal/analysisinternal/extractdoc.go | 113 ---
.../analysisinternal/generated/generated.go | 41 --
.../analysisinternal/typeindex/typeindex.go | 33 -
.../x/tools/internal/astutil/free/free.go | 418 +++++++++++
.../x/tools/internal/astutil/stringlit.go | 8 +-
.../golang.org/x/tools/internal/astutil/util.go | 146 +++-
.../x/tools/internal/cfginternal/cfginternal.go | 16 +
.../golang.org/x/tools/internal/diff/lcs/old.go | 4 -
.../golang.org/x/tools/internal/facts/imports.go | 36 +-
.../x/tools/internal/goplsexport/export.go | 1 +
.../golang.org/x/tools/internal/refactor/delete.go | 245 ++++---
.../x/tools/internal/refactor/inline/callee.go | 35 +-
.../x/tools/internal/refactor/inline/free.go | 382 ----------
.../x/tools/internal/refactor/inline/inline.go | 72 +-
.../x/tools/internal/refactor/inline/util.go | 1 +
.../x/tools/internal/refactor/refactor.go | 5 +
.../golang.org/x/tools/internal/stdlib/deps.go | 770 ++++++++++++---------
.../golang.org/x/tools/internal/stdlib/import.go | 8 +
.../golang.org/x/tools/internal/stdlib/manifest.go | 41 +-
.../x/tools/internal/typeparams/normalize.go | 6 +-
.../x/tools/internal/typesinternal/element.go | 4 +-
.../x/tools/internal/typesinternal/isnamed.go | 4 +-
.../x/tools/internal/typesinternal/qualifier.go | 2 +-
.../x/tools/internal/typesinternal/zerovalue.go | 14 +-
.../x/tools/internal/versions/features.go | 6 +-
src/cmd/vendor/modules.txt | 19 +-
src/go.mod | 2 +-
src/go.sum | 4 +-
src/vendor/golang.org/x/sys/cpu/cpu.go | 3 +
src/vendor/golang.org/x/sys/cpu/cpu_arm64.go | 20 +-
src/vendor/golang.org/x/sys/cpu/cpu_arm64.s | 19 +-
src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go | 1 +
src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go | 1 +
.../golang.org/x/sys/cpu/cpu_netbsd_arm64.go | 2 +-
.../golang.org/x/sys/cpu/cpu_openbsd_arm64.go | 2 +-
src/vendor/modules.txt | 2 +-
143 files changed, 4795 insertions(+), 2874 deletions(-)
create mode 100644 src/cmd/vendor/golang.org/x/telemetry/codereview.cfg
delete mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/url.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/gofix.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal/ctrlflowinternal.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/doc.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/extractdoc.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/readfile.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/version.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/readfile.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/url.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/validatefix.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysis/typeindex/typeindex.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/generated/generated.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/astutil/free/free.go
create mode 100644 src/cmd/vendor/golang.org/x/tools/internal/cfginternal/cfginternal.go
delete mode 100644 src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/free.go
(limited to 'src')
diff --git a/src/cmd/go.mod b/src/cmd/go.mod
index 42d510c34f..090b2c943f 100644
--- a/src/cmd/go.mod
+++ b/src/cmd/go.mod
@@ -6,12 +6,12 @@ require (
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938
golang.org/x/build v0.0.0-20250806225920-b7c66c047964
- golang.org/x/mod v0.29.0
- golang.org/x/sync v0.17.0
- golang.org/x/sys v0.37.0
- golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8
+ golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526
+ golang.org/x/sync v0.18.0
+ golang.org/x/sys v0.38.0
+ golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54
golang.org/x/term v0.34.0
- golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5
+ golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883
)
require (
diff --git a/src/cmd/go.sum b/src/cmd/go.sum
index 0a09e6e401..e4955f224b 100644
--- a/src/cmd/go.sum
+++ b/src/cmd/go.sum
@@ -10,19 +10,19 @@ golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 h1:VJ182b/ajNehMFRltVfCh
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY=
-golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
-golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
+golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 h1:LPpBM4CGUFMC47OqgAr2YIUxEUjH1Ur+D3KR/1LiuuQ=
+golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
+golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5 h1:cz7f45KGWAtyIrz6bm45Gc+lw8beIxBSW3EQh4Bwbg4=
-golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883 h1:aeO0AW8d+a+5+hNQx9f4J5egD89zftrY2x42KGQjLzI=
+golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/print.go b/src/cmd/vendor/golang.org/x/mod/modfile/print.go
index 2a0123d4b9..48dbd82aec 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/print.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/print.go
@@ -33,7 +33,7 @@ type printer struct {
}
// printf prints to the buffer.
-func (p *printer) printf(format string, args ...interface{}) {
+func (p *printer) printf(format string, args ...any) {
fmt.Fprintf(p, format, args...)
}
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/read.go b/src/cmd/vendor/golang.org/x/mod/modfile/read.go
index 2d7486804f..504a2f1df6 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/read.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/read.go
@@ -94,7 +94,7 @@ func (x *FileSyntax) Span() (start, end Position) {
// line, the new line is added at the end of the block containing hint,
// extracting hint into a new block if it is not yet in one.
//
-// If the hint is non-nil buts its first token does not match,
+// If the hint is non-nil but its first token does not match,
// the new line is added after the block containing hint
// (or hint itself, if not in a block).
//
@@ -600,7 +600,7 @@ func (in *input) readToken() {
// Checked all punctuation. Must be identifier token.
if c := in.peekRune(); !isIdent(c) {
- in.Error(fmt.Sprintf("unexpected input character %#q", c))
+ in.Error(fmt.Sprintf("unexpected input character %#q", rune(c)))
}
// Scan over identifier.
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
index a86ee4fd82..c5b8305de7 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
@@ -368,7 +368,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
Err: err,
})
}
- errorf := func(format string, args ...interface{}) {
+ errorf := func(format string, args ...any) {
wrapError(fmt.Errorf(format, args...))
}
@@ -574,7 +574,7 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V
Err: err,
}
}
- errorf := func(format string, args ...interface{}) *Error {
+ errorf := func(format string, args ...any) *Error {
return wrapError(fmt.Errorf(format, args...))
}
@@ -685,7 +685,7 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string,
Err: err,
})
}
- errorf := func(format string, args ...interface{}) {
+ errorf := func(format string, args ...any) {
wrapError(fmt.Errorf(format, args...))
}
@@ -1594,7 +1594,7 @@ func (f *File) AddRetract(vi VersionInterval, rationale string) error {
r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
}
if rationale != "" {
- for _, line := range strings.Split(rationale, "\n") {
+ for line := range strings.SplitSeq(rationale, "\n") {
com := Comment{Token: "// " + line}
r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
}
diff --git a/src/cmd/vendor/golang.org/x/mod/module/module.go b/src/cmd/vendor/golang.org/x/mod/module/module.go
index 16e1aa7ab4..739c13f48f 100644
--- a/src/cmd/vendor/golang.org/x/mod/module/module.go
+++ b/src/cmd/vendor/golang.org/x/mod/module/module.go
@@ -261,7 +261,7 @@ func modPathOK(r rune) bool {
// importPathOK reports whether r can appear in a package import path element.
//
-// Import paths are intermediate between module paths and file paths: we allow
+// Import paths are intermediate between module paths and file paths: we
// disallow characters that would be confusing or ambiguous as arguments to
// 'go get' (such as '@' and ' ' ), but allow certain characters that are
// otherwise-unambiguous on the command line and historically used for some
@@ -802,8 +802,8 @@ func MatchPrefixPatterns(globs, target string) bool {
for globs != "" {
// Extract next non-empty glob in comma-separated list.
var glob string
- if i := strings.Index(globs, ","); i >= 0 {
- glob, globs = globs[:i], globs[i+1:]
+ if before, after, ok := strings.Cut(globs, ","); ok {
+ glob, globs = before, after
} else {
glob, globs = globs, ""
}
diff --git a/src/cmd/vendor/golang.org/x/mod/semver/semver.go b/src/cmd/vendor/golang.org/x/mod/semver/semver.go
index 628f8fd687..824b282c83 100644
--- a/src/cmd/vendor/golang.org/x/mod/semver/semver.go
+++ b/src/cmd/vendor/golang.org/x/mod/semver/semver.go
@@ -45,8 +45,8 @@ func IsValid(v string) bool {
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
-// Two semantic versions compare equal only if their canonical formattings
-// are identical strings.
+// Two semantic versions compare equal only if their canonical formatting
+// is an identical string.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/cache.go b/src/cmd/vendor/golang.org/x/mod/sumdb/cache.go
index 629e591f42..749a80dfa4 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/cache.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/cache.go
@@ -20,13 +20,13 @@ type parCache struct {
type cacheEntry struct {
done uint32
mu sync.Mutex
- result interface{}
+ result any
}
// Do calls the function f if and only if Do is being called for the first time with this key.
// No call to Do with a given key returns until the one call to f returns.
// Do returns the value returned by the one call to f.
-func (c *parCache) Do(key interface{}, f func() interface{}) interface{} {
+func (c *parCache) Do(key any, f func() any) any {
entryIface, ok := c.m.Load(key)
if !ok {
entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
@@ -46,7 +46,7 @@ func (c *parCache) Do(key interface{}, f func() interface{}) interface{} {
// Get returns the cached result associated with key.
// It returns nil if there is no such result.
// If the result for key is being computed, Get does not wait for the computation to finish.
-func (c *parCache) Get(key interface{}) interface{} {
+func (c *parCache) Get(key any) any {
entryIface, ok := c.m.Load(key)
if !ok {
return nil
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/client.go b/src/cmd/vendor/golang.org/x/mod/sumdb/client.go
index 04dbdfe46a..f926eda1bb 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/client.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/client.go
@@ -244,7 +244,7 @@ func (c *Client) Lookup(path, vers string) (lines []string, err error) {
data []byte
err error
}
- result := c.record.Do(file, func() interface{} {
+ result := c.record.Do(file, func() any {
// Try the on-disk cache, or else get from web.
writeCache := false
data, err := c.ops.ReadCache(file)
@@ -284,7 +284,7 @@ func (c *Client) Lookup(path, vers string) (lines []string, err error) {
// (with or without /go.mod).
prefix := path + " " + vers + " "
var hashes []string
- for _, line := range strings.Split(string(result.data), "\n") {
+ for line := range strings.SplitSeq(string(result.data), "\n") {
if strings.HasPrefix(line, prefix) {
hashes = append(hashes, line)
}
@@ -552,7 +552,7 @@ func (c *Client) readTile(tile tlog.Tile) ([]byte, error) {
err error
}
- result := c.tileCache.Do(tile, func() interface{} {
+ result := c.tileCache.Do(tile, func() any {
// Try the requested tile in on-disk cache.
data, err := c.ops.ReadCache(c.tileCacheKey(tile))
if err == nil {
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go b/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go
index db9865c317..8b2b25278d 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/note/note.go
@@ -240,8 +240,8 @@ func isValidName(name string) bool {
// NewVerifier construct a new [Verifier] from an encoded verifier key.
func NewVerifier(vkey string) (Verifier, error) {
- name, vkey := chop(vkey, "+")
- hash16, key64 := chop(vkey, "+")
+ name, vkey, _ := strings.Cut(vkey, "+")
+ hash16, key64, _ := strings.Cut(vkey, "+")
hash, err1 := strconv.ParseUint(hash16, 16, 32)
key, err2 := base64.StdEncoding.DecodeString(key64)
if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
@@ -276,12 +276,8 @@ func NewVerifier(vkey string) (Verifier, error) {
// chop chops s at the first instance of sep, if any,
// and returns the text before and after sep.
// If sep is not present, chop returns before is s and after is empty.
-func chop(s, sep string) (before, after string) {
- i := strings.Index(s, sep)
- if i < 0 {
- return s, ""
- }
- return s[:i], s[i+len(sep):]
+func chop(s, sep string) (before, after string, ok bool) {
+ return strings.Cut(s, sep)
}
// verifier is a trivial Verifier implementation.
@@ -297,10 +293,10 @@ func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) }
// NewSigner constructs a new [Signer] from an encoded signer key.
func NewSigner(skey string) (Signer, error) {
- priv1, skey := chop(skey, "+")
- priv2, skey := chop(skey, "+")
- name, skey := chop(skey, "+")
- hash16, key64 := chop(skey, "+")
+ priv1, skey, _ := strings.Cut(skey, "+")
+ priv2, skey, _ := strings.Cut(skey, "+")
+ name, skey, _ := strings.Cut(skey, "+")
+ hash16, key64, _ := strings.Cut(skey, "+")
hash, err1 := strconv.ParseUint(hash16, 16, 32)
key, err2 := base64.StdEncoding.DecodeString(key64)
if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
@@ -557,7 +553,7 @@ func Open(msg []byte, known Verifiers) (*Note, error) {
return nil, errMalformedNote
}
line = line[len(sigPrefix):]
- name, b64 := chop(string(line), " ")
+ name, b64, _ := chop(string(line), " ")
sig, err := base64.StdEncoding.DecodeString(b64)
if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 {
return nil, errMalformedNote
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/server.go b/src/cmd/vendor/golang.org/x/mod/sumdb/server.go
index 216a2562c2..2433c939d7 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/server.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/server.go
@@ -76,8 +76,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
return
}
- i := strings.Index(mod, "@")
- escPath, escVers := mod[:i], mod[i+1:]
+ escPath, escVers, _ := strings.Cut(mod, "@")
path, err := module.UnescapePath(escPath)
if err != nil {
reportError(w, err)
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/test.go b/src/cmd/vendor/golang.org/x/mod/sumdb/test.go
index fb772452d9..0868bef5fc 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/test.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/test.go
@@ -66,7 +66,7 @@ func (s *TestServer) ReadRecords(ctx context.Context, id, n int64) ([][]byte, er
defer s.mu.Unlock()
var list [][]byte
- for i := int64(0); i < n; i++ {
+ for i := range n {
if id+i >= int64(len(s.records)) {
return nil, fmt.Errorf("missing records")
}
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/note.go b/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/note.go
index fc6d5fa0a3..1ea765a54f 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/note.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/note.go
@@ -35,7 +35,7 @@ type Tree struct {
// A future backwards-incompatible encoding would use a different
// first line (for example, "go.sum database tree v2").
func FormatTree(tree Tree) []byte {
- return []byte(fmt.Sprintf("go.sum database tree\n%d\n%s\n", tree.N, tree.Hash))
+ return fmt.Appendf(nil, "go.sum database tree\n%d\n%s\n", tree.N, tree.Hash)
}
var errMalformedTree = errors.New("malformed tree note")
@@ -87,7 +87,7 @@ func FormatRecord(id int64, text []byte) (msg []byte, err error) {
if !isValidRecordText(text) {
return nil, errMalformedRecord
}
- msg = []byte(fmt.Sprintf("%d\n", id))
+ msg = fmt.Appendf(nil, "%d\n", id)
msg = append(msg, text...)
msg = append(msg, '\n')
return msg, nil
diff --git a/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/tlog.go b/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/tlog.go
index f7ea753832..480b5eff5a 100644
--- a/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/tlog.go
+++ b/src/cmd/vendor/golang.org/x/mod/sumdb/tlog/tlog.go
@@ -194,7 +194,7 @@ func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) {
// and consumes a hash from an adjacent subtree.
m := int(bits.TrailingZeros64(uint64(n + 1)))
indexes := make([]int64, m)
- for i := 0; i < m; i++ {
+ for i := range m {
// We arrange indexes in sorted order.
// Note that n>>i is always odd.
indexes[m-1-i] = StoredHashIndex(i, n>>uint(i)-1)
@@ -210,7 +210,7 @@ func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) {
}
// Build new hashes.
- for i := 0; i < m; i++ {
+ for i := range m {
h = NodeHash(old[m-1-i], h)
hashes = append(hashes, h)
}
diff --git a/src/cmd/vendor/golang.org/x/mod/zip/zip.go b/src/cmd/vendor/golang.org/x/mod/zip/zip.go
index 3673db4997..48363ceb72 100644
--- a/src/cmd/vendor/golang.org/x/mod/zip/zip.go
+++ b/src/cmd/vendor/golang.org/x/mod/zip/zip.go
@@ -780,7 +780,7 @@ func (fi dataFileInfo) Size() int64 { return int64(len(fi.f.data)) }
func (fi dataFileInfo) Mode() os.FileMode { return 0644 }
func (fi dataFileInfo) ModTime() time.Time { return time.Time{} }
func (fi dataFileInfo) IsDir() bool { return false }
-func (fi dataFileInfo) Sys() interface{} { return nil }
+func (fi dataFileInfo) Sys() any { return nil }
// isVendoredPackage attempts to report whether the given filename is contained
// in a package whose import path contains (but does not end with) the component
diff --git a/src/cmd/vendor/golang.org/x/sync/errgroup/errgroup.go b/src/cmd/vendor/golang.org/x/sync/errgroup/errgroup.go
index 1d8cffae8c..2f45dbc86e 100644
--- a/src/cmd/vendor/golang.org/x/sync/errgroup/errgroup.go
+++ b/src/cmd/vendor/golang.org/x/sync/errgroup/errgroup.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
-// cancelation for groups of goroutines working on subtasks of a common task.
+// cancellation for groups of goroutines working on subtasks of a common task.
//
// [errgroup.Group] is related to [sync.WaitGroup] but adds handling of tasks
// returning errors.
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh b/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh
index d1c8b2640e..42517077c4 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh
+++ b/src/cmd/vendor/golang.org/x/sys/unix/mkerrors.sh
@@ -226,6 +226,7 @@ struct ltchars {
#include
#include
#include
+#include
#include
#include
#include
@@ -529,6 +530,7 @@ ccflags="$@"
$2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ ||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ ||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ ||
+ $2 ~ /^(DT|EI|ELF|EV|NN|NT|PF|SHF|SHN|SHT|STB|STT|VER)_/ ||
$2 ~ /^O?XTABS$/ ||
$2 ~ /^TC[IO](ON|OFF)$/ ||
$2 ~ /^IN_/ ||
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/syscall_linux.go b/src/cmd/vendor/golang.org/x/sys/unix/syscall_linux.go
index 9439af961d..06c0eea6fb 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/syscall_linux.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/syscall_linux.go
@@ -2643,3 +2643,9 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
//sys Mseal(b []byte, flags uint) (err error)
+
+//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY
+
+func SetMemPolicy(mode int, mask *CPUSet) error {
+ return setMemPolicy(mode, mask, _CPU_SETSIZE)
+}
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go
index b6db27d937..d0a75da572 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zerrors_linux.go
@@ -853,20 +853,86 @@ const (
DM_VERSION_MAJOR = 0x4
DM_VERSION_MINOR = 0x32
DM_VERSION_PATCHLEVEL = 0x0
+ DT_ADDRRNGHI = 0x6ffffeff
+ DT_ADDRRNGLO = 0x6ffffe00
DT_BLK = 0x6
DT_CHR = 0x2
+ DT_DEBUG = 0x15
DT_DIR = 0x4
+ DT_ENCODING = 0x20
DT_FIFO = 0x1
+ DT_FINI = 0xd
+ DT_FLAGS_1 = 0x6ffffffb
+ DT_GNU_HASH = 0x6ffffef5
+ DT_HASH = 0x4
+ DT_HIOS = 0x6ffff000
+ DT_HIPROC = 0x7fffffff
+ DT_INIT = 0xc
+ DT_JMPREL = 0x17
DT_LNK = 0xa
+ DT_LOOS = 0x6000000d
+ DT_LOPROC = 0x70000000
+ DT_NEEDED = 0x1
+ DT_NULL = 0x0
+ DT_PLTGOT = 0x3
+ DT_PLTREL = 0x14
+ DT_PLTRELSZ = 0x2
DT_REG = 0x8
+ DT_REL = 0x11
+ DT_RELA = 0x7
+ DT_RELACOUNT = 0x6ffffff9
+ DT_RELAENT = 0x9
+ DT_RELASZ = 0x8
+ DT_RELCOUNT = 0x6ffffffa
+ DT_RELENT = 0x13
+ DT_RELSZ = 0x12
+ DT_RPATH = 0xf
DT_SOCK = 0xc
+ DT_SONAME = 0xe
+ DT_STRSZ = 0xa
+ DT_STRTAB = 0x5
+ DT_SYMBOLIC = 0x10
+ DT_SYMENT = 0xb
+ DT_SYMTAB = 0x6
+ DT_TEXTREL = 0x16
DT_UNKNOWN = 0x0
+ DT_VALRNGHI = 0x6ffffdff
+ DT_VALRNGLO = 0x6ffffd00
+ DT_VERDEF = 0x6ffffffc
+ DT_VERDEFNUM = 0x6ffffffd
+ DT_VERNEED = 0x6ffffffe
+ DT_VERNEEDNUM = 0x6fffffff
+ DT_VERSYM = 0x6ffffff0
DT_WHT = 0xe
ECHO = 0x8
ECRYPTFS_SUPER_MAGIC = 0xf15f
EFD_SEMAPHORE = 0x1
EFIVARFS_MAGIC = 0xde5e81e4
EFS_SUPER_MAGIC = 0x414a53
+ EI_CLASS = 0x4
+ EI_DATA = 0x5
+ EI_MAG0 = 0x0
+ EI_MAG1 = 0x1
+ EI_MAG2 = 0x2
+ EI_MAG3 = 0x3
+ EI_NIDENT = 0x10
+ EI_OSABI = 0x7
+ EI_PAD = 0x8
+ EI_VERSION = 0x6
+ ELFCLASS32 = 0x1
+ ELFCLASS64 = 0x2
+ ELFCLASSNONE = 0x0
+ ELFCLASSNUM = 0x3
+ ELFDATA2LSB = 0x1
+ ELFDATA2MSB = 0x2
+ ELFDATANONE = 0x0
+ ELFMAG = "\177ELF"
+ ELFMAG0 = 0x7f
+ ELFMAG1 = 'E'
+ ELFMAG2 = 'L'
+ ELFMAG3 = 'F'
+ ELFOSABI_LINUX = 0x3
+ ELFOSABI_NONE = 0x0
EM_386 = 0x3
EM_486 = 0x6
EM_68K = 0x4
@@ -1152,14 +1218,24 @@ const (
ETH_P_WCCP = 0x883e
ETH_P_X25 = 0x805
ETH_P_XDSA = 0xf8
+ ET_CORE = 0x4
+ ET_DYN = 0x3
+ ET_EXEC = 0x2
+ ET_HIPROC = 0xffff
+ ET_LOPROC = 0xff00
+ ET_NONE = 0x0
+ ET_REL = 0x1
EV_ABS = 0x3
EV_CNT = 0x20
+ EV_CURRENT = 0x1
EV_FF = 0x15
EV_FF_STATUS = 0x17
EV_KEY = 0x1
EV_LED = 0x11
EV_MAX = 0x1f
EV_MSC = 0x4
+ EV_NONE = 0x0
+ EV_NUM = 0x2
EV_PWR = 0x16
EV_REL = 0x2
EV_REP = 0x14
@@ -2276,7 +2352,167 @@ const (
NLM_F_REPLACE = 0x100
NLM_F_REQUEST = 0x1
NLM_F_ROOT = 0x100
+ NN_386_IOPERM = "LINUX"
+ NN_386_TLS = "LINUX"
+ NN_ARC_V2 = "LINUX"
+ NN_ARM_FPMR = "LINUX"
+ NN_ARM_GCS = "LINUX"
+ NN_ARM_HW_BREAK = "LINUX"
+ NN_ARM_HW_WATCH = "LINUX"
+ NN_ARM_PACA_KEYS = "LINUX"
+ NN_ARM_PACG_KEYS = "LINUX"
+ NN_ARM_PAC_ENABLED_KEYS = "LINUX"
+ NN_ARM_PAC_MASK = "LINUX"
+ NN_ARM_POE = "LINUX"
+ NN_ARM_SSVE = "LINUX"
+ NN_ARM_SVE = "LINUX"
+ NN_ARM_SYSTEM_CALL = "LINUX"
+ NN_ARM_TAGGED_ADDR_CTRL = "LINUX"
+ NN_ARM_TLS = "LINUX"
+ NN_ARM_VFP = "LINUX"
+ NN_ARM_ZA = "LINUX"
+ NN_ARM_ZT = "LINUX"
+ NN_AUXV = "CORE"
+ NN_FILE = "CORE"
+ NN_GNU_PROPERTY_TYPE_0 = "GNU"
+ NN_LOONGARCH_CPUCFG = "LINUX"
+ NN_LOONGARCH_CSR = "LINUX"
+ NN_LOONGARCH_HW_BREAK = "LINUX"
+ NN_LOONGARCH_HW_WATCH = "LINUX"
+ NN_LOONGARCH_LASX = "LINUX"
+ NN_LOONGARCH_LBT = "LINUX"
+ NN_LOONGARCH_LSX = "LINUX"
+ NN_MIPS_DSP = "LINUX"
+ NN_MIPS_FP_MODE = "LINUX"
+ NN_MIPS_MSA = "LINUX"
+ NN_PPC_DEXCR = "LINUX"
+ NN_PPC_DSCR = "LINUX"
+ NN_PPC_EBB = "LINUX"
+ NN_PPC_HASHKEYR = "LINUX"
+ NN_PPC_PKEY = "LINUX"
+ NN_PPC_PMU = "LINUX"
+ NN_PPC_PPR = "LINUX"
+ NN_PPC_SPE = "LINUX"
+ NN_PPC_TAR = "LINUX"
+ NN_PPC_TM_CDSCR = "LINUX"
+ NN_PPC_TM_CFPR = "LINUX"
+ NN_PPC_TM_CGPR = "LINUX"
+ NN_PPC_TM_CPPR = "LINUX"
+ NN_PPC_TM_CTAR = "LINUX"
+ NN_PPC_TM_CVMX = "LINUX"
+ NN_PPC_TM_CVSX = "LINUX"
+ NN_PPC_TM_SPR = "LINUX"
+ NN_PPC_VMX = "LINUX"
+ NN_PPC_VSX = "LINUX"
+ NN_PRFPREG = "CORE"
+ NN_PRPSINFO = "CORE"
+ NN_PRSTATUS = "CORE"
+ NN_PRXFPREG = "LINUX"
+ NN_RISCV_CSR = "LINUX"
+ NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
+ NN_RISCV_VECTOR = "LINUX"
+ NN_S390_CTRS = "LINUX"
+ NN_S390_GS_BC = "LINUX"
+ NN_S390_GS_CB = "LINUX"
+ NN_S390_HIGH_GPRS = "LINUX"
+ NN_S390_LAST_BREAK = "LINUX"
+ NN_S390_PREFIX = "LINUX"
+ NN_S390_PV_CPU_DATA = "LINUX"
+ NN_S390_RI_CB = "LINUX"
+ NN_S390_SYSTEM_CALL = "LINUX"
+ NN_S390_TDB = "LINUX"
+ NN_S390_TIMER = "LINUX"
+ NN_S390_TODCMP = "LINUX"
+ NN_S390_TODPREG = "LINUX"
+ NN_S390_VXRS_HIGH = "LINUX"
+ NN_S390_VXRS_LOW = "LINUX"
+ NN_SIGINFO = "CORE"
+ NN_TASKSTRUCT = "CORE"
+ NN_VMCOREDD = "LINUX"
+ NN_X86_SHSTK = "LINUX"
+ NN_X86_XSAVE_LAYOUT = "LINUX"
+ NN_X86_XSTATE = "LINUX"
NSFS_MAGIC = 0x6e736673
+ NT_386_IOPERM = 0x201
+ NT_386_TLS = 0x200
+ NT_ARC_V2 = 0x600
+ NT_ARM_FPMR = 0x40e
+ NT_ARM_GCS = 0x410
+ NT_ARM_HW_BREAK = 0x402
+ NT_ARM_HW_WATCH = 0x403
+ NT_ARM_PACA_KEYS = 0x407
+ NT_ARM_PACG_KEYS = 0x408
+ NT_ARM_PAC_ENABLED_KEYS = 0x40a
+ NT_ARM_PAC_MASK = 0x406
+ NT_ARM_POE = 0x40f
+ NT_ARM_SSVE = 0x40b
+ NT_ARM_SVE = 0x405
+ NT_ARM_SYSTEM_CALL = 0x404
+ NT_ARM_TAGGED_ADDR_CTRL = 0x409
+ NT_ARM_TLS = 0x401
+ NT_ARM_VFP = 0x400
+ NT_ARM_ZA = 0x40c
+ NT_ARM_ZT = 0x40d
+ NT_AUXV = 0x6
+ NT_FILE = 0x46494c45
+ NT_GNU_PROPERTY_TYPE_0 = 0x5
+ NT_LOONGARCH_CPUCFG = 0xa00
+ NT_LOONGARCH_CSR = 0xa01
+ NT_LOONGARCH_HW_BREAK = 0xa05
+ NT_LOONGARCH_HW_WATCH = 0xa06
+ NT_LOONGARCH_LASX = 0xa03
+ NT_LOONGARCH_LBT = 0xa04
+ NT_LOONGARCH_LSX = 0xa02
+ NT_MIPS_DSP = 0x800
+ NT_MIPS_FP_MODE = 0x801
+ NT_MIPS_MSA = 0x802
+ NT_PPC_DEXCR = 0x111
+ NT_PPC_DSCR = 0x105
+ NT_PPC_EBB = 0x106
+ NT_PPC_HASHKEYR = 0x112
+ NT_PPC_PKEY = 0x110
+ NT_PPC_PMU = 0x107
+ NT_PPC_PPR = 0x104
+ NT_PPC_SPE = 0x101
+ NT_PPC_TAR = 0x103
+ NT_PPC_TM_CDSCR = 0x10f
+ NT_PPC_TM_CFPR = 0x109
+ NT_PPC_TM_CGPR = 0x108
+ NT_PPC_TM_CPPR = 0x10e
+ NT_PPC_TM_CTAR = 0x10d
+ NT_PPC_TM_CVMX = 0x10a
+ NT_PPC_TM_CVSX = 0x10b
+ NT_PPC_TM_SPR = 0x10c
+ NT_PPC_VMX = 0x100
+ NT_PPC_VSX = 0x102
+ NT_PRFPREG = 0x2
+ NT_PRPSINFO = 0x3
+ NT_PRSTATUS = 0x1
+ NT_PRXFPREG = 0x46e62b7f
+ NT_RISCV_CSR = 0x900
+ NT_RISCV_TAGGED_ADDR_CTRL = 0x902
+ NT_RISCV_VECTOR = 0x901
+ NT_S390_CTRS = 0x304
+ NT_S390_GS_BC = 0x30c
+ NT_S390_GS_CB = 0x30b
+ NT_S390_HIGH_GPRS = 0x300
+ NT_S390_LAST_BREAK = 0x306
+ NT_S390_PREFIX = 0x305
+ NT_S390_PV_CPU_DATA = 0x30e
+ NT_S390_RI_CB = 0x30d
+ NT_S390_SYSTEM_CALL = 0x307
+ NT_S390_TDB = 0x308
+ NT_S390_TIMER = 0x301
+ NT_S390_TODCMP = 0x302
+ NT_S390_TODPREG = 0x303
+ NT_S390_VXRS_HIGH = 0x30a
+ NT_S390_VXRS_LOW = 0x309
+ NT_SIGINFO = 0x53494749
+ NT_TASKSTRUCT = 0x4
+ NT_VMCOREDD = 0x700
+ NT_X86_SHSTK = 0x204
+ NT_X86_XSAVE_LAYOUT = 0x205
+ NT_X86_XSTATE = 0x202
OCFS2_SUPER_MAGIC = 0x7461636f
OCRNL = 0x8
OFDEL = 0x80
@@ -2463,6 +2699,59 @@ const (
PERF_RECORD_MISC_USER = 0x2
PERF_SAMPLE_BRANCH_PLM_ALL = 0x7
PERF_SAMPLE_WEIGHT_TYPE = 0x1004000
+ PF_ALG = 0x26
+ PF_APPLETALK = 0x5
+ PF_ASH = 0x12
+ PF_ATMPVC = 0x8
+ PF_ATMSVC = 0x14
+ PF_AX25 = 0x3
+ PF_BLUETOOTH = 0x1f
+ PF_BRIDGE = 0x7
+ PF_CAIF = 0x25
+ PF_CAN = 0x1d
+ PF_DECnet = 0xc
+ PF_ECONET = 0x13
+ PF_FILE = 0x1
+ PF_IB = 0x1b
+ PF_IEEE802154 = 0x24
+ PF_INET = 0x2
+ PF_INET6 = 0xa
+ PF_IPX = 0x4
+ PF_IRDA = 0x17
+ PF_ISDN = 0x22
+ PF_IUCV = 0x20
+ PF_KCM = 0x29
+ PF_KEY = 0xf
+ PF_LLC = 0x1a
+ PF_LOCAL = 0x1
+ PF_MAX = 0x2e
+ PF_MCTP = 0x2d
+ PF_MPLS = 0x1c
+ PF_NETBEUI = 0xd
+ PF_NETLINK = 0x10
+ PF_NETROM = 0x6
+ PF_NFC = 0x27
+ PF_PACKET = 0x11
+ PF_PHONET = 0x23
+ PF_PPPOX = 0x18
+ PF_QIPCRTR = 0x2a
+ PF_R = 0x4
+ PF_RDS = 0x15
+ PF_ROSE = 0xb
+ PF_ROUTE = 0x10
+ PF_RXRPC = 0x21
+ PF_SECURITY = 0xe
+ PF_SMC = 0x2b
+ PF_SNA = 0x16
+ PF_TIPC = 0x1e
+ PF_UNIX = 0x1
+ PF_UNSPEC = 0x0
+ PF_VSOCK = 0x28
+ PF_W = 0x2
+ PF_WANPIPE = 0x19
+ PF_X = 0x1
+ PF_X25 = 0x9
+ PF_XDP = 0x2c
PID_FS_MAGIC = 0x50494446
PIPEFS_MAGIC = 0x50495045
PPPIOCGNPMODE = 0xc008744c
@@ -2758,6 +3047,23 @@ const (
PTRACE_SYSCALL_INFO_NONE = 0x0
PTRACE_SYSCALL_INFO_SECCOMP = 0x3
PTRACE_TRACEME = 0x0
+ PT_AARCH64_MEMTAG_MTE = 0x70000002
+ PT_DYNAMIC = 0x2
+ PT_GNU_EH_FRAME = 0x6474e550
+ PT_GNU_PROPERTY = 0x6474e553
+ PT_GNU_RELRO = 0x6474e552
+ PT_GNU_STACK = 0x6474e551
+ PT_HIOS = 0x6fffffff
+ PT_HIPROC = 0x7fffffff
+ PT_INTERP = 0x3
+ PT_LOAD = 0x1
+ PT_LOOS = 0x60000000
+ PT_LOPROC = 0x70000000
+ PT_NOTE = 0x4
+ PT_NULL = 0x0
+ PT_PHDR = 0x6
+ PT_SHLIB = 0x5
+ PT_TLS = 0x7
P_ALL = 0x0
P_PGID = 0x2
P_PID = 0x1
@@ -3091,6 +3397,47 @@ const (
SEEK_MAX = 0x4
SEEK_SET = 0x0
SELINUX_MAGIC = 0xf97cff8c
+ SHF_ALLOC = 0x2
+ SHF_EXCLUDE = 0x8000000
+ SHF_EXECINSTR = 0x4
+ SHF_GROUP = 0x200
+ SHF_INFO_LINK = 0x40
+ SHF_LINK_ORDER = 0x80
+ SHF_MASKOS = 0xff00000
+ SHF_MASKPROC = 0xf0000000
+ SHF_MERGE = 0x10
+ SHF_ORDERED = 0x4000000
+ SHF_OS_NONCONFORMING = 0x100
+ SHF_RELA_LIVEPATCH = 0x100000
+ SHF_RO_AFTER_INIT = 0x200000
+ SHF_STRINGS = 0x20
+ SHF_TLS = 0x400
+ SHF_WRITE = 0x1
+ SHN_ABS = 0xfff1
+ SHN_COMMON = 0xfff2
+ SHN_HIPROC = 0xff1f
+ SHN_HIRESERVE = 0xffff
+ SHN_LIVEPATCH = 0xff20
+ SHN_LOPROC = 0xff00
+ SHN_LORESERVE = 0xff00
+ SHN_UNDEF = 0x0
+ SHT_DYNAMIC = 0x6
+ SHT_DYNSYM = 0xb
+ SHT_HASH = 0x5
+ SHT_HIPROC = 0x7fffffff
+ SHT_HIUSER = 0xffffffff
+ SHT_LOPROC = 0x70000000
+ SHT_LOUSER = 0x80000000
+ SHT_NOBITS = 0x8
+ SHT_NOTE = 0x7
+ SHT_NULL = 0x0
+ SHT_NUM = 0xc
+ SHT_PROGBITS = 0x1
+ SHT_REL = 0x9
+ SHT_RELA = 0x4
+ SHT_SHLIB = 0xa
+ SHT_STRTAB = 0x3
+ SHT_SYMTAB = 0x2
SHUT_RD = 0x0
SHUT_RDWR = 0x2
SHUT_WR = 0x1
@@ -3317,6 +3664,16 @@ const (
STATX_UID = 0x8
STATX_WRITE_ATOMIC = 0x10000
STATX__RESERVED = 0x80000000
+ STB_GLOBAL = 0x1
+ STB_LOCAL = 0x0
+ STB_WEAK = 0x2
+ STT_COMMON = 0x5
+ STT_FILE = 0x4
+ STT_FUNC = 0x2
+ STT_NOTYPE = 0x0
+ STT_OBJECT = 0x1
+ STT_SECTION = 0x3
+ STT_TLS = 0x6
SYNC_FILE_RANGE_WAIT_AFTER = 0x4
SYNC_FILE_RANGE_WAIT_BEFORE = 0x1
SYNC_FILE_RANGE_WRITE = 0x2
@@ -3553,6 +3910,8 @@ const (
UTIME_OMIT = 0x3ffffffe
V9FS_MAGIC = 0x1021997
VERASE = 0x2
+ VER_FLG_BASE = 0x1
+ VER_FLG_WEAK = 0x2
VINTR = 0x0
VKILL = 0x3
VLNEXT = 0xf
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_linux.go
index 5cc1e8eb2f..8935d10a31 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_linux.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_linux.go
@@ -2238,3 +2238,13 @@ func Mseal(b []byte, flags uint) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func setMemPolicy(mode int, mask *CPUSet, size int) (err error) {
+ _, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), uintptr(size))
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/ztypes_linux.go b/src/cmd/vendor/golang.org/x/sys/unix/ztypes_linux.go
index 944e75a11c..c1a4670171 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/ztypes_linux.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/ztypes_linux.go
@@ -3590,6 +3590,8 @@ type Nhmsg struct {
Flags uint32
}
+const SizeofNhmsg = 0x8
+
type NexthopGrp struct {
Id uint32
Weight uint8
@@ -3597,6 +3599,8 @@ type NexthopGrp struct {
Resvd2 uint16
}
+const SizeofNexthopGrp = 0x8
+
const (
NHA_UNSPEC = 0x0
NHA_ID = 0x1
@@ -6332,3 +6336,30 @@ type SockDiagReq struct {
}
const RTM_NEWNVLAN = 0x70
+
+const (
+ MPOL_BIND = 0x2
+ MPOL_DEFAULT = 0x0
+ MPOL_F_ADDR = 0x2
+ MPOL_F_MEMS_ALLOWED = 0x4
+ MPOL_F_MOF = 0x8
+ MPOL_F_MORON = 0x10
+ MPOL_F_NODE = 0x1
+ MPOL_F_NUMA_BALANCING = 0x2000
+ MPOL_F_RELATIVE_NODES = 0x4000
+ MPOL_F_SHARED = 0x1
+ MPOL_F_STATIC_NODES = 0x8000
+ MPOL_INTERLEAVE = 0x3
+ MPOL_LOCAL = 0x4
+ MPOL_MAX = 0x7
+ MPOL_MF_INTERNAL = 0x10
+ MPOL_MF_LAZY = 0x8
+ MPOL_MF_MOVE_ALL = 0x4
+ MPOL_MF_MOVE = 0x2
+ MPOL_MF_STRICT = 0x1
+ MPOL_MF_VALID = 0x7
+ MPOL_MODE_FLAGS = 0xe000
+ MPOL_PREFERRED = 0x1
+ MPOL_PREFERRED_MANY = 0x5
+ MPOL_WEIGHTED_INTERLEAVE = 0x6
+)
diff --git a/src/cmd/vendor/golang.org/x/sys/windows/syscall_windows.go b/src/cmd/vendor/golang.org/x/sys/windows/syscall_windows.go
index bd51337306..69439df2a4 100644
--- a/src/cmd/vendor/golang.org/x/sys/windows/syscall_windows.go
+++ b/src/cmd/vendor/golang.org/x/sys/windows/syscall_windows.go
@@ -892,8 +892,12 @@ const socket_error = uintptr(^uint32(0))
//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
//sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx
//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex
+//sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2
+//sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2
//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry
+//sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable
//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange
+//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2
//sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange
//sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2
@@ -916,6 +920,17 @@ type RawSockaddrInet6 struct {
Scope_id uint32
}
+// RawSockaddrInet is a union that contains an IPv4, an IPv6 address, or an address family. See
+// https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_inet.
+//
+// A [*RawSockaddrInet] may be converted to a [*RawSockaddrInet4] or [*RawSockaddrInet6] using
+// unsafe, depending on the address family.
+type RawSockaddrInet struct {
+ Family uint16
+ Port uint16
+ Data [6]uint32
+}
+
type RawSockaddr struct {
Family uint16
Data [14]int8
diff --git a/src/cmd/vendor/golang.org/x/sys/windows/types_windows.go b/src/cmd/vendor/golang.org/x/sys/windows/types_windows.go
index 358be3c7f5..6e4f50eb48 100644
--- a/src/cmd/vendor/golang.org/x/sys/windows/types_windows.go
+++ b/src/cmd/vendor/golang.org/x/sys/windows/types_windows.go
@@ -2320,6 +2320,82 @@ type MibIfRow2 struct {
OutQLen uint64
}
+// IP_ADDRESS_PREFIX stores an IP address prefix. See
+// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix.
+type IpAddressPrefix struct {
+ Prefix RawSockaddrInet
+ PrefixLength uint8
+}
+
+// NL_ROUTE_ORIGIN enumeration from nldef.h or
+// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_origin.
+const (
+ NlroManual = 0
+ NlroWellKnown = 1
+ NlroDHCP = 2
+ NlroRouterAdvertisement = 3
+ Nlro6to4 = 4
+)
+
+// NL_ROUTE_ORIGIN enumeration from nldef.h or
+// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_protocol.
+const (
+ MIB_IPPROTO_OTHER = 1
+ MIB_IPPROTO_LOCAL = 2
+ MIB_IPPROTO_NETMGMT = 3
+ MIB_IPPROTO_ICMP = 4
+ MIB_IPPROTO_EGP = 5
+ MIB_IPPROTO_GGP = 6
+ MIB_IPPROTO_HELLO = 7
+ MIB_IPPROTO_RIP = 8
+ MIB_IPPROTO_IS_IS = 9
+ MIB_IPPROTO_ES_IS = 10
+ MIB_IPPROTO_CISCO = 11
+ MIB_IPPROTO_BBN = 12
+ MIB_IPPROTO_OSPF = 13
+ MIB_IPPROTO_BGP = 14
+ MIB_IPPROTO_IDPR = 15
+ MIB_IPPROTO_EIGRP = 16
+ MIB_IPPROTO_DVMRP = 17
+ MIB_IPPROTO_RPL = 18
+ MIB_IPPROTO_DHCP = 19
+ MIB_IPPROTO_NT_AUTOSTATIC = 10002
+ MIB_IPPROTO_NT_STATIC = 10006
+ MIB_IPPROTO_NT_STATIC_NON_DOD = 10007
+)
+
+// MIB_IPFORWARD_ROW2 stores information about an IP route entry. See
+// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2.
+type MibIpForwardRow2 struct {
+ InterfaceLuid uint64
+ InterfaceIndex uint32
+ DestinationPrefix IpAddressPrefix
+ NextHop RawSockaddrInet
+ SitePrefixLength uint8
+ ValidLifetime uint32
+ PreferredLifetime uint32
+ Metric uint32
+ Protocol uint32
+ Loopback uint8
+ AutoconfigureAddress uint8
+ Publish uint8
+ Immortal uint8
+ Age uint32
+ Origin uint32
+}
+
+// MIB_IPFORWARD_TABLE2 contains a table of IP route entries. See
+// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_table2.
+type MibIpForwardTable2 struct {
+ NumEntries uint32
+ Table [1]MibIpForwardRow2
+}
+
+// Rows returns the IP route entries in the table.
+func (t *MibIpForwardTable2) Rows() []MibIpForwardRow2 {
+ return unsafe.Slice(&t.Table[0], t.NumEntries)
+}
+
// MIB_UNICASTIPADDRESS_ROW stores information about a unicast IP address. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_row.
type MibUnicastIpAddressRow struct {
diff --git a/src/cmd/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/src/cmd/vendor/golang.org/x/sys/windows/zsyscall_windows.go
index 426151a019..f25b7308a1 100644
--- a/src/cmd/vendor/golang.org/x/sys/windows/zsyscall_windows.go
+++ b/src/cmd/vendor/golang.org/x/sys/windows/zsyscall_windows.go
@@ -182,13 +182,17 @@ var (
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2")
+ procFreeMibTable = modiphlpapi.NewProc("FreeMibTable")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo")
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
+ procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
+ procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2")
procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
+ procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2")
procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
procAddDllDirectory = modkernel32.NewProc("AddDllDirectory")
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
@@ -1624,6 +1628,11 @@ func CancelMibChangeNotify2(notificationHandle Handle) (errcode error) {
return
}
+func FreeMibTable(memory unsafe.Pointer) {
+ syscall.SyscallN(procFreeMibTable.Addr(), uintptr(memory))
+ return
+}
+
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetAdaptersAddresses.Addr(), uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)))
if r0 != 0 {
@@ -1664,6 +1673,22 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) {
return
}
+func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) {
+ r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row)))
+ if r0 != 0 {
+ errcode = syscall.Errno(r0)
+ }
+ return
+}
+
+func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) {
+ r0, _, _ := syscall.SyscallN(procGetIpForwardTable2.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
+ if r0 != 0 {
+ errcode = syscall.Errno(r0)
+ }
+ return
+}
+
func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
@@ -1684,6 +1709,18 @@ func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsa
return
}
+func NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
+ var _p0 uint32
+ if initialNotification {
+ _p0 = 1
+ }
+ r0, _, _ := syscall.SyscallN(procNotifyRouteChange2.Addr(), uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)))
+ if r0 != 0 {
+ errcode = syscall.Errno(r0)
+ }
+ return
+}
+
func NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {
diff --git a/src/cmd/vendor/golang.org/x/telemetry/codereview.cfg b/src/cmd/vendor/golang.org/x/telemetry/codereview.cfg
new file mode 100644
index 0000000000..3f8b14b64e
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/telemetry/codereview.cfg
@@ -0,0 +1 @@
+issuerepo: golang/go
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/diagnostic.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/diagnostic.go
index f6118bec64..527540c62c 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/diagnostic.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/diagnostic.go
@@ -33,8 +33,9 @@ type Diagnostic struct {
URL string
// SuggestedFixes is an optional list of fixes to address the
- // problem described by the diagnostic. Each one represents
- // an alternative strategy; at most one may be applied.
+ // problem described by the diagnostic. Each one represents an
+ // alternative strategy, and should have a distinct and
+ // descriptive message; at most one may be applied.
//
// Fixes for different diagnostics should be treated as
// independent changes to the same baseline file state,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go
deleted file mode 100644
index 43a456a457..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/fix.go
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright 2018 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package analysisflags
-
-// This file defines the -fix logic common to unitchecker and
-// {single,multi}checker.
-
-import (
- "fmt"
- "go/format"
- "go/token"
- "log"
- "maps"
- "os"
- "sort"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/diff"
-)
-
-// FixAction abstracts a checker action (running one analyzer on one
-// package) for the purposes of applying its diagnostics' fixes.
-type FixAction struct {
- Name string // e.g. "analyzer@package"
- FileSet *token.FileSet
- ReadFileFunc analysisinternal.ReadFileFunc
- Diagnostics []analysis.Diagnostic
-}
-
-// ApplyFixes attempts to apply the first suggested fix associated
-// with each diagnostic reported by the specified actions.
-// All fixes must have been validated by [analysisinternal.ValidateFixes].
-//
-// Each fix is treated as an independent change; fixes are merged in
-// an arbitrary deterministic order as if by a three-way diff tool
-// such as the UNIX diff3 command or 'git merge'. Any fix that cannot be
-// cleanly merged is discarded, in which case the final summary tells
-// the user to re-run the tool.
-// TODO(adonovan): make the checker tool re-run the analysis itself.
-//
-// When the same file is analyzed as a member of both a primary
-// package "p" and a test-augmented package "p [p.test]", there may be
-// duplicate diagnostics and fixes. One set of fixes will be applied
-// and the other will be discarded; but re-running the tool may then
-// show zero fixes, which may cause the confused user to wonder what
-// happened to the other ones.
-// TODO(adonovan): consider pre-filtering completely identical fixes.
-//
-// A common reason for overlapping fixes is duplicate additions of the
-// same import. The merge algorithm may often cleanly resolve such
-// fixes, coalescing identical edits, but the merge may sometimes be
-// confused by nearby changes.
-//
-// Even when merging succeeds, there is no guarantee that the
-// composition of the two fixes is semantically correct. Coalescing
-// identical edits is appropriate for imports, but not for, say,
-// increments to a counter variable; the correct resolution in that
-// case might be to increment it twice. Or consider two fixes that
-// each delete the penultimate reference to an import or local
-// variable: each fix is sound individually, and they may be textually
-// distant from each other, but when both are applied, the program is
-// no longer valid because it has an unreferenced import or local
-// variable.
-// TODO(adonovan): investigate replacing the final "gofmt" step with a
-// formatter that applies the unused-import deletion logic of
-// "goimports".
-//
-// Merging depends on both the order of fixes and they order of edits
-// within them. For example, if three fixes add import "a" twice and
-// import "b" once, the two imports of "a" may be combined if they
-// appear in order [a, a, b], or not if they appear as [a, b, a].
-// TODO(adonovan): investigate an algebraic approach to imports;
-// that is, for fixes to Go source files, convert changes within the
-// import(...) portion of the file into semantic edits, compose those
-// edits algebraically, then convert the result back to edits.
-//
-// applyFixes returns success if all fixes are valid, could be cleanly
-// merged, and the corresponding files were successfully updated.
-//
-// If the -diff flag was set, instead of updating the files it display the final
-// patch composed of all the cleanly merged fixes.
-//
-// TODO(adonovan): handle file-system level aliases such as symbolic
-// links using robustio.FileID.
-func ApplyFixes(actions []FixAction, verbose bool) error {
- // Select fixes to apply.
- //
- // If there are several for a given Diagnostic, choose the first.
- // Preserve the order of iteration, for determinism.
- type fixact struct {
- fix *analysis.SuggestedFix
- act FixAction
- }
- var fixes []*fixact
- for _, act := range actions {
- for _, diag := range act.Diagnostics {
- for i := range diag.SuggestedFixes {
- fix := &diag.SuggestedFixes[i]
- if i == 0 {
- fixes = append(fixes, &fixact{fix, act})
- } else {
- // TODO(adonovan): abstract the logger.
- log.Printf("%s: ignoring alternative fix %q", act.Name, fix.Message)
- }
- }
- }
- }
-
- // Read file content on demand, from the virtual
- // file system that fed the analyzer (see #62292).
- //
- // This cache assumes that all successful reads for the same
- // file name return the same content.
- // (It is tempting to group fixes by package and do the
- // merge/apply/format steps one package at a time, but
- // packages are not disjoint, due to test variants, so this
- // would not really address the issue.)
- baselineContent := make(map[string][]byte)
- getBaseline := func(readFile analysisinternal.ReadFileFunc, filename string) ([]byte, error) {
- content, ok := baselineContent[filename]
- if !ok {
- var err error
- content, err = readFile(filename)
- if err != nil {
- return nil, err
- }
- baselineContent[filename] = content
- }
- return content, nil
- }
-
- // Apply each fix, updating the current state
- // only if the entire fix can be cleanly merged.
- accumulatedEdits := make(map[string][]diff.Edit)
- goodFixes := 0
-fixloop:
- for _, fixact := range fixes {
- // Convert analysis.TextEdits to diff.Edits, grouped by file.
- // Precondition: a prior call to validateFix succeeded.
- fileEdits := make(map[string][]diff.Edit)
- for _, edit := range fixact.fix.TextEdits {
- file := fixact.act.FileSet.File(edit.Pos)
-
- baseline, err := getBaseline(fixact.act.ReadFileFunc, file.Name())
- if err != nil {
- log.Printf("skipping fix to file %s: %v", file.Name(), err)
- continue fixloop
- }
-
- // We choose to treat size mismatch as a serious error,
- // as it indicates a concurrent write to at least one file,
- // and possibly others (consider a git checkout, for example).
- if file.Size() != len(baseline) {
- return fmt.Errorf("concurrent file modification detected in file %s (size changed from %d -> %d bytes); aborting fix",
- file.Name(), file.Size(), len(baseline))
- }
-
- fileEdits[file.Name()] = append(fileEdits[file.Name()], diff.Edit{
- Start: file.Offset(edit.Pos),
- End: file.Offset(edit.End),
- New: string(edit.NewText),
- })
- }
-
- // Apply each set of edits by merging atop
- // the previous accumulated state.
- after := make(map[string][]diff.Edit)
- for file, edits := range fileEdits {
- if prev := accumulatedEdits[file]; len(prev) > 0 {
- merged, ok := diff.Merge(prev, edits)
- if !ok {
- // debugging
- if false {
- log.Printf("%s: fix %s conflicts", fixact.act.Name, fixact.fix.Message)
- }
- continue fixloop // conflict
- }
- edits = merged
- }
- after[file] = edits
- }
-
- // The entire fix applied cleanly; commit it.
- goodFixes++
- maps.Copy(accumulatedEdits, after)
- // debugging
- if false {
- log.Printf("%s: fix %s applied", fixact.act.Name, fixact.fix.Message)
- }
- }
- badFixes := len(fixes) - goodFixes
-
- // Show diff or update files to final state.
- var files []string
- for file := range accumulatedEdits {
- files = append(files, file)
- }
- sort.Strings(files) // for deterministic -diff
- var filesUpdated, totalFiles int
- for _, file := range files {
- edits := accumulatedEdits[file]
- if len(edits) == 0 {
- continue // the diffs annihilated (a miracle?)
- }
-
- // Apply accumulated fixes.
- baseline := baselineContent[file] // (cache hit)
- final, err := diff.ApplyBytes(baseline, edits)
- if err != nil {
- log.Fatalf("internal error in diff.ApplyBytes: %v", err)
- }
-
- // Attempt to format each file.
- if formatted, err := format.Source(final); err == nil {
- final = formatted
- }
-
- if diffFlag {
- // Since we formatted the file, we need to recompute the diff.
- unified := diff.Unified(file+" (old)", file+" (new)", string(baseline), string(final))
- // TODO(adonovan): abstract the I/O.
- os.Stdout.WriteString(unified)
-
- } else {
- // write
- totalFiles++
- // TODO(adonovan): abstract the I/O.
- if err := os.WriteFile(file, final, 0644); err != nil {
- log.Println(err)
- continue
- }
- filesUpdated++
- }
- }
-
- // TODO(adonovan): consider returning a structured result that
- // maps each SuggestedFix to its status:
- // - invalid
- // - secondary, not selected
- // - applied
- // - had conflicts.
- // and a mapping from each affected file to:
- // - its final/original content pair, and
- // - whether formatting was successful.
- // Then file writes and the UI can be applied by the caller
- // in whatever form they like.
-
- // If victory was incomplete, report an error that indicates partial progress.
- //
- // badFixes > 0 indicates that we decided not to attempt some
- // fixes due to conflicts or failure to read the source; still
- // it's a relatively benign situation since the user can
- // re-run the tool, and we may still make progress.
- //
- // filesUpdated < totalFiles indicates that some file updates
- // failed. This should be rare, but is a serious error as it
- // may apply half a fix, or leave the files in a bad state.
- //
- // These numbers are potentially misleading:
- // The denominator includes duplicate conflicting fixes due to
- // common files in packages "p" and "p [p.test]", which may
- // have been fixed fixed and won't appear in the re-run.
- // TODO(adonovan): eliminate identical fixes as an initial
- // filtering step.
- //
- // TODO(adonovan): should we log that n files were updated in case of total victory?
- if badFixes > 0 || filesUpdated < totalFiles {
- if diffFlag {
- return fmt.Errorf("%d of %d fixes skipped (e.g. due to conflicts)", badFixes, len(fixes))
- } else {
- return fmt.Errorf("applied %d of %d fixes; %d files updated. (Re-run the command to apply more.)",
- goodFixes, len(fixes), filesUpdated)
- }
- }
-
- if verbose {
- log.Printf("applied %d fixes, updated %d files", len(fixes), filesUpdated)
- }
-
- return nil
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
index ffc4169083..c7637df00a 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
@@ -13,22 +13,20 @@ import (
"encoding/json"
"flag"
"fmt"
- "go/token"
"io"
"log"
"os"
"strconv"
- "strings"
"golang.org/x/tools/go/analysis"
)
// flags common to all {single,multi,unit}checkers.
var (
- JSON = false // -json
- Context = -1 // -c=N: if N>0, display offending line plus N lines of context
- Fix bool // -fix
- diffFlag bool // -diff (changes [ApplyFixes] behavior)
+ JSON = false // -json
+ Context = -1 // -c=N: if N>0, display offending line plus N lines of context
+ Fix bool // -fix
+ Diff bool // -diff
)
// Parse creates a flag for each of the analyzer's flags,
@@ -78,7 +76,7 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
- flag.BoolVar(&diffFlag, "diff", false, "with -fix, don't update the files, but print a unified diff")
+ flag.BoolVar(&Diff, "diff", false, "with -fix, don't update the files, but print a unified diff")
// Add shims for legacy vet flags to enable existing
// scripts that run vet to continue to work.
@@ -310,150 +308,3 @@ var vetLegacyFlags = map[string]string{
"unusedfuncs": "unusedresult.funcs",
"unusedstringmethods": "unusedresult.stringmethods",
}
-
-// ---- output helpers common to all drivers ----
-//
-// These functions should not depend on global state (flags)!
-// Really they belong in a different package.
-
-// TODO(adonovan): don't accept an io.Writer if we don't report errors.
-// Either accept a bytes.Buffer (infallible), or return a []byte.
-
-// PrintPlain prints a diagnostic in plain text form.
-// If contextLines is nonnegative, it also prints the
-// offending line plus this many lines of context.
-func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
- print := func(pos, end token.Pos, message string) {
- posn := fset.Position(pos)
- fmt.Fprintf(out, "%s: %s\n", posn, message)
-
- // show offending line plus N lines of context.
- if contextLines >= 0 {
- end := fset.Position(end)
- if !end.IsValid() {
- end = posn
- }
- // TODO(adonovan): highlight the portion of the line indicated
- // by pos...end using ASCII art, terminal colors, etc?
- data, _ := os.ReadFile(posn.Filename)
- lines := strings.Split(string(data), "\n")
- for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
- if 1 <= i && i <= len(lines) {
- fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
- }
- }
- }
- }
-
- print(diag.Pos, diag.End, diag.Message)
- for _, rel := range diag.Related {
- print(rel.Pos, rel.End, "\t"+rel.Message)
- }
-}
-
-// A JSONTree is a mapping from package ID to analysis name to result.
-// Each result is either a jsonError or a list of JSONDiagnostic.
-type JSONTree map[string]map[string]any
-
-// A TextEdit describes the replacement of a portion of a file.
-// Start and End are zero-based half-open indices into the original byte
-// sequence of the file, and New is the new text.
-type JSONTextEdit struct {
- Filename string `json:"filename"`
- Start int `json:"start"`
- End int `json:"end"`
- New string `json:"new"`
-}
-
-// A JSONSuggestedFix describes an edit that should be applied as a whole or not
-// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
-// consists of multiple non-contiguous edits.
-type JSONSuggestedFix struct {
- Message string `json:"message"`
- Edits []JSONTextEdit `json:"edits"`
-}
-
-// A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
-//
-// TODO(matloob): include End position if present.
-type JSONDiagnostic struct {
- Category string `json:"category,omitempty"`
- Posn string `json:"posn"` // e.g. "file.go:line:column"
- Message string `json:"message"`
- SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
- Related []JSONRelatedInformation `json:"related,omitempty"`
-}
-
-// A JSONRelated describes a secondary position and message related to
-// a primary diagnostic.
-//
-// TODO(adonovan): include End position if present.
-type JSONRelatedInformation struct {
- Posn string `json:"posn"` // e.g. "file.go:line:column"
- Message string `json:"message"`
-}
-
-// Add adds the result of analysis 'name' on package 'id'.
-// The result is either a list of diagnostics or an error.
-func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
- var v any
- if err != nil {
- type jsonError struct {
- Err string `json:"error"`
- }
- v = jsonError{err.Error()}
- } else if len(diags) > 0 {
- diagnostics := make([]JSONDiagnostic, 0, len(diags))
- for _, f := range diags {
- var fixes []JSONSuggestedFix
- for _, fix := range f.SuggestedFixes {
- var edits []JSONTextEdit
- for _, edit := range fix.TextEdits {
- edits = append(edits, JSONTextEdit{
- Filename: fset.Position(edit.Pos).Filename,
- Start: fset.Position(edit.Pos).Offset,
- End: fset.Position(edit.End).Offset,
- New: string(edit.NewText),
- })
- }
- fixes = append(fixes, JSONSuggestedFix{
- Message: fix.Message,
- Edits: edits,
- })
- }
- var related []JSONRelatedInformation
- for _, r := range f.Related {
- related = append(related, JSONRelatedInformation{
- Posn: fset.Position(r.Pos).String(),
- Message: r.Message,
- })
- }
- jdiag := JSONDiagnostic{
- Category: f.Category,
- Posn: fset.Position(f.Pos).String(),
- Message: f.Message,
- SuggestedFixes: fixes,
- Related: related,
- }
- diagnostics = append(diagnostics, jdiag)
- }
- v = diagnostics
- }
- if v != nil {
- m, ok := tree[id]
- if !ok {
- m = make(map[string]any)
- tree[id] = m
- }
- m[name] = v
- }
-}
-
-func (tree JSONTree) Print(out io.Writer) error {
- data, err := json.MarshalIndent(tree, "", "\t")
- if err != nil {
- log.Panicf("internal error: JSON marshaling failed: %v", err)
- }
- _, err = fmt.Fprintf(out, "%s\n", data)
- return err
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/url.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/url.go
deleted file mode 100644
index 26a917a991..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/url.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package analysisflags
-
-import (
- "fmt"
- "net/url"
-
- "golang.org/x/tools/go/analysis"
-)
-
-// ResolveURL resolves the URL field for a Diagnostic from an Analyzer
-// and returns the URL. See Diagnostic.URL for details.
-func ResolveURL(a *analysis.Analyzer, d analysis.Diagnostic) (string, error) {
- if d.URL == "" && d.Category == "" && a.URL == "" {
- return "", nil // do nothing
- }
- raw := d.URL
- if d.URL == "" && d.Category != "" {
- raw = "#" + d.Category
- }
- u, err := url.Parse(raw)
- if err != nil {
- return "", fmt.Errorf("invalid Diagnostic.URL %q: %s", raw, err)
- }
- base, err := url.Parse(a.URL)
- if err != nil {
- return "", fmt.Errorf("invalid Analyzer.URL %q: %s", a.URL, err)
- }
- return base.ResolveReference(u).String(), nil
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go
index b4e91edce3..8ccf982d23 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
@@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "appends",
- Doc: analysisinternal.MustExtractDoc(doc, "appends"),
+ Doc: analyzerutil.MustExtractDoc(doc, "appends"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go
index e9c0879844..ba9ca38a81 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go
@@ -19,7 +19,7 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report mismatches between assembly files and Go declarations"
@@ -175,7 +175,7 @@ func run(pass *analysis.Pass) (any, error) {
Files:
for _, fname := range sfiles {
- content, tf, err := analysisinternal.ReadFile(pass, fname)
+ content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go
index 8080aed020..69734df825 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go
@@ -18,7 +18,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@@ -29,7 +29,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "assign",
- Doc: analysisinternal.MustExtractDoc(doc, "assign"),
+ Doc: analyzerutil.MustExtractDoc(doc, "assign"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go
index 9faa3f67c1..c6ab7ff7a2 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go
@@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "atomic",
- Doc: analysisinternal.MustExtractDoc(doc, "atomic"),
+ Doc: analyzerutil.MustExtractDoc(doc, "atomic"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go
index 7dd4f249e2..d0b28e5b84 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go
@@ -14,7 +14,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "check //go:build and // +build directives"
@@ -86,7 +86,7 @@ func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since this may not be a Go source file.
// Read the raw bytes instead.
- content, tf, err := analysisinternal.ReadFile(pass, filename)
+ content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
index bf1202b92b..54b8062cc0 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
@@ -350,8 +350,8 @@ func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
case *types.Array:
return typeOKForCgoCall(t.Elem(), m)
case *types.Struct:
- for i := 0; i < t.NumFields(); i++ {
- if !typeOKForCgoCall(t.Field(i).Type(), m) {
+ for field := range t.Fields() {
+ if !typeOKForCgoCall(field.Type(), m) {
return false
}
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go
index 4190cc5900..208602f486 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go
@@ -328,8 +328,8 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ
ttyp, ok := typ.Underlying().(*types.Tuple)
if ok {
- for i := 0; i < ttyp.Len(); i++ {
- subpath := lockPath(tpkg, ttyp.At(i).Type(), seen)
+ for v := range ttyp.Variables() {
+ subpath := lockPath(tpkg, v.Type(), seen)
if subpath != nil {
return append(subpath, typ.String())
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
index 951aaed00f..d6c2586e73 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go
@@ -16,9 +16,12 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/cfginternal"
+ "golang.org/x/tools/internal/typesinternal"
)
var Analyzer = &analysis.Analyzer{
@@ -26,7 +29,7 @@ var Analyzer = &analysis.Analyzer{
Doc: "build a control-flow graph",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
Run: run,
- ResultType: reflect.TypeOf(new(CFGs)),
+ ResultType: reflect.TypeFor[*CFGs](),
FactTypes: []analysis.Fact{new(noReturn)},
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
@@ -44,7 +47,20 @@ type CFGs struct {
defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
funcDecls map[*types.Func]*declInfo
funcLits map[*ast.FuncLit]*litInfo
- pass *analysis.Pass // transient; nil after construction
+ noReturn map[*types.Func]bool // functions lacking a reachable return statement
+ pass *analysis.Pass // transient; nil after construction
+}
+
+// TODO(adonovan): add (*CFGs).NoReturn to public API.
+func (c *CFGs) isNoReturn(fn *types.Func) bool {
+ return c.noReturn[fn]
+}
+
+func init() {
+ // Expose the hidden method to callers in x/tools.
+ ctrlflowinternal.NoReturn = func(c any, fn *types.Func) bool {
+ return c.(*CFGs).isNoReturn(fn)
+ }
}
// CFGs has two maps: funcDecls for named functions and funcLits for
@@ -54,15 +70,14 @@ type CFGs struct {
// *types.Func but not the other way.
type declInfo struct {
- decl *ast.FuncDecl
- cfg *cfg.CFG // iff decl.Body != nil
- started bool // to break cycles
- noReturn bool
+ decl *ast.FuncDecl
+ cfg *cfg.CFG // iff decl.Body != nil
+ started bool // to break cycles
}
type litInfo struct {
cfg *cfg.CFG
- noReturn bool
+ noReturn bool // (currently unused)
}
// FuncDecl returns the control-flow graph for a named function.
@@ -118,6 +133,7 @@ func run(pass *analysis.Pass) (any, error) {
defs: pass.TypesInfo.Defs,
funcDecls: funcDecls,
funcLits: funcLits,
+ noReturn: make(map[*types.Func]bool),
pass: pass,
}
@@ -138,7 +154,7 @@ func run(pass *analysis.Pass) (any, error) {
li := funcLits[lit]
if li.cfg == nil {
li.cfg = cfg.New(lit.Body, c.callMayReturn)
- if !hasReachableReturn(li.cfg) {
+ if cfginternal.IsNoReturn(li.cfg) {
li.noReturn = true
}
}
@@ -158,27 +174,28 @@ func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
// The buildDecl call tree thus resembles the static call graph.
// We mark each node when we start working on it to break cycles.
- if !di.started { // break cycle
- di.started = true
+ if di.started {
+ return // break cycle
+ }
+ di.started = true
- if isIntrinsicNoReturn(fn) {
- di.noReturn = true
- }
- if di.decl.Body != nil {
- di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
- if !hasReachableReturn(di.cfg) {
- di.noReturn = true
- }
- }
- if di.noReturn {
- c.pass.ExportObjectFact(fn, new(noReturn))
- }
+ noreturn := isIntrinsicNoReturn(fn)
- // debugging
- if false {
- log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn)
+ if di.decl.Body != nil {
+ di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
+ if cfginternal.IsNoReturn(di.cfg) {
+ noreturn = true
}
}
+ if noreturn {
+ c.pass.ExportObjectFact(fn, new(noReturn))
+ c.noReturn[fn] = true
+ }
+
+ // debugging
+ if false {
+ log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn)
+ }
}
// callMayReturn reports whether the called function may return.
@@ -201,31 +218,26 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
// Function or method declared in this package?
if di, ok := c.funcDecls[fn]; ok {
c.buildDecl(fn, di)
- return !di.noReturn
+ return !c.noReturn[fn]
}
// Not declared in this package.
// Is there a fact from another package?
- return !c.pass.ImportObjectFact(fn, new(noReturn))
+ if c.pass.ImportObjectFact(fn, new(noReturn)) {
+ c.noReturn[fn] = true
+ return false
+ }
+
+ return true
}
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
-func hasReachableReturn(g *cfg.CFG) bool {
- for _, b := range g.Blocks {
- if b.Live && b.Return() != nil {
- return true
- }
- }
- return false
-}
-
// isIntrinsicNoReturn reports whether a function intrinsically never
// returns because it stops execution of the calling thread.
// It is the base case in the recursion.
func isIntrinsicNoReturn(fn *types.Func) bool {
// Add functions here as the need arises, but don't allocate memory.
- path, name := fn.Pkg().Path(), fn.Name()
- return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
- path == "runtime" && name == "Goexit"
+ return typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
+ typesinternal.IsFunctionNamed(fn, "runtime", "Goexit")
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go
index 3069ee9fec..af93407cae 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go
@@ -12,7 +12,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "defers",
Requires: []*analysis.Analyzer{inspect.Analyzer},
- Doc: analysisinternal.MustExtractDoc(doc, "defers"),
+ Doc: analyzerutil.MustExtractDoc(doc, "defers"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers",
Run: run,
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go
index c84d25842e..5fa28861e5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go
@@ -14,7 +14,7 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = `check Go toolchain directives such as //go:debug
@@ -86,7 +86,7 @@ func checkGoFile(pass *analysis.Pass, f *ast.File) {
func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since is not a Go source file.
// Read the raw bytes instead.
- content, tf, err := analysisinternal.ReadFile(pass, filename)
+ content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
index b3df99929d..f1465f7343 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
@@ -12,7 +12,7 @@ import (
"go/types"
"golang.org/x/tools/go/analysis"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go
index 809095d40a..a7d558103a 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go
@@ -13,7 +13,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report assembly that clobbers the frame pointer before saving it"
@@ -98,7 +98,7 @@ func run(pass *analysis.Pass) (any, error) {
}
for _, fname := range sfiles {
- content, tf, err := analysisinternal.ReadFile(pass, fname)
+ content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/hostport/hostport.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/hostport/hostport.go
index 07f154963e..d41a0e4cbf 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/hostport/hostport.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/hostport/hostport.go
@@ -17,7 +17,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/ifaceassert.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/ifaceassert.go
index a6dcf1cf8e..da0acbd8e2 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/ifaceassert.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/ifaceassert.go
@@ -12,7 +12,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams"
)
@@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "ifaceassert",
- Doc: analysisinternal.MustExtractDoc(doc, "ifaceassert"),
+ Doc: analyzerutil.MustExtractDoc(doc, "ifaceassert"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/gofix.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/gofix.go
deleted file mode 100644
index 629d5d8526..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/gofix.go
+++ /dev/null
@@ -1,537 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package inline
-
-import (
- "fmt"
- "go/ast"
- "go/token"
- "go/types"
- "slices"
- "strings"
-
- _ "embed"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/gofixdirective"
- "golang.org/x/tools/go/ast/edge"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/astutil"
- "golang.org/x/tools/internal/diff"
- "golang.org/x/tools/internal/packagepath"
- "golang.org/x/tools/internal/refactor"
- "golang.org/x/tools/internal/refactor/inline"
- "golang.org/x/tools/internal/typesinternal"
-)
-
-//go:embed doc.go
-var doc string
-
-var Analyzer = &analysis.Analyzer{
- Name: "inline",
- Doc: analysisinternal.MustExtractDoc(doc, "inline"),
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline",
- Run: run,
- FactTypes: []analysis.Fact{
- (*goFixInlineFuncFact)(nil),
- (*goFixInlineConstFact)(nil),
- (*goFixInlineAliasFact)(nil),
- },
- Requires: []*analysis.Analyzer{inspect.Analyzer},
-}
-
-var allowBindingDecl bool
-
-func init() {
- Analyzer.Flags.BoolVar(&allowBindingDecl, "allow_binding_decl", false,
- "permit inlinings that require a 'var params = args' declaration")
-}
-
-// analyzer holds the state for this analysis.
-type analyzer struct {
- pass *analysis.Pass
- root inspector.Cursor
- // memoization of repeated calls for same file.
- fileContent map[string][]byte
- // memoization of fact imports (nil => no fact)
- inlinableFuncs map[*types.Func]*inline.Callee
- inlinableConsts map[*types.Const]*goFixInlineConstFact
- inlinableAliases map[*types.TypeName]*goFixInlineAliasFact
-}
-
-func run(pass *analysis.Pass) (any, error) {
- a := &analyzer{
- pass: pass,
- root: pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root(),
- fileContent: make(map[string][]byte),
- inlinableFuncs: make(map[*types.Func]*inline.Callee),
- inlinableConsts: make(map[*types.Const]*goFixInlineConstFact),
- inlinableAliases: make(map[*types.TypeName]*goFixInlineAliasFact),
- }
- gofixdirective.Find(pass, a.root, a)
- a.inline()
- return nil, nil
-}
-
-// HandleFunc exports a fact for functions marked with go:fix.
-func (a *analyzer) HandleFunc(decl *ast.FuncDecl) {
- content, err := a.readFile(decl)
- if err != nil {
- a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err)
- return
- }
- callee, err := inline.AnalyzeCallee(discard, a.pass.Fset, a.pass.Pkg, a.pass.TypesInfo, decl, content)
- if err != nil {
- a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err)
- return
- }
- fn := a.pass.TypesInfo.Defs[decl.Name].(*types.Func)
- a.pass.ExportObjectFact(fn, &goFixInlineFuncFact{callee})
- a.inlinableFuncs[fn] = callee
-}
-
-// HandleAlias exports a fact for aliases marked with go:fix.
-func (a *analyzer) HandleAlias(spec *ast.TypeSpec) {
- // Remember that this is an inlinable alias.
- typ := &goFixInlineAliasFact{}
- lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName)
- a.inlinableAliases[lhs] = typ
- // Create a fact only if the LHS is exported and defined at top level.
- // We create a fact even if the RHS is non-exported,
- // so we can warn about uses in other packages.
- if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
- a.pass.ExportObjectFact(lhs, typ)
- }
-}
-
-// HandleConst exports a fact for constants marked with go:fix.
-func (a *analyzer) HandleConst(nameIdent, rhsIdent *ast.Ident) {
- lhs := a.pass.TypesInfo.Defs[nameIdent].(*types.Const)
- rhs := a.pass.TypesInfo.Uses[rhsIdent].(*types.Const) // must be so in a well-typed program
- con := &goFixInlineConstFact{
- RHSName: rhs.Name(),
- RHSPkgName: rhs.Pkg().Name(),
- RHSPkgPath: rhs.Pkg().Path(),
- }
- if rhs.Pkg() == a.pass.Pkg {
- con.rhsObj = rhs
- }
- a.inlinableConsts[lhs] = con
- // Create a fact only if the LHS is exported and defined at top level.
- // We create a fact even if the RHS is non-exported,
- // so we can warn about uses in other packages.
- if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
- a.pass.ExportObjectFact(lhs, con)
- }
-}
-
-// inline inlines each static call to an inlinable function
-// and each reference to an inlinable constant or type alias.
-//
-// TODO(adonovan): handle multiple diffs that each add the same import.
-func (a *analyzer) inline() {
- for cur := range a.root.Preorder((*ast.CallExpr)(nil), (*ast.Ident)(nil)) {
- switch n := cur.Node().(type) {
- case *ast.CallExpr:
- a.inlineCall(n, cur)
-
- case *ast.Ident:
- switch t := a.pass.TypesInfo.Uses[n].(type) {
- case *types.TypeName:
- a.inlineAlias(t, cur)
- case *types.Const:
- a.inlineConst(t, cur)
- }
- }
- }
-}
-
-// If call is a call to an inlinable func, suggest inlining its use at cur.
-func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) {
- if fn := typeutil.StaticCallee(a.pass.TypesInfo, call); fn != nil {
- // Inlinable?
- callee, ok := a.inlinableFuncs[fn]
- if !ok {
- var fact goFixInlineFuncFact
- if a.pass.ImportObjectFact(fn, &fact) {
- callee = fact.Callee
- a.inlinableFuncs[fn] = callee
- }
- }
- if callee == nil {
- return // nope
- }
-
- // Inline the call.
- content, err := a.readFile(call)
- if err != nil {
- a.pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err)
- return
- }
- curFile := astutil.EnclosingFile(cur)
- caller := &inline.Caller{
- Fset: a.pass.Fset,
- Types: a.pass.Pkg,
- Info: a.pass.TypesInfo,
- File: curFile,
- Call: call,
- Content: content,
- }
- res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard})
- if err != nil {
- a.pass.Reportf(call.Lparen, "%v", err)
- return
- }
-
- if res.Literalized {
- // Users are not fond of inlinings that literalize
- // f(x) to func() { ... }(), so avoid them.
- //
- // (Unfortunately the inliner is very timid,
- // and often literalizes when it cannot prove that
- // reducing the call is safe; the user of this tool
- // has no indication of what the problem is.)
- return
- }
- if res.BindingDecl && !allowBindingDecl {
- // When applying fix en masse, users are similarly
- // unenthusiastic about inlinings that cannot
- // entirely eliminate the parameters and
- // insert a 'var params = args' declaration.
- // The flag allows them to decline such fixes.
- return
- }
- got := res.Content
-
- // Suggest the "fix".
- var textEdits []analysis.TextEdit
- for _, edit := range diff.Bytes(content, got) {
- textEdits = append(textEdits, analysis.TextEdit{
- Pos: curFile.FileStart + token.Pos(edit.Start),
- End: curFile.FileStart + token.Pos(edit.End),
- NewText: []byte(edit.New),
- })
- }
- a.pass.Report(analysis.Diagnostic{
- Pos: call.Pos(),
- End: call.End(),
- Message: fmt.Sprintf("Call of %v should be inlined", callee),
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: fmt.Sprintf("Inline call of %v", callee),
- TextEdits: textEdits,
- }},
- })
- }
-}
-
-// If tn is the TypeName of an inlinable alias, suggest inlining its use at cur.
-func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
- inalias, ok := a.inlinableAliases[tn]
- if !ok {
- var fact goFixInlineAliasFact
- if a.pass.ImportObjectFact(tn, &fact) {
- inalias = &fact
- a.inlinableAliases[tn] = inalias
- }
- }
- if inalias == nil {
- return // nope
- }
-
- alias := tn.Type().(*types.Alias)
- // Remember the names of the alias's type params. When we check for shadowing
- // later, we'll ignore these because they won't appear in the replacement text.
- typeParamNames := map[*types.TypeName]bool{}
- for tp := range alias.TypeParams().TypeParams() {
- typeParamNames[tp.Obj()] = true
- }
- rhs := alias.Rhs()
- curPath := a.pass.Pkg.Path()
- curFile := astutil.EnclosingFile(curId)
- id := curId.Node().(*ast.Ident)
- // We have an identifier A here (n), possibly qualified by a package
- // identifier (sel.n), and an inlinable "type A = rhs" elsewhere.
- //
- // We can replace A with rhs if no name in rhs is shadowed at n's position,
- // and every package in rhs is importable by the current package.
-
- var (
- importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix
- edits []analysis.TextEdit
- )
- for _, tn := range typenames(rhs) {
- // Ignore the type parameters of the alias: they won't appear in the result.
- if typeParamNames[tn] {
- continue
- }
- var pkgPath, pkgName string
- if pkg := tn.Pkg(); pkg != nil {
- pkgPath = pkg.Path()
- pkgName = pkg.Name()
- }
- if pkgPath == "" || pkgPath == curPath {
- // The name is in the current package or the universe scope, so no import
- // is required. Check that it is not shadowed (that is, that the type
- // it refers to in rhs is the same one it refers to at n).
- scope := a.pass.TypesInfo.Scopes[curFile].Innermost(id.Pos()) // n's scope
- _, obj := scope.LookupParent(tn.Name(), id.Pos()) // what qn.name means in n's scope
- if obj != tn {
- return
- }
- } else if !packagepath.CanImport(a.pass.Pkg.Path(), pkgPath) {
- // If this package can't see the package of this part of rhs, we can't inline.
- return
- } else if _, ok := importPrefixes[pkgPath]; !ok {
- // Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
- // with the package path for use by the TypeString qualifier below.
- prefix, eds := refactor.AddImport(
- a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
- importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
- edits = append(edits, eds...)
- }
- }
- // Find the complete identifier, which may take any of these forms:
- // Id
- // Id[T]
- // Id[K, V]
- // pkg.Id
- // pkg.Id[T]
- // pkg.Id[K, V]
- var expr ast.Expr = id
- if astutil.IsChildOf(curId, edge.SelectorExpr_Sel) {
- curId = curId.Parent()
- expr = curId.Node().(ast.Expr)
- }
- // If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
- // Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
- switch ek, _ := curId.ParentEdge(); ek {
- case edge.IndexExpr_X:
- expr = curId.Parent().Node().(*ast.IndexExpr)
- case edge.IndexListExpr_X:
- expr = curId.Parent().Node().(*ast.IndexListExpr)
- }
- t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
- if targs := t.TypeArgs(); targs.Len() > 0 {
- // Instantiate the alias with the type args from this use.
- // For example, given type A = M[K, V], compute the type of the use
- // A[int, Foo] as M[int, Foo].
- // Don't validate instantiation: it can't panic unless we have a bug,
- // in which case seeing the stack trace via telemetry would be helpful.
- instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
- rhs = instAlias.(*types.Alias).Rhs()
- }
- // To get the replacement text, render the alias RHS using the package prefixes
- // we assigned above.
- newText := types.TypeString(rhs, func(p *types.Package) string {
- if p == a.pass.Pkg {
- return ""
- }
- if prefix, ok := importPrefixes[p.Path()]; ok {
- return prefix
- }
- panic(fmt.Sprintf("in %q, package path %q has no import prefix", rhs, p.Path()))
- })
- a.reportInline("type alias", "Type alias", expr, edits, newText)
-}
-
-// typenames returns the TypeNames for types within t (including t itself) that have
-// them: basic types, named types and alias types.
-// The same name may appear more than once.
-func typenames(t types.Type) []*types.TypeName {
- var tns []*types.TypeName
-
- var visit func(types.Type)
- visit = func(t types.Type) {
- if hasName, ok := t.(interface{ Obj() *types.TypeName }); ok {
- tns = append(tns, hasName.Obj())
- }
- switch t := t.(type) {
- case *types.Basic:
- tns = append(tns, types.Universe.Lookup(t.Name()).(*types.TypeName))
- case *types.Named:
- for t := range t.TypeArgs().Types() {
- visit(t)
- }
- case *types.Alias:
- for t := range t.TypeArgs().Types() {
- visit(t)
- }
- case *types.TypeParam:
- tns = append(tns, t.Obj())
- case *types.Pointer:
- visit(t.Elem())
- case *types.Slice:
- visit(t.Elem())
- case *types.Array:
- visit(t.Elem())
- case *types.Chan:
- visit(t.Elem())
- case *types.Map:
- visit(t.Key())
- visit(t.Elem())
- case *types.Struct:
- for i := range t.NumFields() {
- visit(t.Field(i).Type())
- }
- case *types.Signature:
- // Ignore the receiver: although it may be present, it has no meaning
- // in a type expression.
- // Ditto for receiver type params.
- // Also, function type params cannot appear in a type expression.
- if t.TypeParams() != nil {
- panic("Signature.TypeParams in type expression")
- }
- visit(t.Params())
- visit(t.Results())
- case *types.Interface:
- for i := range t.NumEmbeddeds() {
- visit(t.EmbeddedType(i))
- }
- for i := range t.NumExplicitMethods() {
- visit(t.ExplicitMethod(i).Type())
- }
- case *types.Tuple:
- for v := range t.Variables() {
- visit(v.Type())
- }
- case *types.Union:
- panic("Union in type expression")
- default:
- panic(fmt.Sprintf("unknown type %T", t))
- }
- }
-
- visit(t)
-
- return tns
-}
-
-// If con is an inlinable constant, suggest inlining its use at cur.
-func (a *analyzer) inlineConst(con *types.Const, cur inspector.Cursor) {
- incon, ok := a.inlinableConsts[con]
- if !ok {
- var fact goFixInlineConstFact
- if a.pass.ImportObjectFact(con, &fact) {
- incon = &fact
- a.inlinableConsts[con] = incon
- }
- }
- if incon == nil {
- return // nope
- }
-
- // If n is qualified by a package identifier, we'll need the full selector expression.
- curFile := astutil.EnclosingFile(cur)
- n := cur.Node().(*ast.Ident)
-
- // We have an identifier A here (n), possibly qualified by a package identifier (sel.X,
- // where sel is the parent of n), // and an inlinable "const A = B" elsewhere (incon).
- // Consider replacing A with B.
-
- // Check that the expression we are inlining (B) means the same thing
- // (refers to the same object) in n's scope as it does in A's scope.
- // If the RHS is not in the current package, AddImport will handle
- // shadowing, so we only need to worry about when both expressions
- // are in the current package.
- if a.pass.Pkg.Path() == incon.RHSPkgPath {
- // incon.rhsObj is the object referred to by B in the definition of A.
- scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope
- _, obj := scope.LookupParent(incon.RHSName, n.Pos()) // what "B" means in n's scope
- if obj == nil {
- // Should be impossible: if code at n can refer to the LHS,
- // it can refer to the RHS.
- panic(fmt.Sprintf("no object for inlinable const %s RHS %s", n.Name, incon.RHSName))
- }
- if obj != incon.rhsObj {
- // "B" means something different here than at the inlinable const's scope.
- return
- }
- } else if !packagepath.CanImport(a.pass.Pkg.Path(), incon.RHSPkgPath) {
- // If this package can't see the RHS's package, we can't inline.
- return
- }
- var (
- importPrefix string
- edits []analysis.TextEdit
- )
- if incon.RHSPkgPath != a.pass.Pkg.Path() {
- importPrefix, edits = refactor.AddImport(
- a.pass.TypesInfo, curFile, incon.RHSPkgName, incon.RHSPkgPath, incon.RHSName, n.Pos())
- }
- // If n is qualified by a package identifier, we'll need the full selector expression.
- var expr ast.Expr = n
- if astutil.IsChildOf(cur, edge.SelectorExpr_Sel) {
- expr = cur.Parent().Node().(ast.Expr)
- }
- a.reportInline("constant", "Constant", expr, edits, importPrefix+incon.RHSName)
-}
-
-// reportInline reports a diagnostic for fixing an inlinable name.
-func (a *analyzer) reportInline(kind, capKind string, ident ast.Expr, edits []analysis.TextEdit, newText string) {
- edits = append(edits, analysis.TextEdit{
- Pos: ident.Pos(),
- End: ident.End(),
- NewText: []byte(newText),
- })
- name := astutil.Format(a.pass.Fset, ident)
- a.pass.Report(analysis.Diagnostic{
- Pos: ident.Pos(),
- End: ident.End(),
- Message: fmt.Sprintf("%s %s should be inlined", capKind, name),
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: fmt.Sprintf("Inline %s %s", kind, name),
- TextEdits: edits,
- }},
- })
-}
-
-func (a *analyzer) readFile(node ast.Node) ([]byte, error) {
- filename := a.pass.Fset.File(node.Pos()).Name()
- content, ok := a.fileContent[filename]
- if !ok {
- var err error
- content, err = a.pass.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- a.fileContent[filename] = content
- }
- return content, nil
-}
-
-// A goFixInlineFuncFact is exported for each function marked "//go:fix inline".
-// It holds information about the callee to support inlining.
-type goFixInlineFuncFact struct{ Callee *inline.Callee }
-
-func (f *goFixInlineFuncFact) String() string { return "goFixInline " + f.Callee.String() }
-func (*goFixInlineFuncFact) AFact() {}
-
-// A goFixInlineConstFact is exported for each constant marked "//go:fix inline".
-// It holds information about an inlinable constant. Gob-serializable.
-type goFixInlineConstFact struct {
- // Information about "const LHSName = RHSName".
- RHSName string
- RHSPkgPath string
- RHSPkgName string
- rhsObj types.Object // for current package
-}
-
-func (c *goFixInlineConstFact) String() string {
- return fmt.Sprintf("goFixInline const %q.%s", c.RHSPkgPath, c.RHSName)
-}
-
-func (*goFixInlineConstFact) AFact() {}
-
-// A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline".
-// It holds no information; its mere existence demonstrates that an alias is inlinable.
-type goFixInlineAliasFact struct{}
-
-func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" }
-func (*goFixInlineAliasFact) AFact() {}
-
-func discard(string, ...any) {}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
new file mode 100644
index 0000000000..1b3cb108c6
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
@@ -0,0 +1,578 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package inline
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "slices"
+ "strings"
+
+ _ "embed"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/analysis/passes/internal/gofixdirective"
+ "golang.org/x/tools/go/ast/edge"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/diff"
+ "golang.org/x/tools/internal/moreiters"
+ "golang.org/x/tools/internal/packagepath"
+ "golang.org/x/tools/internal/refactor"
+ "golang.org/x/tools/internal/refactor/inline"
+ "golang.org/x/tools/internal/typesinternal"
+)
+
+//go:embed doc.go
+var doc string
+
+var Analyzer = &analysis.Analyzer{
+ Name: "inline",
+ Doc: analyzerutil.MustExtractDoc(doc, "inline"),
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline",
+ Run: run,
+ FactTypes: []analysis.Fact{
+ (*goFixInlineFuncFact)(nil),
+ (*goFixInlineConstFact)(nil),
+ (*goFixInlineAliasFact)(nil),
+ },
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+}
+
+var allowBindingDecl bool
+
+func init() {
+ Analyzer.Flags.BoolVar(&allowBindingDecl, "allow_binding_decl", false,
+ "permit inlinings that require a 'var params = args' declaration")
+}
+
+// analyzer holds the state for this analysis.
+type analyzer struct {
+ pass *analysis.Pass
+ root inspector.Cursor
+ // memoization of repeated calls for same file.
+ fileContent map[string][]byte
+ // memoization of fact imports (nil => no fact)
+ inlinableFuncs map[*types.Func]*inline.Callee
+ inlinableConsts map[*types.Const]*goFixInlineConstFact
+ inlinableAliases map[*types.TypeName]*goFixInlineAliasFact
+}
+
+func run(pass *analysis.Pass) (any, error) {
+ a := &analyzer{
+ pass: pass,
+ root: pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root(),
+ fileContent: make(map[string][]byte),
+ inlinableFuncs: make(map[*types.Func]*inline.Callee),
+ inlinableConsts: make(map[*types.Const]*goFixInlineConstFact),
+ inlinableAliases: make(map[*types.TypeName]*goFixInlineAliasFact),
+ }
+ gofixdirective.Find(pass, a.root, a)
+ a.inline()
+ return nil, nil
+}
+
+// HandleFunc exports a fact for functions marked with go:fix.
+func (a *analyzer) HandleFunc(decl *ast.FuncDecl) {
+ content, err := a.readFile(decl)
+ if err != nil {
+ a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: cannot read source file: %v", err)
+ return
+ }
+ callee, err := inline.AnalyzeCallee(discard, a.pass.Fset, a.pass.Pkg, a.pass.TypesInfo, decl, content)
+ if err != nil {
+ a.pass.Reportf(decl.Doc.Pos(), "invalid inlining candidate: %v", err)
+ return
+ }
+ fn := a.pass.TypesInfo.Defs[decl.Name].(*types.Func)
+ a.pass.ExportObjectFact(fn, &goFixInlineFuncFact{callee})
+ a.inlinableFuncs[fn] = callee
+}
+
+// HandleAlias exports a fact for aliases marked with go:fix.
+func (a *analyzer) HandleAlias(spec *ast.TypeSpec) {
+ // Remember that this is an inlinable alias.
+ typ := &goFixInlineAliasFact{}
+ lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName)
+ a.inlinableAliases[lhs] = typ
+ // Create a fact only if the LHS is exported and defined at top level.
+ // We create a fact even if the RHS is non-exported,
+ // so we can warn about uses in other packages.
+ if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
+ a.pass.ExportObjectFact(lhs, typ)
+ }
+}
+
+// HandleConst exports a fact for constants marked with go:fix.
+func (a *analyzer) HandleConst(nameIdent, rhsIdent *ast.Ident) {
+ lhs := a.pass.TypesInfo.Defs[nameIdent].(*types.Const)
+ rhs := a.pass.TypesInfo.Uses[rhsIdent].(*types.Const) // must be so in a well-typed program
+ con := &goFixInlineConstFact{
+ RHSName: rhs.Name(),
+ RHSPkgName: rhs.Pkg().Name(),
+ RHSPkgPath: rhs.Pkg().Path(),
+ }
+ if rhs.Pkg() == a.pass.Pkg {
+ con.rhsObj = rhs
+ }
+ a.inlinableConsts[lhs] = con
+ // Create a fact only if the LHS is exported and defined at top level.
+ // We create a fact even if the RHS is non-exported,
+ // so we can warn about uses in other packages.
+ if lhs.Exported() && typesinternal.IsPackageLevel(lhs) {
+ a.pass.ExportObjectFact(lhs, con)
+ }
+}
+
+// inline inlines each static call to an inlinable function
+// and each reference to an inlinable constant or type alias.
+func (a *analyzer) inline() {
+ for cur := range a.root.Preorder((*ast.CallExpr)(nil), (*ast.Ident)(nil)) {
+ switch n := cur.Node().(type) {
+ case *ast.CallExpr:
+ a.inlineCall(n, cur)
+
+ case *ast.Ident:
+ switch t := a.pass.TypesInfo.Uses[n].(type) {
+ case *types.TypeName:
+ a.inlineAlias(t, cur)
+ case *types.Const:
+ a.inlineConst(t, cur)
+ }
+ }
+ }
+}
+
+// If call is a call to an inlinable func, suggest inlining its use at cur.
+func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) {
+ if fn := typeutil.StaticCallee(a.pass.TypesInfo, call); fn != nil {
+ // Inlinable?
+ callee, ok := a.inlinableFuncs[fn]
+ if !ok {
+ var fact goFixInlineFuncFact
+ if a.pass.ImportObjectFact(fn, &fact) {
+ callee = fact.Callee
+ a.inlinableFuncs[fn] = callee
+ }
+ }
+ if callee == nil {
+ return // nope
+ }
+
+ if a.withinTestOf(cur, fn) {
+ return // don't inline a function from within its own test
+ }
+
+ // Inline the call.
+ content, err := a.readFile(call)
+ if err != nil {
+ a.pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err)
+ return
+ }
+ curFile := astutil.EnclosingFile(cur)
+ caller := &inline.Caller{
+ Fset: a.pass.Fset,
+ Types: a.pass.Pkg,
+ Info: a.pass.TypesInfo,
+ File: curFile,
+ Call: call,
+ Content: content,
+ }
+ res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard})
+ if err != nil {
+ a.pass.Reportf(call.Lparen, "%v", err)
+ return
+ }
+
+ if res.Literalized {
+ // Users are not fond of inlinings that literalize
+ // f(x) to func() { ... }(), so avoid them.
+ //
+ // (Unfortunately the inliner is very timid,
+ // and often literalizes when it cannot prove that
+ // reducing the call is safe; the user of this tool
+ // has no indication of what the problem is.)
+ return
+ }
+ if res.BindingDecl && !allowBindingDecl {
+ // When applying fix en masse, users are similarly
+ // unenthusiastic about inlinings that cannot
+ // entirely eliminate the parameters and
+ // insert a 'var params = args' declaration.
+ // The flag allows them to decline such fixes.
+ return
+ }
+ got := res.Content
+
+ // Suggest the "fix".
+ var textEdits []analysis.TextEdit
+ for _, edit := range diff.Bytes(content, got) {
+ textEdits = append(textEdits, analysis.TextEdit{
+ Pos: curFile.FileStart + token.Pos(edit.Start),
+ End: curFile.FileStart + token.Pos(edit.End),
+ NewText: []byte(edit.New),
+ })
+ }
+ a.pass.Report(analysis.Diagnostic{
+ Pos: call.Pos(),
+ End: call.End(),
+ Message: fmt.Sprintf("Call of %v should be inlined", callee),
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Inline call of %v", callee),
+ TextEdits: textEdits,
+ }},
+ })
+ }
+}
+
+// withinTestOf reports whether cur is within a dedicated test
+// function for the inlinable target function.
+// A call within its dedicated test should not be inlined.
+func (a *analyzer) withinTestOf(cur inspector.Cursor, target *types.Func) bool {
+ curFuncDecl, ok := moreiters.First(cur.Enclosing((*ast.FuncDecl)(nil)))
+ if !ok {
+ return false // not in a function
+ }
+ funcDecl := curFuncDecl.Node().(*ast.FuncDecl)
+ if funcDecl.Recv != nil {
+ return false // not a test func
+ }
+ if strings.TrimSuffix(a.pass.Pkg.Path(), "_test") != target.Pkg().Path() {
+ return false // different package
+ }
+ if !strings.HasSuffix(a.pass.Fset.File(funcDecl.Pos()).Name(), "_test.go") {
+ return false // not a test file
+ }
+
+ // Computed expected SYMBOL portion of "TestSYMBOL_comment"
+ // for the target symbol.
+ symbol := target.Name()
+ if recv := target.Signature().Recv(); recv != nil {
+ _, named := typesinternal.ReceiverNamed(recv)
+ symbol = named.Obj().Name() + "_" + symbol
+ }
+
+ // TODO(adonovan): use a proper Test function parser.
+ fname := funcDecl.Name.Name
+ for _, pre := range []string{"Test", "Example", "Bench"} {
+ if fname == pre+symbol || strings.HasPrefix(fname, pre+symbol+"_") {
+ return true
+ }
+ }
+
+ return false
+}
+
+// If tn is the TypeName of an inlinable alias, suggest inlining its use at cur.
+func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
+ inalias, ok := a.inlinableAliases[tn]
+ if !ok {
+ var fact goFixInlineAliasFact
+ if a.pass.ImportObjectFact(tn, &fact) {
+ inalias = &fact
+ a.inlinableAliases[tn] = inalias
+ }
+ }
+ if inalias == nil {
+ return // nope
+ }
+
+ alias := tn.Type().(*types.Alias)
+ // Remember the names of the alias's type params. When we check for shadowing
+ // later, we'll ignore these because they won't appear in the replacement text.
+ typeParamNames := map[*types.TypeName]bool{}
+ for tp := range alias.TypeParams().TypeParams() {
+ typeParamNames[tp.Obj()] = true
+ }
+ rhs := alias.Rhs()
+ curPath := a.pass.Pkg.Path()
+ curFile := astutil.EnclosingFile(curId)
+ id := curId.Node().(*ast.Ident)
+ // We have an identifier A here (n), possibly qualified by a package
+ // identifier (sel.n), and an inlinable "type A = rhs" elsewhere.
+ //
+ // We can replace A with rhs if no name in rhs is shadowed at n's position,
+ // and every package in rhs is importable by the current package.
+
+ var (
+ importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix
+ edits []analysis.TextEdit
+ )
+ for _, tn := range typenames(rhs) {
+ // Ignore the type parameters of the alias: they won't appear in the result.
+ if typeParamNames[tn] {
+ continue
+ }
+ var pkgPath, pkgName string
+ if pkg := tn.Pkg(); pkg != nil {
+ pkgPath = pkg.Path()
+ pkgName = pkg.Name()
+ }
+ if pkgPath == "" || pkgPath == curPath {
+ // The name is in the current package or the universe scope, so no import
+ // is required. Check that it is not shadowed (that is, that the type
+ // it refers to in rhs is the same one it refers to at n).
+ scope := a.pass.TypesInfo.Scopes[curFile].Innermost(id.Pos()) // n's scope
+ _, obj := scope.LookupParent(tn.Name(), id.Pos()) // what qn.name means in n's scope
+ if obj != tn {
+ return
+ }
+ } else if !packagepath.CanImport(a.pass.Pkg.Path(), pkgPath) {
+ // If this package can't see the package of this part of rhs, we can't inline.
+ return
+ } else if _, ok := importPrefixes[pkgPath]; !ok {
+ // Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
+ // with the package path for use by the TypeString qualifier below.
+ prefix, eds := refactor.AddImport(
+ a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
+ importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
+ edits = append(edits, eds...)
+ }
+ }
+ // Find the complete identifier, which may take any of these forms:
+ // Id
+ // Id[T]
+ // Id[K, V]
+ // pkg.Id
+ // pkg.Id[T]
+ // pkg.Id[K, V]
+ var expr ast.Expr = id
+ if astutil.IsChildOf(curId, edge.SelectorExpr_Sel) {
+ curId = curId.Parent()
+ expr = curId.Node().(ast.Expr)
+ }
+ // If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
+ // Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
+ switch ek, _ := curId.ParentEdge(); ek {
+ case edge.IndexExpr_X:
+ expr = curId.Parent().Node().(*ast.IndexExpr)
+ case edge.IndexListExpr_X:
+ expr = curId.Parent().Node().(*ast.IndexListExpr)
+ }
+ t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
+ if targs := t.TypeArgs(); targs.Len() > 0 {
+ // Instantiate the alias with the type args from this use.
+ // For example, given type A = M[K, V], compute the type of the use
+ // A[int, Foo] as M[int, Foo].
+ // Don't validate instantiation: it can't panic unless we have a bug,
+ // in which case seeing the stack trace via telemetry would be helpful.
+ instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
+ rhs = instAlias.(*types.Alias).Rhs()
+ }
+ // To get the replacement text, render the alias RHS using the package prefixes
+ // we assigned above.
+ newText := types.TypeString(rhs, func(p *types.Package) string {
+ if p == a.pass.Pkg {
+ return ""
+ }
+ if prefix, ok := importPrefixes[p.Path()]; ok {
+ return prefix
+ }
+ panic(fmt.Sprintf("in %q, package path %q has no import prefix", rhs, p.Path()))
+ })
+ a.reportInline("type alias", "Type alias", expr, edits, newText)
+}
+
+// typenames returns the TypeNames for types within t (including t itself) that have
+// them: basic types, named types and alias types.
+// The same name may appear more than once.
+func typenames(t types.Type) []*types.TypeName {
+ var tns []*types.TypeName
+
+ var visit func(types.Type)
+ visit = func(t types.Type) {
+ if hasName, ok := t.(interface{ Obj() *types.TypeName }); ok {
+ tns = append(tns, hasName.Obj())
+ }
+ switch t := t.(type) {
+ case *types.Basic:
+ tns = append(tns, types.Universe.Lookup(t.Name()).(*types.TypeName))
+ case *types.Named:
+ for t := range t.TypeArgs().Types() {
+ visit(t)
+ }
+ case *types.Alias:
+ for t := range t.TypeArgs().Types() {
+ visit(t)
+ }
+ case *types.TypeParam:
+ tns = append(tns, t.Obj())
+ case *types.Pointer:
+ visit(t.Elem())
+ case *types.Slice:
+ visit(t.Elem())
+ case *types.Array:
+ visit(t.Elem())
+ case *types.Chan:
+ visit(t.Elem())
+ case *types.Map:
+ visit(t.Key())
+ visit(t.Elem())
+ case *types.Struct:
+ for field := range t.Fields() {
+ visit(field.Type())
+ }
+ case *types.Signature:
+ // Ignore the receiver: although it may be present, it has no meaning
+ // in a type expression.
+ // Ditto for receiver type params.
+ // Also, function type params cannot appear in a type expression.
+ if t.TypeParams() != nil {
+ panic("Signature.TypeParams in type expression")
+ }
+ visit(t.Params())
+ visit(t.Results())
+ case *types.Interface:
+ for etyp := range t.EmbeddedTypes() {
+ visit(etyp)
+ }
+ for method := range t.ExplicitMethods() {
+ visit(method.Type())
+ }
+ case *types.Tuple:
+ for v := range t.Variables() {
+ visit(v.Type())
+ }
+ case *types.Union:
+ panic("Union in type expression")
+ default:
+ panic(fmt.Sprintf("unknown type %T", t))
+ }
+ }
+
+ visit(t)
+
+ return tns
+}
+
+// If con is an inlinable constant, suggest inlining its use at cur.
+func (a *analyzer) inlineConst(con *types.Const, cur inspector.Cursor) {
+ incon, ok := a.inlinableConsts[con]
+ if !ok {
+ var fact goFixInlineConstFact
+ if a.pass.ImportObjectFact(con, &fact) {
+ incon = &fact
+ a.inlinableConsts[con] = incon
+ }
+ }
+ if incon == nil {
+ return // nope
+ }
+
+ // If n is qualified by a package identifier, we'll need the full selector expression.
+ curFile := astutil.EnclosingFile(cur)
+ n := cur.Node().(*ast.Ident)
+
+ // We have an identifier A here (n), possibly qualified by a package identifier (sel.X,
+ // where sel is the parent of n), // and an inlinable "const A = B" elsewhere (incon).
+ // Consider replacing A with B.
+
+ // Check that the expression we are inlining (B) means the same thing
+ // (refers to the same object) in n's scope as it does in A's scope.
+ // If the RHS is not in the current package, AddImport will handle
+ // shadowing, so we only need to worry about when both expressions
+ // are in the current package.
+ if a.pass.Pkg.Path() == incon.RHSPkgPath {
+ // incon.rhsObj is the object referred to by B in the definition of A.
+ scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope
+ _, obj := scope.LookupParent(incon.RHSName, n.Pos()) // what "B" means in n's scope
+ if obj == nil {
+ // Should be impossible: if code at n can refer to the LHS,
+ // it can refer to the RHS.
+ panic(fmt.Sprintf("no object for inlinable const %s RHS %s", n.Name, incon.RHSName))
+ }
+ if obj != incon.rhsObj {
+ // "B" means something different here than at the inlinable const's scope.
+ return
+ }
+ } else if !packagepath.CanImport(a.pass.Pkg.Path(), incon.RHSPkgPath) {
+ // If this package can't see the RHS's package, we can't inline.
+ return
+ }
+ var (
+ importPrefix string
+ edits []analysis.TextEdit
+ )
+ if incon.RHSPkgPath != a.pass.Pkg.Path() {
+ importPrefix, edits = refactor.AddImport(
+ a.pass.TypesInfo, curFile, incon.RHSPkgName, incon.RHSPkgPath, incon.RHSName, n.Pos())
+ }
+ // If n is qualified by a package identifier, we'll need the full selector expression.
+ var expr ast.Expr = n
+ if astutil.IsChildOf(cur, edge.SelectorExpr_Sel) {
+ expr = cur.Parent().Node().(ast.Expr)
+ }
+ a.reportInline("constant", "Constant", expr, edits, importPrefix+incon.RHSName)
+}
+
+// reportInline reports a diagnostic for fixing an inlinable name.
+func (a *analyzer) reportInline(kind, capKind string, ident ast.Expr, edits []analysis.TextEdit, newText string) {
+ edits = append(edits, analysis.TextEdit{
+ Pos: ident.Pos(),
+ End: ident.End(),
+ NewText: []byte(newText),
+ })
+ name := astutil.Format(a.pass.Fset, ident)
+ a.pass.Report(analysis.Diagnostic{
+ Pos: ident.Pos(),
+ End: ident.End(),
+ Message: fmt.Sprintf("%s %s should be inlined", capKind, name),
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Inline %s %s", kind, name),
+ TextEdits: edits,
+ }},
+ })
+}
+
+func (a *analyzer) readFile(node ast.Node) ([]byte, error) {
+ filename := a.pass.Fset.File(node.Pos()).Name()
+ content, ok := a.fileContent[filename]
+ if !ok {
+ var err error
+ content, err = a.pass.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ a.fileContent[filename] = content
+ }
+ return content, nil
+}
+
+// A goFixInlineFuncFact is exported for each function marked "//go:fix inline".
+// It holds information about the callee to support inlining.
+type goFixInlineFuncFact struct{ Callee *inline.Callee }
+
+func (f *goFixInlineFuncFact) String() string { return "goFixInline " + f.Callee.String() }
+func (*goFixInlineFuncFact) AFact() {}
+
+// A goFixInlineConstFact is exported for each constant marked "//go:fix inline".
+// It holds information about an inlinable constant. Gob-serializable.
+type goFixInlineConstFact struct {
+ // Information about "const LHSName = RHSName".
+ RHSName string
+ RHSPkgPath string
+ RHSPkgName string
+ rhsObj types.Object // for current package
+}
+
+func (c *goFixInlineConstFact) String() string {
+ return fmt.Sprintf("goFixInline const %q.%s", c.RHSPkgPath, c.RHSName)
+}
+
+func (*goFixInlineConstFact) AFact() {}
+
+// A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline".
+// It holds no information; its mere existence demonstrates that an alias is inlinable.
+type goFixInlineAliasFact struct{}
+
+func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" }
+func (*goFixInlineAliasFact) AFact() {}
+
+func discard(string, ...any) {}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go
index ee1972f56d..aae5d255f9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go
@@ -41,7 +41,7 @@ var Analyzer = &analysis.Analyzer{
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect",
Run: run,
RunDespiteErrors: true,
- ResultType: reflect.TypeOf(new(inspector.Inspector)),
+ ResultType: reflect.TypeFor[*inspector.Inspector](),
}
func run(pass *analysis.Pass) (any, error) {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal/ctrlflowinternal.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal/ctrlflowinternal.go
new file mode 100644
index 0000000000..ee7a37228e
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal/ctrlflowinternal.go
@@ -0,0 +1,17 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ctrlflowinternal exposes internals of ctrlflow.
+// It cannot actually depend on symbols from ctrlflow.
+package ctrlflowinternal
+
+import "go/types"
+
+// NoReturn exposes the (*ctrlflow.CFGs).NoReturn method to the buildssa analyzer.
+//
+// You must link [golang.org/x/tools/go/analysis/passes/ctrlflow] into your
+// application for it to be non-nil.
+var NoReturn = func(cfgs any, fn *types.Func) bool {
+ panic("x/tools/go/analysis/passes/ctrlflow is not linked into this application")
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go
index 868226328f..41b19d7933 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go
@@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
@@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "loopclosure",
- Doc: analysisinternal.MustExtractDoc(doc, "loopclosure"),
+ Doc: analyzerutil.MustExtractDoc(doc, "loopclosure"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/loopclosure",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -55,8 +55,8 @@ func run(pass *analysis.Pass) (any, error) {
switch n := n.(type) {
case *ast.File:
// Only traverse the file if its goversion is strictly before go1.22.
- goversion := versions.FileVersion(pass.TypesInfo, n)
- return versions.Before(goversion, versions.Go1_22)
+ return !analyzerutil.FileUsesGoVersion(pass, n, versions.Go1_22)
+
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
@@ -308,12 +308,11 @@ func parallelSubtest(info *types.Info, call *ast.CallExpr) []ast.Stmt {
if !ok {
continue
}
- expr := exprStmt.X
- if isMethodCall(info, expr, "testing", "T", "Parallel") {
- call, _ := expr.(*ast.CallExpr)
- if call == nil {
- continue
- }
+ call, ok := exprStmt.X.(*ast.CallExpr)
+ if !ok {
+ continue
+ }
+ if isMethodCall(info, call, "testing", "T", "Parallel") {
x, _ := call.Fun.(*ast.SelectorExpr)
if x == nil {
continue
@@ -347,26 +346,6 @@ func unlabel(stmt ast.Stmt) (ast.Stmt, bool) {
}
}
-// isMethodCall reports whether expr is a method call of
-// ...
-func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method string) bool {
- call, ok := expr.(*ast.CallExpr)
- if !ok {
- return false
- }
-
- // Check that we are calling a method
- f := typeutil.StaticCallee(info, call)
- if f == nil || f.Name() != method {
- return false
- }
- recv := f.Type().(*types.Signature).Recv()
- if recv == nil {
- return false
- }
-
- // Check that the receiver is a . or
- // *..
- _, named := typesinternal.ReceiverNamed(recv)
- return typesinternal.IsTypeNamed(named, pkgPath, typeName)
+func isMethodCall(info *types.Info, call *ast.CallExpr, pkgPath, typeName, method string) bool {
+ return typesinternal.IsMethodNamed(typeutil.Callee(info, call), pkgPath, typeName, method)
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go
index dfaecf51e2..28a5f6cd93 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "lostcancel",
- Doc: analysisinternal.MustExtractDoc(doc, "lostcancel"),
+ Doc: analyzerutil.MustExtractDoc(doc, "lostcancel"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel",
Run: run,
Requires: []*analysis.Analyzer{
@@ -316,8 +316,8 @@ outer:
}
func tupleContains(tuple *types.Tuple, v *types.Var) bool {
- for i := 0; i < tuple.Len(); i++ {
- if tuple.At(i) == v {
+ for v0 := range tuple.Variables() {
+ if v0 == v {
return true
}
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/any.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/any.go
index 05999f8f2b..579ab865da 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/any.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/any.go
@@ -9,29 +9,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/versions"
)
var AnyAnalyzer = &analysis.Analyzer{
- Name: "any",
- Doc: analysisinternal.MustExtractDoc(doc, "any"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: runAny,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
+ Name: "any",
+ Doc: analyzerutil.MustExtractDoc(doc, "any"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: runAny,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
}
// The any pass replaces interface{} with go1.18's 'any'.
func runAny(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.18") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_18) {
for curIface := range curFile.Preorder((*ast.InterfaceType)(nil)) {
iface := curIface.Node().(*ast.InterfaceType)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/bloop.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/bloop.go
index eb1ac170c6..ad45d74478 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/bloop.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/bloop.go
@@ -15,20 +15,19 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var BLoopAnalyzer = &analysis.Analyzer{
Name: "bloop",
- Doc: analysisinternal.MustExtractDoc(doc, "bloop"),
+ Doc: analyzerutil.MustExtractDoc(doc, "bloop"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -45,16 +44,13 @@ var BLoopAnalyzer = &analysis.Analyzer{
// for i := 0; i < b.N; i++ {} => for b.Loop() {}
// for range b.N {}
func bloop(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
if !typesinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
)
// edits computes the text edits for a matched for/range loop
@@ -102,7 +98,7 @@ func bloop(pass *analysis.Pass) (any, error) {
(*ast.ForStmt)(nil),
(*ast.RangeStmt)(nil),
}
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curLoop := range curFile.Preorder(loops...) {
switch n := curLoop.Node().(type) {
case *ast.ForStmt:
@@ -189,6 +185,7 @@ func enclosingFunc(c inspector.Cursor) (inspector.Cursor, bool) {
// 2. The only b.N loop in that benchmark function
// - b.Loop() can only be called once per benchmark execution
// - Multiple calls result in "B.Loop called with timer stopped" error
+// - Multiple loops may have complex interdependencies that are hard to analyze
func usesBenchmarkNOnce(c inspector.Cursor, info *types.Info) bool {
// Find the enclosing benchmark function
curFunc, ok := enclosingFunc(c)
@@ -205,17 +202,14 @@ func usesBenchmarkNOnce(c inspector.Cursor, info *types.Info) bool {
return false
}
- // Count b.N references in this benchmark function
+ // Count all b.N references in this benchmark function (including nested functions)
bnRefCount := 0
- filter := []ast.Node{(*ast.SelectorExpr)(nil), (*ast.FuncLit)(nil)}
+ filter := []ast.Node{(*ast.SelectorExpr)(nil)}
curFunc.Inspect(filter, func(cur inspector.Cursor) bool {
- switch n := cur.Node().(type) {
- case *ast.FuncLit:
- return false // don't descend into nested function literals
- case *ast.SelectorExpr:
- if n.Sel.Name == "N" && typesinternal.IsPointerToNamed(info.TypeOf(n.X), "testing", "B") {
- bnRefCount++
- }
+ sel := cur.Node().(*ast.SelectorExpr)
+ if sel.Sel.Name == "N" &&
+ typesinternal.IsPointerToNamed(info.TypeOf(sel.X), "testing", "B") {
+ bnRefCount++
}
return true
})
@@ -240,7 +234,7 @@ func isIncrementLoop(info *types.Info, loop *ast.ForStmt) *types.Var {
if assign, ok := loop.Init.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Rhs) == 1 &&
- isZeroIntLiteral(info, assign.Rhs[0]) &&
+ isZeroIntConst(info, assign.Rhs[0]) &&
is[*ast.IncDecStmt](loop.Post) &&
loop.Post.(*ast.IncDecStmt).Tok == token.INC &&
astutil.EqualSyntax(loop.Post.(*ast.IncDecStmt).X, assign.Lhs[0]) {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
index bc143d7a6d..7469002f56 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
@@ -176,8 +176,12 @@ This analyzer finds declarations of functions of this form:
and suggests a fix to turn them into inlinable wrappers around
go1.26's built-in new(expr) function:
+ //go:fix inline
func varOf(x int) *int { return new(x) }
+(The directive comment causes the 'inline' analyzer to suggest
+that calls to such functions are inlined.)
+
In addition, this analyzer suggests a fix for each call
to one of the functions before it is transformed, so that
@@ -187,9 +191,9 @@ is replaced by:
use(new(123))
-(Wrapper functions such as varOf are common when working with Go
+Wrapper functions such as varOf are common when working with Go
serialization packages such as for JSON or protobuf, where pointers
-are often used to express optionality.)
+are often used to express optionality.
# Analyzer omitzero
@@ -327,6 +331,44 @@ iterator offered by the same data type:
where x is one of various well-known types in the standard library.
+# Analyzer stringscut
+
+stringscut: replace strings.Index etc. with strings.Cut
+
+This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18.
+
+For example:
+
+ idx := strings.Index(s, substr)
+ if idx >= 0 {
+ return s[:idx]
+ }
+
+is replaced by:
+
+ before, _, ok := strings.Cut(s, substr)
+ if ok {
+ return before
+ }
+
+And:
+
+ idx := strings.Index(s, substr)
+ if idx >= 0 {
+ return
+ }
+
+is replaced by:
+
+ found := strings.Contains(s, substr)
+ if found {
+ return
+ }
+
+It also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings.
+
+Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.
+
# Analyzer stringscutprefix
stringscutprefix: replace HasPrefix/TrimPrefix with CutPrefix
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go
index b6387ad840..d9a922f846 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go
@@ -14,21 +14,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var errorsastypeAnalyzer = &analysis.Analyzer{
Name: "errorsastype",
- Doc: analysisinternal.MustExtractDoc(doc, "errorsastype"),
+ Doc: analyzerutil.MustExtractDoc(doc, "errorsastype"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
- Requires: []*analysis.Analyzer{generated.Analyzer, typeindexanalyzer.Analyzer},
+ Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: errorsastype,
}
@@ -78,8 +78,6 @@ func init() {
//
// - if errors.As(err, myerr) && othercond { ... }
func errorsastype(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@@ -97,7 +95,7 @@ func errorsastype(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curDeclStmt)
- if !fileUses(info, file, "go1.26") {
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // errors.AsType is too new
}
@@ -127,6 +125,8 @@ func errorsastype(pass *analysis.Pass) (any, error) {
errtype := types.TypeString(v.Type(), qual)
// Choose a name for the "ok" variable.
+ // TODO(adonovan): this pattern also appears in stditerators,
+ // and is wanted elsewhere; factor.
okName := "ok"
if okVar := lookup(info, curCall, "ok"); okVar != nil {
// The name 'ok' is already declared, but
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/fmtappendf.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/fmtappendf.go
index f2e5360542..389f703466 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/fmtappendf.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/fmtappendf.go
@@ -13,18 +13,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var FmtAppendfAnalyzer = &analysis.Analyzer{
Name: "fmtappendf",
- Doc: analysisinternal.MustExtractDoc(doc, "fmtappendf"),
+ Doc: analyzerutil.MustExtractDoc(doc, "fmtappendf"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -35,8 +34,6 @@ var FmtAppendfAnalyzer = &analysis.Analyzer{
// The fmtappend function replaces []byte(fmt.Sprintf(...)) by
// fmt.Appendf(nil, ...), and similarly for Sprint, Sprintln.
func fmtappendf(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for _, fn := range []types.Object{
index.Object("fmt", "Sprintf"),
@@ -50,7 +47,7 @@ func fmtappendf(pass *analysis.Pass) (any, error) {
conv := curCall.Parent().Node().(*ast.CallExpr)
tv := pass.TypesInfo.Types[conv.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) &&
- fileUses(pass.TypesInfo, astutil.EnclosingFile(curCall), "go1.19") {
+ analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
// Have: []byte(fmt.SprintX(...))
// Find "Sprint" identifier.
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go
index 76e3a8a73c..67f60acaaf 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/forvar.go
@@ -10,22 +10,18 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
+ "golang.org/x/tools/internal/versions"
)
var ForVarAnalyzer = &analysis.Analyzer{
- Name: "forvar",
- Doc: analysisinternal.MustExtractDoc(doc, "forvar"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: forvar,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
+ Name: "forvar",
+ Doc: analyzerutil.MustExtractDoc(doc, "forvar"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: forvar,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
}
// forvar offers to fix unnecessary copying of a for variable
@@ -39,54 +35,77 @@ var ForVarAnalyzer = &analysis.Analyzer{
// where the two idents are the same,
// and the ident is defined (:=) as a variable in the for statement.
// (Note that this 'fix' does not work for three clause loops
-// because the Go specification says "The variable used by each subsequent iteration
+// because the Go specfilesUsingGoVersionsays "The variable used by each subsequent iteration
// is declared implicitly before executing the post statement and initialized to the
// value of the previous iteration's variable at that moment.")
+//
+// Variant: same thing in an IfStmt.Init, when the IfStmt is the sole
+// loop body statement:
+//
+// for _, x := range foo {
+// if x := x; cond { ... }
+// }
+//
+// (The restriction is necessary to avoid potential problems arising
+// from merging two distinct variables.)
+//
+// This analyzer is synergistic with stditerators,
+// which may create redundant "x := x" statements.
func forvar(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.22") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
loop := curLoop.Node().(*ast.RangeStmt)
if loop.Tok != token.DEFINE {
continue
}
- isLoopVarRedecl := func(assign *ast.AssignStmt) bool {
- for i, lhs := range assign.Lhs {
- if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
- (astutil.EqualSyntax(lhs, loop.Key) || astutil.EqualSyntax(lhs, loop.Value))) {
- return false
+ isLoopVarRedecl := func(stmt ast.Stmt) bool {
+ if assign, ok := stmt.(*ast.AssignStmt); ok &&
+ assign.Tok == token.DEFINE &&
+ len(assign.Lhs) == len(assign.Rhs) {
+
+ for i, lhs := range assign.Lhs {
+ if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
+ (astutil.EqualSyntax(lhs, loop.Key) ||
+ astutil.EqualSyntax(lhs, loop.Value))) {
+ return false
+ }
}
+ return true
}
- return true
+ return false
}
// Have: for k, v := range x { stmts }
//
// Delete the prefix of stmts that are
// of the form k := k; v := v; k, v := k, v; v, k := v, k.
for _, stmt := range loop.Body.List {
- if assign, ok := stmt.(*ast.AssignStmt); ok &&
- assign.Tok == token.DEFINE &&
- len(assign.Lhs) == len(assign.Rhs) &&
- isLoopVarRedecl(assign) {
-
- curStmt, _ := curLoop.FindNode(stmt)
- edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
- if len(edits) > 0 {
- pass.Report(analysis.Diagnostic{
- Pos: stmt.Pos(),
- End: stmt.End(),
- Message: "copying variable is unneeded",
- SuggestedFixes: []analysis.SuggestedFix{{
- Message: "Remove unneeded redeclaration",
- TextEdits: edits,
- }},
- })
- }
+ if isLoopVarRedecl(stmt) {
+ // { x := x; ... }
+ // ------
+ } else if ifstmt, ok := stmt.(*ast.IfStmt); ok &&
+ ifstmt.Init != nil &&
+ len(loop.Body.List) == 1 && // must be sole statement in loop body
+ isLoopVarRedecl(ifstmt.Init) {
+ // if x := x; cond {
+ // ------
+ stmt = ifstmt.Init
} else {
break // stop at first other statement
}
+
+ curStmt, _ := curLoop.FindNode(stmt)
+ edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
+ if len(edits) > 0 {
+ pass.Report(analysis.Diagnostic{
+ Pos: stmt.Pos(),
+ End: stmt.End(),
+ Message: "copying variable is unneeded",
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: "Remove unneeded redeclaration",
+ TextEdits: edits,
+ }},
+ })
+ }
}
}
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
index 3072cf6f51..2352c8b608 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
@@ -15,23 +15,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
var MapsLoopAnalyzer = &analysis.Analyzer{
- Name: "mapsloop",
- Doc: analysisinternal.MustExtractDoc(doc, "mapsloop"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: mapsloop,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
+ Name: "mapsloop",
+ Doc: analyzerutil.MustExtractDoc(doc, "mapsloop"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: mapsloop,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
}
// The mapsloop pass offers to simplify a loop of map insertions:
@@ -55,8 +52,6 @@ var MapsLoopAnalyzer = &analysis.Analyzer{
// m = make(M)
// m = M{}
func mapsloop(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "maps", "bytes", "runtime") {
@@ -223,8 +218,7 @@ func mapsloop(pass *analysis.Pass) (any, error) {
}
// Find all range loops around m[k] = v.
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.23") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
index 7ebf837375..23a0977f21 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
@@ -15,19 +15,18 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var MinMaxAnalyzer = &analysis.Analyzer{
Name: "minmax",
- Doc: analysisinternal.MustExtractDoc(doc, "minmax"),
+ Doc: analyzerutil.MustExtractDoc(doc, "minmax"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -56,8 +55,6 @@ var MinMaxAnalyzer = &analysis.Analyzer{
// - "x := a" or "x = a" or "var x = a" in pattern 2
// - "x < b" or "a < b" in pattern 2
func minmax(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Check for user-defined min/max functions that can be removed
checkUserDefinedMinMax(pass)
@@ -201,8 +198,7 @@ func minmax(pass *analysis.Pass) (any, error) {
// Find all "if a < b { lhs = rhs }" statements.
info := pass.TypesInfo
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
astFile := curFile.Node().(*ast.File)
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
index df23fc23c8..013ce79d6c 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
@@ -16,15 +16,15 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/typesinternal"
- "golang.org/x/tools/internal/versions"
)
//go:embed doc.go
@@ -48,6 +48,7 @@ var Suite = []*analysis.Analyzer{
// SlicesDeleteAnalyzer, // not nil-preserving!
SlicesSortAnalyzer,
stditeratorsAnalyzer,
+ stringscutAnalyzer,
StringsCutPrefixAnalyzer,
StringsSeqAnalyzer,
StringsBuilderAnalyzer,
@@ -57,18 +58,6 @@ var Suite = []*analysis.Analyzer{
// -- helpers --
-// skipGenerated decorates pass.Report to suppress diagnostics in generated files.
-func skipGenerated(pass *analysis.Pass) {
- report := pass.Report
- pass.Report = func(diag analysis.Diagnostic) {
- generated := pass.ResultOf[generated.Analyzer].(*generated.Result)
- if generated.IsGenerated(diag.Pos) {
- return // skip
- }
- report(diag)
- }
-}
-
// formatExprs formats a comma-separated list of expressions.
func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
var buf strings.Builder
@@ -81,8 +70,8 @@ func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
return buf.String()
}
-// isZeroIntLiteral reports whether e is an integer whose value is 0.
-func isZeroIntLiteral(info *types.Info, e ast.Expr) bool {
+// isZeroIntConst reports whether e is an integer whose value is 0.
+func isZeroIntConst(info *types.Info, e ast.Expr) bool {
return isIntLiteral(info, e, 0)
}
@@ -91,36 +80,28 @@ func isIntLiteral(info *types.Info, e ast.Expr, n int64) bool {
return info.Types[e].Value == constant.MakeInt64(n)
}
-// filesUsing returns a cursor for each *ast.File in the inspector
+// filesUsingGoVersion returns a cursor for each *ast.File in the inspector
// that uses at least the specified version of Go (e.g. "go1.24").
//
+// The pass's analyzer must require [inspect.Analyzer].
+//
// TODO(adonovan): opt: eliminate this function, instead following the
-// approach of [fmtappendf], which uses typeindex and [fileUses].
-// See "Tip" at [fileUses] for motivation.
-func filesUsing(inspect *inspector.Inspector, info *types.Info, version string) iter.Seq[inspector.Cursor] {
+// approach of [fmtappendf], which uses typeindex and
+// [analyzerutil.FileUsesGoVersion]; see "Tip" documented at the
+// latter function for motivation.
+func filesUsingGoVersion(pass *analysis.Pass, version string) iter.Seq[inspector.Cursor] {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
return func(yield func(inspector.Cursor) bool) {
for curFile := range inspect.Root().Children() {
file := curFile.Node().(*ast.File)
- if !versions.Before(info.FileVersions[file], version) && !yield(curFile) {
+ if analyzerutil.FileUsesGoVersion(pass, file, version) && !yield(curFile) {
break
}
}
}
}
-// fileUses reports whether the specified file uses at least the
-// specified version of Go (e.g. "go1.24").
-//
-// Tip: we recommend using this check "late", just before calling
-// pass.Report, rather than "early" (when entering each ast.File, or
-// each candidate node of interest, during the traversal), because the
-// operation is not free, yet is not a highly selective filter: the
-// fraction of files that pass most version checks is high and
-// increases over time.
-func fileUses(info *types.Info, file *ast.File, version string) bool {
- return !versions.Before(info.FileVersions[file], version)
-}
-
// within reports whether the current pass is analyzing one of the
// specified standard packages or their dependencies.
func within(pass *analysis.Pass, pkgs ...string) bool {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
index b8893244d5..6cb75f247c 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
@@ -17,13 +17,14 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/versions"
)
var NewExprAnalyzer = &analysis.Analyzer{
Name: "newexpr",
- Doc: analysisinternal.MustExtractDoc(doc, "newexpr"),
+ Doc: analyzerutil.MustExtractDoc(doc, "newexpr"),
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -60,7 +61,7 @@ func run(pass *analysis.Pass) (any, error) {
// Check file version.
file := astutil.EnclosingFile(curFuncDecl)
- if !fileUses(info, file, "go1.26") {
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
@@ -87,25 +88,18 @@ func run(pass *analysis.Pass) (any, error) {
}
}
- // Disabled until we resolve https://go.dev/issue/75726
- // (Go version skew between caller and callee in inliner.)
- // TODO(adonovan): fix and reenable.
+ // Add a //go:fix inline annotation, if not already present.
//
- // Also, restore these lines to our section of doc.go:
- // //go:fix inline
- // ...
- // (The directive comment causes the inline analyzer to suggest
- // that calls to such functions are inlined.)
- if false {
- // Add a //go:fix inline annotation, if not already present.
- // TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
- if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
- edits = append(edits, analysis.TextEdit{
- Pos: decl.Pos(),
- End: decl.Pos(),
- NewText: []byte("//go:fix inline\n"),
- })
- }
+ // The inliner will not inline a newer callee body into an
+ // older Go file; see https://go.dev/issue/75726.
+ //
+ // TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
+ if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
+ edits = append(edits, analysis.TextEdit{
+ Pos: decl.Pos(),
+ End: decl.Pos(),
+ NewText: []byte("//go:fix inline\n"),
+ })
}
if len(edits) > 0 {
@@ -140,7 +134,7 @@ func run(pass *analysis.Pass) (any, error) {
// Check file version.
file := astutil.EnclosingFile(curCall)
- if !fileUses(info, file, "go1.26") {
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go
index bd309cf9d5..4a05d64f42 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go
@@ -12,21 +12,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/versions"
)
var OmitZeroAnalyzer = &analysis.Analyzer{
- Name: "omitzero",
- Doc: analysisinternal.MustExtractDoc(doc, "omitzero"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: omitzero,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
+ Name: "omitzero",
+ Doc: analyzerutil.MustExtractDoc(doc, "omitzero"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: omitzero,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
}
func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Field) {
@@ -48,25 +44,20 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
// No omitempty in json tag
return
}
- omitEmptyPos, omitEmptyEnd, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3])
+ omitEmpty, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3])
if err != nil {
return
}
- removePos, removeEnd := omitEmptyPos, omitEmptyEnd
+ var remove analysis.Range = omitEmpty
jsonTag := reflect.StructTag(tagconv).Get("json")
if jsonTag == ",omitempty" {
// Remove the entire struct tag if json is the only package used
if match[1]-match[0] == len(tagconv) {
- removePos = curField.Tag.Pos()
- removeEnd = curField.Tag.End()
+ remove = curField.Tag
} else {
// Remove the json tag if omitempty is the only field
- removePos, err = astutil.PosInStringLiteral(curField.Tag, match[0])
- if err != nil {
- return
- }
- removeEnd, err = astutil.PosInStringLiteral(curField.Tag, match[1])
+ remove, err = astutil.RangeInStringLiteral(curField.Tag, match[0], match[1])
if err != nil {
return
}
@@ -81,8 +72,8 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
Message: "Remove redundant omitempty tag",
TextEdits: []analysis.TextEdit{
{
- Pos: removePos,
- End: removeEnd,
+ Pos: remove.Pos(),
+ End: remove.End(),
},
},
},
@@ -90,8 +81,8 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
Message: "Replace omitempty with omitzero (behavior change)",
TextEdits: []analysis.TextEdit{
{
- Pos: omitEmptyPos,
- End: omitEmptyEnd,
+ Pos: omitEmpty.Pos(),
+ End: omitEmpty.End(),
NewText: []byte(",omitzero"),
},
},
@@ -100,18 +91,14 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
}
// The omitzero pass searches for instances of "omitempty" in a json field tag on a
-// struct. Since "omitempty" does not have any effect when applied to a struct field,
+// struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field,
// it suggests either deleting "omitempty" or replacing it with "omitzero", which
// correctly excludes structs from a json encoding.
func omitzero(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- info := pass.TypesInfo
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
- checkOmitEmptyField(pass, info, curField)
+ checkOmitEmptyField(pass, pass.TypesInfo, curField)
}
}
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go
index e8af8074ff..57b502ab80 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go
@@ -10,13 +10,14 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/goplsexport"
+ "golang.org/x/tools/internal/versions"
)
var plusBuildAnalyzer = &analysis.Analyzer{
Name: "plusbuild",
- Doc: analysisinternal.MustExtractDoc(doc, "plusbuild"),
+ Doc: analyzerutil.MustExtractDoc(doc, "plusbuild"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#plusbuild",
Run: plusbuild,
}
@@ -28,7 +29,7 @@ func init() {
func plusbuild(pass *analysis.Pass) (any, error) {
check := func(f *ast.File) {
- if !fileUses(pass.TypesInfo, f, "go1.18") {
+ if !analyzerutil.FileUsesGoVersion(pass, f, versions.Go1_18) {
return
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
index adc840f11d..6b1edf38b3 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
@@ -15,19 +15,18 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var RangeIntAnalyzer = &analysis.Analyzer{
Name: "rangeint",
- Doc: analysisinternal.MustExtractDoc(doc, "rangeint"),
+ Doc: analyzerutil.MustExtractDoc(doc, "rangeint"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -66,21 +65,19 @@ var RangeIntAnalyzer = &analysis.Analyzer{
// - a constant; or
// - len(s), where s has the above properties.
func rangeint(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
+ var (
+ info = pass.TypesInfo
+ typeindex = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ )
- info := pass.TypesInfo
-
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- typeindex := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
-
- for curFile := range filesUsing(inspect, info, "go1.22") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
nextLoop:
for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
loop := curLoop.Node().(*ast.ForStmt)
if init, ok := loop.Init.(*ast.AssignStmt); ok &&
isSimpleAssign(init) &&
is[*ast.Ident](init.Lhs[0]) &&
- isZeroIntLiteral(info, init.Rhs[0]) {
+ isZeroIntConst(info, init.Rhs[0]) {
// Have: for i = 0; ... (or i := 0)
index := init.Lhs[0].(*ast.Ident)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
index c9b0fa42ee..0fc781813f 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
@@ -14,9 +14,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@@ -26,9 +25,8 @@ import (
var ReflectTypeForAnalyzer = &analysis.Analyzer{
Name: "reflecttypefor",
- Doc: analysisinternal.MustExtractDoc(doc, "reflecttypefor"),
+ Doc: analyzerutil.MustExtractDoc(doc, "reflecttypefor"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -37,8 +35,6 @@ var ReflectTypeForAnalyzer = &analysis.Analyzer{
}
func reflecttypefor(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@@ -89,7 +85,7 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curCall)
- if versions.Before(info.FileVersions[file], "go1.22") {
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_22) {
continue // TypeFor requires go1.22
}
tokFile := pass.Fset.File(file.Pos())
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go
index 032f874df1..960a46644b 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go
@@ -13,25 +13,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default.
var AppendClippedAnalyzer = &analysis.Analyzer{
- Name: "appendclipped",
- Doc: analysisinternal.MustExtractDoc(doc, "appendclipped"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: appendclipped,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
+ Name: "appendclipped",
+ Doc: analyzerutil.MustExtractDoc(doc, "appendclipped"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: appendclipped,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
}
// The appendclipped pass offers to simplify a tower of append calls:
@@ -59,8 +55,6 @@ var AppendClippedAnalyzer = &analysis.Analyzer{
// The fix does not always preserve nilness the of base slice when the
// addends (a, b, c) are all empty (see #73557).
func appendclipped(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "bytes", "runtime") {
@@ -205,8 +199,7 @@ func appendclipped(pass *analysis.Pass) (any, error) {
skip := make(map[*ast.CallExpr]bool)
// Visit calls of form append(x, y...).
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
@@ -266,7 +259,7 @@ func clippedSlice(info *types.Info, e ast.Expr) (res ast.Expr, empty bool) {
// x[:0:0], x[:len(x):len(x)], x[:k:k]
if e.Slice3 && e.High != nil && e.Max != nil && astutil.EqualSyntax(e.High, e.Max) { // x[:k:k]
res = e
- empty = isZeroIntLiteral(info, e.High) // x[:0:0]
+ empty = isZeroIntConst(info, e.High) // x[:0:0]
if call, ok := e.High.(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == builtinLen &&
astutil.EqualSyntax(call.Args[0], e.X) {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
index b3c2e562c9..3b32685266 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
@@ -14,20 +14,19 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var SlicesContainsAnalyzer = &analysis.Analyzer{
Name: "slicescontains",
- Doc: analysisinternal.MustExtractDoc(doc, "slicescontains"),
+ Doc: analyzerutil.MustExtractDoc(doc, "slicescontains"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -66,8 +65,6 @@ var SlicesContainsAnalyzer = &analysis.Analyzer{
// TODO(adonovan): Add a check that needle/predicate expression from
// if-statement has no effects. Now the program behavior may change.
func slicescontains(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
@@ -75,9 +72,8 @@ func slicescontains(pass *analysis.Pass) (any, error) {
}
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
)
// check is called for each RangeStmt of this form:
@@ -312,7 +308,7 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// Special case:
// prev="lhs = false" body={ lhs = true; break }
- // => lhs = slices.Contains(...) (or negation)
+ // => lhs = slices.Contains(...) (or its negation)
if assign, ok := body.List[0].(*ast.AssignStmt); ok &&
len(body.List) == 2 &&
assign.Tok == token.ASSIGN &&
@@ -320,13 +316,12 @@ func slicescontains(pass *analysis.Pass) (any, error) {
len(assign.Rhs) == 1 {
// Have: body={ lhs = rhs; break }
-
if prevAssign, ok := prevStmt.(*ast.AssignStmt); ok &&
len(prevAssign.Lhs) == 1 &&
len(prevAssign.Rhs) == 1 &&
astutil.EqualSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
- is[*ast.Ident](assign.Rhs[0]) &&
- info.Uses[assign.Rhs[0].(*ast.Ident)] == builtinTrue {
+ isTrueOrFalse(info, assign.Rhs[0]) ==
+ -isTrueOrFalse(info, prevAssign.Rhs[0]) {
// Have:
// lhs = false
@@ -336,15 +331,14 @@ func slicescontains(pass *analysis.Pass) (any, error) {
//
// TODO(adonovan):
// - support "var lhs bool = false" and variants.
- // - support negation.
- // Both these variants seem quite significant.
// - allow the break to be omitted.
+ neg := cond(isTrueOrFalse(info, assign.Rhs[0]) < 0, "!", "")
report([]analysis.TextEdit{
- // Replace "rhs" of previous assignment by slices.Contains(...)
+ // Replace "rhs" of previous assignment by [!]slices.Contains(...)
{
Pos: prevAssign.Rhs[0].Pos(),
End: prevAssign.Rhs[0].End(),
- NewText: []byte(contains),
+ NewText: []byte(neg + contains),
},
// Delete the loop and preceding space.
{
@@ -388,7 +382,7 @@ func slicescontains(pass *analysis.Pass) (any, error) {
}
}
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
@@ -420,13 +414,19 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// isReturnTrueOrFalse returns nonzero if stmt returns true (+1) or false (-1).
func isReturnTrueOrFalse(info *types.Info, stmt ast.Stmt) int {
if ret, ok := stmt.(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
- if id, ok := ret.Results[0].(*ast.Ident); ok {
- switch info.Uses[id] {
- case builtinTrue:
- return +1
- case builtinFalse:
- return -1
- }
+ return isTrueOrFalse(info, ret.Results[0])
+ }
+ return 0
+}
+
+// isTrueOrFalse returns nonzero if expr is literally true (+1) or false (-1).
+func isTrueOrFalse(info *types.Info, expr ast.Expr) int {
+ if id, ok := expr.(*ast.Ident); ok {
+ switch info.Uses[id] {
+ case builtinTrue:
+ return +1
+ case builtinFalse:
+ return -1
}
}
return 0
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesdelete.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesdelete.go
index b3e063db0f..7b3aa875c0 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesdelete.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesdelete.go
@@ -12,24 +12,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default (not nil-preserving).
var SlicesDeleteAnalyzer = &analysis.Analyzer{
- Name: "slicesdelete",
- Doc: analysisinternal.MustExtractDoc(doc, "slicesdelete"),
- Requires: []*analysis.Analyzer{
- generated.Analyzer,
- inspect.Analyzer,
- },
- Run: slicesdelete,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
+ Name: "slicesdelete",
+ Doc: analyzerutil.MustExtractDoc(doc, "slicesdelete"),
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Run: slicesdelete,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
}
// The slicesdelete pass attempts to replace instances of append(s[:i], s[i+k:]...)
@@ -37,15 +33,12 @@ var SlicesDeleteAnalyzer = &analysis.Analyzer{
// Other variations that will also have suggested replacements include:
// append(s[:i-1], s[i:]...) and append(s[:i+k1], s[i+k2:]) where k2 > k1.
func slicesdelete(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
return nil, nil
}
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info := pass.TypesInfo
report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
insert := func(pos token.Pos, text string) analysis.TextEdit {
@@ -55,7 +48,7 @@ func slicesdelete(pass *analysis.Pass) (any, error) {
return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
}
isIntShadowed := func() bool {
- scope := pass.TypesInfo.Scopes[file].Innermost(call.Lparen)
+ scope := info.Scopes[file].Innermost(call.Lparen)
if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
return true // int type is shadowed
}
@@ -130,7 +123,7 @@ func slicesdelete(pass *analysis.Pass) (any, error) {
}},
})
}
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/sortslice.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/sortslice.go
index 66af16d1f6..e22b8c55f5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/sortslice.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/sortslice.go
@@ -11,20 +11,19 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
// (Not to be confused with go/analysis/passes/sortslice.)
var SlicesSortAnalyzer = &analysis.Analyzer{
Name: "slicessort",
- Doc: analysisinternal.MustExtractDoc(doc, "slicessort"),
+ Doc: analyzerutil.MustExtractDoc(doc, "slicessort"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -52,8 +51,6 @@ var SlicesSortAnalyzer = &analysis.Analyzer{
// - sort.Sort(x) where x has a named slice type whose Less method is the natural order.
// -> sort.Slice(x)
func slicessort(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "sort", "runtime") {
@@ -87,7 +84,7 @@ func slicessort(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curCall)
if isIndex(compare.X, i) && isIndex(compare.Y, j) &&
- fileUses(info, file, "go1.21") {
+ analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
// Have: sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
prefix, importEdits := refactor.AddImport(
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
index 20817520e1..cc59580671 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
@@ -14,9 +14,8 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
@@ -26,9 +25,8 @@ import (
var stditeratorsAnalyzer = &analysis.Analyzer{
Name: "stditerators",
- Doc: analysisinternal.MustExtractDoc(doc, "stditerators"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stditerators"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stditerators,
@@ -89,8 +87,6 @@ var stditeratorsTable = [...]struct {
// iterator for that reason? We don't want to go fix to
// undo optimizations. Do we need a suppression mechanism?
func stditerators(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@@ -116,6 +112,10 @@ func stditerators(pass *analysis.Pass) (any, error) {
//
// for ... { e := x.At(i); use(e) }
//
+ // or
+ //
+ // for ... { if e := x.At(i); cond { use(e) } }
+ //
// then chooseName prefers the name e and additionally
// returns the var's symbol. We'll transform this to:
//
@@ -124,10 +124,11 @@ func stditerators(pass *analysis.Pass) (any, error) {
// which leaves a redundant assignment that a
// subsequent 'forvar' pass will eliminate.
chooseName := func(curBody inspector.Cursor, x ast.Expr, i *types.Var) (string, *types.Var) {
- // Is body { elem := x.At(i); ... } ?
- body := curBody.Node().(*ast.BlockStmt)
- if len(body.List) > 0 {
- if assign, ok := body.List[0].(*ast.AssignStmt); ok &&
+
+ // isVarAssign reports whether stmt has the form v := x.At(i)
+ // and returns the variable if so.
+ isVarAssign := func(stmt ast.Stmt) *types.Var {
+ if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
@@ -138,15 +139,47 @@ func stditerators(pass *analysis.Pass) (any, error) {
astutil.EqualSyntax(ast.Unparen(call.Fun).(*ast.SelectorExpr).X, x) &&
is[*ast.Ident](call.Args[0]) &&
info.Uses[call.Args[0].(*ast.Ident)] == i {
- // Have: { elem := x.At(i); ... }
+ // Have: elem := x.At(i)
id := assign.Lhs[0].(*ast.Ident)
- return id.Name, info.Defs[id].(*types.Var)
+ return info.Defs[id].(*types.Var)
+ }
+ }
+ return nil
+ }
+
+ body := curBody.Node().(*ast.BlockStmt)
+ if len(body.List) > 0 {
+ // Is body { elem := x.At(i); ... } ?
+ if v := isVarAssign(body.List[0]); v != nil {
+ return v.Name(), v
+ }
+
+ // Or { if elem := x.At(i); cond { ... } } ?
+ if ifstmt, ok := body.List[0].(*ast.IfStmt); ok && ifstmt.Init != nil {
+ if v := isVarAssign(ifstmt.Init); v != nil {
+ return v.Name(), v
}
}
}
loop := curBody.Parent().Node()
- return refactor.FreshName(info.Scopes[loop], loop.Pos(), row.elemname), nil
+
+ // Choose a fresh name only if
+ // (a) the preferred name is already declared here, and
+ // (b) there are references to it from the loop body.
+ // TODO(adonovan): this pattern also appears in errorsastype,
+ // and is wanted elsewhere; factor.
+ name := row.elemname
+ if v := lookup(info, curBody, name); v != nil {
+ // is it free in body?
+ for curUse := range index.Uses(v) {
+ if curBody.Contains(curUse) {
+ name = refactor.FreshName(info.Scopes[loop], loop.Pos(), name)
+ break
+ }
+ }
+ }
+ return name, nil
}
// Process each call of x.Len().
@@ -191,7 +224,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
}
// Have: for i := 0; i < x.Len(); i++ { ... }.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- rng = analysisinternal.Range(loop.For, loop.Post.End())
+ rng = astutil.RangeOf(loop.For, loop.Post.End())
indexVar = v
curBody = curFor.ChildAt(edge.ForStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
@@ -234,7 +267,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
// Have: for i := range x.Len() { ... }
// ~~~~~~~~~~~~~
- rng = analysisinternal.Range(loop.Range, loop.X.End())
+ rng = astutil.RangeOf(loop.Range, loop.X.End())
indexVar = info.Defs[id].(*types.Var)
curBody = curRange.ChildAt(edge.RangeStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
@@ -313,7 +346,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
// may be somewhat expensive.)
if v, ok := methodGoVersion(row.pkgpath, row.typename, row.itermethod); !ok {
panic("no version found")
- } else if file := astutil.EnclosingFile(curLenCall); !fileUses(info, file, v.String()) {
+ } else if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curLenCall), v.String()) {
continue nextCall
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
index 56d0ba73cc..56c5d0e3b3 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
@@ -15,9 +15,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@@ -26,9 +25,8 @@ import (
var StringsBuilderAnalyzer = &analysis.Analyzer{
Name: "stringsbuilder",
- Doc: analysisinternal.MustExtractDoc(doc, "stringsbuilder"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stringsbuilder"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -38,8 +36,6 @@ var StringsBuilderAnalyzer = &analysis.Analyzer{
// stringsbuilder replaces string += string in a loop by strings.Builder.
func stringsbuilder(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "strings", "runtime") {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
new file mode 100644
index 0000000000..521c264c51
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
@@ -0,0 +1,580 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modernize
+
+import (
+ "fmt"
+ "go/ast"
+ "go/constant"
+ "go/token"
+ "go/types"
+ "iter"
+ "strconv"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/edge"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
+ "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/goplsexport"
+ "golang.org/x/tools/internal/refactor"
+ "golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
+)
+
+var stringscutAnalyzer = &analysis.Analyzer{
+ Name: "stringscut",
+ Doc: analyzerutil.MustExtractDoc(doc, "stringscut"),
+ Requires: []*analysis.Analyzer{
+ inspect.Analyzer,
+ typeindexanalyzer.Analyzer,
+ },
+ Run: stringscut,
+ URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#stringscut",
+}
+
+func init() {
+ // Export to gopls until this is a published modernizer.
+ goplsexport.StringsCutModernizer = stringscutAnalyzer
+}
+
+// stringscut offers a fix to replace an occurrence of strings.Index{,Byte} with
+// strings.{Cut,Contains}, and similar fixes for functions in the bytes package.
+// Consider some candidate for replacement i := strings.Index(s, substr).
+// The following must hold for a replacement to occur:
+//
+// 1. All instances of i and s must be in one of these forms.
+// Binary expressions:
+// (a): establishing that i < 0: e.g.: i < 0, 0 > i, i == -1, -1 == i
+// (b): establishing that i > -1: e.g.: i >= 0, 0 <= i, i == 0, 0 == i
+//
+// Slice expressions:
+// a: s[:i], s[0:i]
+// b: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
+//
+// 2. There can be no uses of s, substr, or i where they are
+// potentially modified (i.e. in assignments, or function calls with unknown side
+// effects).
+//
+// Then, the replacement involves the following substitutions:
+//
+// 1. Replace "i := strings.Index(s, substr)" with "before, after, ok := strings.Cut(s, substr)"
+//
+// 2. Replace instances of binary expressions (a) with !ok and binary expressions (b) with ok.
+//
+// 3. Replace slice expressions (a) with "before" and slice expressions (b) with after.
+//
+// 4. The assignments to before, after, and ok may use the blank identifier "_" if they are unused.
+//
+// For example:
+//
+// i := strings.Index(s, substr)
+// if i >= 0 {
+// use(s[:i], s[i+len(substr):])
+// }
+//
+// Would become:
+//
+// before, after, ok := strings.Cut(s, substr)
+// if ok {
+// use(before, after)
+// }
+//
+// If the condition involving `i` establishes that i > -1, then we replace it with
+// `if ok“. Variants listed above include i >= 0, i > 0, and i == 0.
+// If the condition is negated (e.g. establishes `i < 0`), we use `if !ok` instead.
+// If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above,
+// then we replace them with before and after.
+//
+// When the index `i` is used only to check for the presence of the substring or byte slice,
+// the suggested fix uses Contains() instead of Cut.
+//
+// For example:
+//
+// i := strings.Index(s, substr)
+// if i >= 0 {
+// return
+// }
+//
+// Would become:
+//
+// found := strings.Contains(s, substr)
+// if found {
+// return
+// }
+func stringscut(pass *analysis.Pass) (any, error) {
+ var (
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
+
+ stringsIndex = index.Object("strings", "Index")
+ stringsIndexByte = index.Object("strings", "IndexByte")
+ bytesIndex = index.Object("bytes", "Index")
+ bytesIndexByte = index.Object("bytes", "IndexByte")
+ )
+
+ for _, obj := range []types.Object{
+ stringsIndex,
+ stringsIndexByte,
+ bytesIndex,
+ bytesIndexByte,
+ } {
+ // (obj may be nil)
+ nextcall:
+ for curCall := range index.Calls(obj) {
+ // Check file version.
+ if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_18) {
+ continue // strings.Index not available in this file
+ }
+ indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
+ obj := typeutil.Callee(info, indexCall)
+ if obj == nil {
+ continue
+ }
+
+ var iIdent *ast.Ident // defining identifier of i var
+ switch ek, idx := curCall.ParentEdge(); ek {
+ case edge.ValueSpec_Values:
+ // Have: var i = strings.Index(...)
+ curName := curCall.Parent().ChildAt(edge.ValueSpec_Names, idx)
+ iIdent = curName.Node().(*ast.Ident)
+ case edge.AssignStmt_Rhs:
+ // Have: i := strings.Index(...)
+ // (Must be i's definition.)
+ curLhs := curCall.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
+ iIdent, _ = curLhs.Node().(*ast.Ident) // may be nil
+ }
+
+ if iIdent == nil {
+ continue
+ }
+ // Inv: iIdent is i's definition. The following would be skipped: 'var i int; i = strings.Index(...)'
+ // Get uses of i.
+ iObj := info.ObjectOf(iIdent)
+ if iObj == nil {
+ continue
+ }
+
+ var (
+ s = indexCall.Args[0]
+ substr = indexCall.Args[1]
+ )
+
+ // Check that there are no statements that alter the value of s
+ // or substr after the call to Index().
+ if !indexArgValid(info, index, s, indexCall.Pos()) ||
+ !indexArgValid(info, index, substr, indexCall.Pos()) {
+ continue nextcall
+ }
+
+ // Next, examine all uses of i. If the only uses are of the
+ // forms mentioned above (e.g. i < 0, i >= 0, s[:i] and s[i +
+ // len(substr)]), then we can replace the call to Index()
+ // with a call to Cut() and use the returned ok, before,
+ // and after variables accordingly.
+ lessZero, greaterNegOne, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr)
+
+ // Either there are no uses of before, after, or ok, or some use
+ // of i does not match our criteria - don't suggest a fix.
+ if lessZero == nil && greaterNegOne == nil && beforeSlice == nil && afterSlice == nil {
+ continue
+ }
+
+ // If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains()
+ isContains := (len(lessZero) > 0 || len(greaterNegOne) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
+
+ scope := iObj.Parent()
+ var (
+ // TODO(adonovan): avoid FreshName when not needed; see errorsastype.
+ okVarName = refactor.FreshName(scope, iIdent.Pos(), "ok")
+ beforeVarName = refactor.FreshName(scope, iIdent.Pos(), "before")
+ afterVarName = refactor.FreshName(scope, iIdent.Pos(), "after")
+ foundVarName = refactor.FreshName(scope, iIdent.Pos(), "found") // for Contains()
+ )
+
+ // If there will be no uses of ok, before, or after, use the
+ // blank identifier instead.
+ if len(lessZero) == 0 && len(greaterNegOne) == 0 {
+ okVarName = "_"
+ }
+ if len(beforeSlice) == 0 {
+ beforeVarName = "_"
+ }
+ if len(afterSlice) == 0 {
+ afterVarName = "_"
+ }
+
+ var edits []analysis.TextEdit
+ replace := func(exprs []ast.Expr, new string) {
+ for _, expr := range exprs {
+ edits = append(edits, analysis.TextEdit{
+ Pos: expr.Pos(),
+ End: expr.End(),
+ NewText: []byte(new),
+ })
+ }
+ }
+ // Get the ident for the call to strings.Index, which could just be
+ // "Index" if the strings package is dot imported.
+ indexCallId := typesinternal.UsedIdent(info, indexCall.Fun)
+ replacedFunc := "Cut"
+ if isContains {
+ replacedFunc = "Contains"
+ replace(lessZero, "!"+foundVarName) // idx < 0 -> !found
+ replace(greaterNegOne, foundVarName) // idx > -1 -> found
+
+ // Replace the assignment with found, and replace the call to
+ // Index or IndexByte with a call to Contains.
+ // i := strings.Index (...)
+ // ----- --------
+ // found := strings.Contains(...)
+ edits = append(edits, analysis.TextEdit{
+ Pos: iIdent.Pos(),
+ End: iIdent.End(),
+ NewText: []byte(foundVarName),
+ }, analysis.TextEdit{
+ Pos: indexCallId.Pos(),
+ End: indexCallId.End(),
+ NewText: []byte("Contains"),
+ })
+ } else {
+ replace(lessZero, "!"+okVarName) // idx < 0 -> !ok
+ replace(greaterNegOne, okVarName) // idx > -1 -> ok
+ replace(beforeSlice, beforeVarName) // s[:idx] -> before
+ replace(afterSlice, afterVarName) // s[idx+k:] -> after
+
+ // Replace the assignment with before, after, ok, and replace
+ // the call to Index or IndexByte with a call to Cut.
+ // i := strings.Index(...)
+ // ----------------- -----
+ // before, after, ok := strings.Cut (...)
+ edits = append(edits, analysis.TextEdit{
+ Pos: iIdent.Pos(),
+ End: iIdent.End(),
+ NewText: fmt.Appendf(nil, "%s, %s, %s", beforeVarName, afterVarName, okVarName),
+ }, analysis.TextEdit{
+ Pos: indexCallId.Pos(),
+ End: indexCallId.End(),
+ NewText: []byte("Cut"),
+ })
+ }
+
+ // Calls to IndexByte have a byte as their second arg, which
+ // must be converted to a string or []byte to be a valid arg for Cut/Contains.
+ if obj.Name() == "IndexByte" {
+ switch obj.Pkg().Name() {
+ case "strings":
+ searchByteVal := info.Types[substr].Value
+ if searchByteVal == nil {
+ // substr is a variable, e.g. substr := byte('b')
+ // use string(substr)
+ edits = append(edits, []analysis.TextEdit{
+ {
+ Pos: substr.Pos(),
+ NewText: []byte("string("),
+ },
+ {
+ Pos: substr.End(),
+ NewText: []byte(")"),
+ },
+ }...)
+ } else {
+ // substr is a byte constant
+ val, _ := constant.Int64Val(searchByteVal) // inv: must be a valid byte
+ // strings.Cut/Contains requires a string, so convert byte literal to string literal; e.g. 'a' -> "a", 55 -> "7"
+ edits = append(edits, analysis.TextEdit{
+ Pos: substr.Pos(),
+ End: substr.End(),
+ NewText: strconv.AppendQuote(nil, string(byte(val))),
+ })
+ }
+ case "bytes":
+ // bytes.Cut/Contains requires a []byte, so wrap substr in a []byte{}
+ edits = append(edits, []analysis.TextEdit{
+ {
+ Pos: substr.Pos(),
+ NewText: []byte("[]byte{"),
+ },
+ {
+ Pos: substr.End(),
+ NewText: []byte("}"),
+ },
+ }...)
+ }
+ }
+ pass.Report(analysis.Diagnostic{
+ Pos: indexCall.Fun.Pos(),
+ End: indexCall.Fun.End(),
+ Message: fmt.Sprintf("%s.%s can be simplified using %s.%s",
+ obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
+ Category: "stringscut",
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Simplify %s.%s call using %s.%s", obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
+ TextEdits: edits,
+ }},
+ })
+ }
+ }
+
+ return nil, nil
+}
+
+// indexArgValid reports whether expr is a valid strings.Index(_, _) arg
+// for the transformation. An arg is valid iff it is:
+// - constant;
+// - a local variable with no modifying uses after the Index() call; or
+// - []byte(x) where x is also valid by this definition.
+// All other expressions are assumed not referentially transparent,
+// so we cannot be sure that all uses are safe to replace.
+func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afterPos token.Pos) bool {
+ tv := info.Types[expr]
+ if tv.Value != nil {
+ return true // constant
+ }
+ switch expr := expr.(type) {
+ case *ast.CallExpr:
+ return types.Identical(tv.Type, byteSliceType) &&
+ indexArgValid(info, index, expr.Args[0], afterPos) // check s in []byte(s)
+ case *ast.Ident:
+ sObj := info.Uses[expr]
+ sUses := index.Uses(sObj)
+ return !hasModifyingUses(info, sUses, afterPos)
+ default:
+ // For now, skip instances where s or substr are not
+ // identifers, basic lits, or call expressions of the form
+ // []byte(s).
+ // TODO(mkalil): Handle s and substr being expressions like ptr.field[i].
+ // From adonovan: We'd need to analyze s and substr to see
+ // whether they are referentially transparent, and if not,
+ // analyze all code between declaration and use and see if
+ // there are statements or expressions with potential side
+ // effects.
+ return false
+ }
+}
+
+// checkIdxUses inspects the uses of i to make sure they match certain criteria that
+// allows us to suggest a modernization. If all uses of i, s and substr match
+// one of the following four valid formats, it returns a list of occurrences for
+// each format. If any of the uses do not match one of the formats, return nil
+// for all values, since we should not offer a replacement.
+// 1. lessZero - a condition involving i establishing that i is negative (e.g. i < 0, 0 > i, i == -1, -1 == i)
+// 2. greaterNegOne - a condition involving i establishing that i is non-negative (e.g. i >= 0, 0 <= i, i == 0, 0 == i)
+// 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i]
+// 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
+func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (lessZero, greaterNegOne, beforeSlice, afterSlice []ast.Expr) {
+ use := func(cur inspector.Cursor) bool {
+ ek, _ := cur.ParentEdge()
+ n := cur.Parent().Node()
+ switch ek {
+ case edge.BinaryExpr_X, edge.BinaryExpr_Y:
+ check := n.(*ast.BinaryExpr)
+ switch checkIdxComparison(info, check) {
+ case -1:
+ lessZero = append(lessZero, check)
+ return true
+ case 1:
+ greaterNegOne = append(greaterNegOne, check)
+ return true
+ }
+ // Check does not establish that i < 0 or i > -1.
+ // Might be part of an outer slice expression like s[i + k]
+ // which requires a different check.
+ // Check that the thing being sliced is s and that the slice
+ // doesn't have a max index.
+ if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok &&
+ sameObject(info, s, slice.X) &&
+ slice.Max == nil {
+ if isBeforeSlice(info, ek, slice) {
+ beforeSlice = append(beforeSlice, slice)
+ return true
+ } else if isAfterSlice(info, ek, slice, substr) {
+ afterSlice = append(afterSlice, slice)
+ return true
+ }
+ }
+ case edge.SliceExpr_Low, edge.SliceExpr_High:
+ slice := n.(*ast.SliceExpr)
+ // Check that the thing being sliced is s and that the slice doesn't
+ // have a max index.
+ if sameObject(info, s, slice.X) && slice.Max == nil {
+ if isBeforeSlice(info, ek, slice) {
+ beforeSlice = append(beforeSlice, slice)
+ return true
+ } else if isAfterSlice(info, ek, slice, substr) {
+ afterSlice = append(afterSlice, slice)
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ for curIdent := range uses {
+ if !use(curIdent) {
+ return nil, nil, nil, nil
+ }
+ }
+ return lessZero, greaterNegOne, beforeSlice, afterSlice
+}
+
+// hasModifyingUses reports whether any of the uses involve potential
+// modifications. Uses involving assignments before the "afterPos" won't be
+// considered.
+func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
+ for curUse := range uses {
+ ek, _ := curUse.ParentEdge()
+ if ek == edge.AssignStmt_Lhs {
+ if curUse.Node().Pos() <= afterPos {
+ continue
+ }
+ assign := curUse.Parent().Node().(*ast.AssignStmt)
+ if sameObject(info, assign.Lhs[0], curUse.Node().(*ast.Ident)) {
+ // Modifying use because we are reassigning the value of the object.
+ return true
+ }
+ } else if ek == edge.UnaryExpr_X &&
+ curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
+ // Modifying use because we might be passing the object by reference (an explicit &).
+ // We can ignore the case where we have a method call on the expression (which
+ // has an implicit &) because we know the type of s and substr are strings
+ // which cannot have methods on them.
+ return true
+ }
+ }
+ return false
+}
+
+// checkIdxComparison reports whether the check establishes that i is negative
+// or non-negative. It returns -1 in the first case, 1 in the second, and 0 if
+// we can confirm neither condition. We assume that a check passed to
+// checkIdxComparison has i as one of its operands.
+func checkIdxComparison(info *types.Info, check *ast.BinaryExpr) int {
+ // Check establishes that i is negative.
+ // e.g.: i < 0, 0 > i, i == -1, -1 == i
+ if check.Op == token.LSS && (isNegativeConst(info, check.Y) || isZeroIntConst(info, check.Y)) || //i < (0 or neg)
+ check.Op == token.GTR && (isNegativeConst(info, check.X) || isZeroIntConst(info, check.X)) || // (0 or neg) > i
+ check.Op == token.LEQ && (isNegativeConst(info, check.Y)) || //i <= (neg)
+ check.Op == token.GEQ && (isNegativeConst(info, check.X)) || // (neg) >= i
+ check.Op == token.EQL &&
+ (isNegativeConst(info, check.X) || isNegativeConst(info, check.Y)) { // i == neg; neg == i
+ return -1
+ }
+ // Check establishes that i is non-negative.
+ // e.g.: i >= 0, 0 <= i, i == 0, 0 == i
+ if check.Op == token.GTR && (isNonNegativeConst(info, check.Y) || isIntLiteral(info, check.Y, -1)) || // i > (non-neg or -1)
+ check.Op == token.LSS && (isNonNegativeConst(info, check.X) || isIntLiteral(info, check.X, -1)) || // (non-neg or -1) < i
+ check.Op == token.GEQ && isNonNegativeConst(info, check.Y) || // i >= (non-neg)
+ check.Op == token.LEQ && isNonNegativeConst(info, check.X) || // (non-neg) <= i
+ check.Op == token.EQL &&
+ (isNonNegativeConst(info, check.X) || isNonNegativeConst(info, check.Y)) { // i == non-neg; non-neg == i
+ return 1
+ }
+ return 0
+}
+
+// isNegativeConst returns true if the expr is a const int with value < zero.
+func isNegativeConst(info *types.Info, expr ast.Expr) bool {
+ if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
+ if v, ok := constant.Int64Val(tv.Value); ok {
+ return v < 0
+ }
+ }
+ return false
+}
+
+// isNoneNegativeConst returns true if the expr is a const int with value >= zero.
+func isNonNegativeConst(info *types.Info, expr ast.Expr) bool {
+ if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
+ if v, ok := constant.Int64Val(tv.Value); ok {
+ return v >= 0
+ }
+ }
+ return false
+}
+
+// isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i].
+func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool {
+ return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low))
+}
+
+// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
+// or s[i + k:] where k is a const is equal to len(substr).
+func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
+ lowExpr, ok := slice.Low.(*ast.BinaryExpr)
+ if !ok || slice.High != nil {
+ return false
+ }
+ // Returns true if the expression is a call to len(substr).
+ isLenCall := func(expr ast.Expr) bool {
+ call, ok := expr.(*ast.CallExpr)
+ if !ok || len(call.Args) != 1 {
+ return false
+ }
+ return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
+ }
+
+ // Handle len([]byte(substr))
+ if is[*ast.CallExpr](substr) {
+ call := substr.(*ast.CallExpr)
+ tv := info.Types[call.Fun]
+ if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
+ // Only one arg in []byte conversion.
+ substr = call.Args[0]
+ }
+ }
+ substrLen := -1
+ substrVal := info.Types[substr].Value
+ if substrVal != nil {
+ switch substrVal.Kind() {
+ case constant.String:
+ substrLen = len(constant.StringVal(substrVal))
+ case constant.Int:
+ // constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a')
+ // or a numeric byte literal, e.g. bytes.IndexByte(_, 65)
+ substrLen = 1
+ }
+ }
+
+ switch ek {
+ case edge.BinaryExpr_X:
+ kVal := info.Types[lowExpr.Y].Value
+ if kVal == nil {
+ // i + len(substr)
+ return lowExpr.Op == token.ADD && isLenCall(lowExpr.Y)
+ } else {
+ // i + k
+ kInt, ok := constant.Int64Val(kVal)
+ return ok && substrLen == int(kInt)
+ }
+ case edge.BinaryExpr_Y:
+ kVal := info.Types[lowExpr.X].Value
+ if kVal == nil {
+ // len(substr) + i
+ return lowExpr.Op == token.ADD && isLenCall(lowExpr.X)
+ } else {
+ // k + i
+ kInt, ok := constant.Int64Val(kVal)
+ return ok && substrLen == int(kInt)
+ }
+ }
+ return false
+}
+
+// sameObject reports whether we know that the expressions resolve to the same object.
+func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool {
+ if ident1, ok := expr1.(*ast.Ident); ok {
+ if ident2, ok := expr2.(*ast.Ident); ok {
+ uses1, ok1 := info.Uses[ident1]
+ uses2, ok2 := info.Uses[ident2]
+ return ok1 && ok2 && uses1 == uses2
+ }
+ }
+ return false
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
index 9e76f953ed..7dc11308dd 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
@@ -12,22 +12,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var StringsCutPrefixAnalyzer = &analysis.Analyzer{
Name: "stringscutprefix",
- Doc: analysisinternal.MustExtractDoc(doc, "stringscutprefix"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stringscutprefix"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -56,12 +54,9 @@ var StringsCutPrefixAnalyzer = &analysis.Analyzer{
// Variants:
// - bytes.HasPrefix/HasSuffix usage as pattern 1.
func stringscutprefix(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
stringsTrimPrefix = index.Object("strings", "TrimPrefix")
bytesTrimPrefix = index.Object("bytes", "TrimPrefix")
@@ -72,7 +67,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
return nil, nil
}
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.20") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_20) {
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
@@ -206,6 +201,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
+ // TODO(adonovan): avoid FreshName when not needed; see errorsastype.
okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
// Have one of:
// if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsseq.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsseq.go
index ef2b546364..d02a53230f 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsseq.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsseq.go
@@ -13,19 +13,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var StringsSeqAnalyzer = &analysis.Analyzer{
Name: "stringsseq",
- Doc: analysisinternal.MustExtractDoc(doc, "stringsseq"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stringsseq"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -48,12 +46,9 @@ var StringsSeqAnalyzer = &analysis.Analyzer{
// - bytes.SplitSeq
// - bytes.FieldsSeq
func stringsseq(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
stringsSplit = index.Object("strings", "Split")
stringsFields = index.Object("strings", "Fields")
@@ -64,7 +59,7 @@ func stringsseq(pass *analysis.Pass) (any, error) {
return nil, nil
}
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/testingcontext.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/testingcontext.go
index 558cf142dd..939330521c 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/testingcontext.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/testingcontext.go
@@ -17,19 +17,18 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var TestingContextAnalyzer = &analysis.Analyzer{
Name: "testingcontext",
- Doc: analysisinternal.MustExtractDoc(doc, "testingcontext"),
+ Doc: analyzerutil.MustExtractDoc(doc, "testingcontext"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -56,8 +55,6 @@ var TestingContextAnalyzer = &analysis.Analyzer{
// - the call is within a test or subtest function
// - the relevant testing.{T,B,F} is named and not shadowed at the call
func testingContext(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@@ -137,7 +134,7 @@ calls:
testObj = isTestFn(info, n)
}
}
- if testObj != nil && fileUses(info, astutil.EnclosingFile(cur), "go1.24") {
+ if testObj != nil && analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_24) {
// Have a test function. Check that we can resolve the relevant
// testing.{T,B,F} at the current position.
if _, obj := lhs[0].Parent().LookupParent(testObj.Name(), lhs[0].Pos()); obj == testObj {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
index b890f334ba..19564c69b6 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
@@ -14,19 +14,18 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/analysisinternal/generated"
- typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var WaitGroupAnalyzer = &analysis.Analyzer{
Name: "waitgroup",
- Doc: analysisinternal.MustExtractDoc(doc, "waitgroup"),
+ Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
Requires: []*analysis.Analyzer{
- generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@@ -61,8 +60,6 @@ var WaitGroupAnalyzer = &analysis.Analyzer{
// other effects, or blocked, or if WaitGroup.Go propagated panics
// from child to parent goroutine, the argument would be different.)
func waitgroup(pass *analysis.Pass) (any, error) {
- skipGenerated(pass)
-
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@@ -128,7 +125,7 @@ func waitgroup(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curAddCall)
- if !fileUses(info, file, "go1.25") {
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_25) {
continue
}
tokFile := pass.Fset.File(file.Pos())
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go
index 2b5a7c8037..6b37295187 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "nilfunc",
- Doc: analysisinternal.MustExtractDoc(doc, "nilfunc"),
+ Doc: analyzerutil.MustExtractDoc(doc, "nilfunc"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
index 910ffe70d7..1eac2589bf 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
@@ -21,7 +21,7 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/fmtstr"
"golang.org/x/tools/internal/typeparams"
@@ -38,7 +38,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "printf",
- Doc: analysisinternal.MustExtractDoc(doc, "printf"),
+ Doc: analyzerutil.MustExtractDoc(doc, "printf"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -612,7 +612,7 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
// breaking existing tests and CI scripts.
if idx == len(call.Args)-1 &&
fileVersion != "" && // fail open
- versions.AtLeast(fileVersion, "go1.24") {
+ versions.AtLeast(fileVersion, versions.Go1_24) {
pass.Report(analysis.Diagnostic{
Pos: formatArg.Pos(),
@@ -662,7 +662,7 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
anyIndex = true
}
rng := opRange(formatArg, op)
- if !okPrintfArg(pass, call, rng, &maxArgIndex, firstArg, name, op) {
+ if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
// One error per format is enough.
return
}
@@ -694,9 +694,9 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
// such as the position of the %v substring of "...%v...".
func opRange(formatArg ast.Expr, op *fmtstr.Operation) analysis.Range {
if lit, ok := formatArg.(*ast.BasicLit); ok {
- start, end, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
+ rng, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
if err == nil {
- return analysisinternal.Range(start, end) // position of "%v"
+ return rng // position of "%v"
}
}
return formatArg // entire format string
@@ -707,6 +707,7 @@ type printfArgType int
const (
argBool printfArgType = 1 << iota
+ argByte
argInt
argRune
argString
@@ -751,7 +752,7 @@ var printVerbs = []printVerb{
{'o', sharpNumFlag, argInt | argPointer},
{'O', sharpNumFlag, argInt | argPointer},
{'p', "-#", argPointer},
- {'q', " -+.0#", argRune | argInt | argString},
+ {'q', " -+.0#", argRune | argInt | argString}, // note: when analyzing go1.26 code, argInt => argByte
{'s', " -+.0", argString},
{'t', "-", argBool},
{'T', "-", anyType},
@@ -765,7 +766,7 @@ var printVerbs = []printVerb{
// okPrintfArg compares the operation to the arguments actually present,
// reporting any discrepancies it can discern, maxArgIndex was the index of the highest used index.
// If the final argument is ellipsissed, there's little it can do for that.
-func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
+func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
verb := operation.Verb.Verb
var v printVerb
found := false
@@ -777,6 +778,13 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma
}
}
+ // When analyzing go1.26 code, rune and byte are the only %q integers (#72850).
+ if verb == 'q' &&
+ fileVersion != "" && // fail open
+ versions.AtLeast(fileVersion, versions.Go1_26) {
+ v.typ = argRune | argByte | argString
+ }
+
// Could verb's arg implement fmt.Formatter?
// Skip check for the %w verb, which requires an error.
formatter := false
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go
index f7e50f98a9..2cc5c23f12 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/types.go
@@ -204,8 +204,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
case *types.Struct:
// report whether all the elements of the struct match the expected type. For
// instance, with "%d" all the elements must be printable with the "%d" format.
- for i := 0; i < typ.NumFields(); i++ {
- typf := typ.Field(i)
+ for typf := range typ.Fields() {
if !m.match(typf.Type(), false) {
return false
}
@@ -228,14 +227,20 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
types.Bool:
return m.t&argBool != 0
+ case types.Byte:
+ return m.t&(argInt|argByte) != 0
+
+ case types.Rune, types.UntypedRune:
+ return m.t&(argInt|argRune) != 0
+
case types.UntypedInt,
types.Int,
types.Int8,
types.Int16,
- types.Int32,
+ // see case Rune for int32
types.Int64,
types.Uint,
- types.Uint8,
+ // see case Byte for uint8
types.Uint16,
types.Uint32,
types.Uint64,
@@ -259,9 +264,6 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
case types.UnsafePointer:
return m.t&(argPointer|argInt) != 0
- case types.UntypedRune:
- return m.t&(argInt|argRune) != 0
-
case types.UntypedNil:
return false
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go
index 934f3913c2..174c27109e 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go
@@ -19,7 +19,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -29,7 +29,7 @@ var doc string
// Analyzer describes sigchanyzer analysis function detector.
var Analyzer = &analysis.Analyzer{
Name: "sigchanyzer",
- Doc: analysisinternal.MustExtractDoc(doc, "sigchanyzer"),
+ Doc: analyzerutil.MustExtractDoc(doc, "sigchanyzer"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go
index 2cb91c7329..4afbe04684 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go
@@ -19,7 +19,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -29,7 +29,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "slog",
- Doc: analysisinternal.MustExtractDoc(doc, "slog"),
+ Doc: analyzerutil.MustExtractDoc(doc, "slog"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -168,7 +168,7 @@ func isAttr(t types.Type) bool {
// "slog.Logger.With" (instead of "(*log/slog.Logger).With")
func shortName(fn *types.Func) string {
var r string
- if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
+ if recv := fn.Signature().Recv(); recv != nil {
if _, named := typesinternal.ReceiverNamed(recv); named != nil {
r = named.Obj().Name()
} else {
@@ -188,7 +188,7 @@ func kvFuncSkipArgs(fn *types.Func) (int, bool) {
return 0, false
}
var recvName string // by default a slog package function
- if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
+ if recv := fn.Signature().Recv(); recv != nil {
_, named := typesinternal.ReceiverNamed(recv)
if named == nil {
return 0, false // anon struct/interface
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go
index ca303ae5c1..b68385b242 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go
@@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
@@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stdmethods",
- Doc: analysisinternal.MustExtractDoc(doc, "stdmethods"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stdmethods"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -131,12 +131,12 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
}
// Do the =s (if any) all match?
- if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
+ if !matchParams(expect.args, args, "=") || !matchParams(expect.results, results, "=") {
return
}
// Everything must match.
- if !matchParams(pass, expect.args, args, "") || !matchParams(pass, expect.results, results, "") {
+ if !matchParams(expect.args, args, "") || !matchParams(expect.results, results, "") {
expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
if len(expect.results) == 1 {
expectFmt += " " + argjoin(expect.results)
@@ -168,7 +168,7 @@ func argjoin(x []string) string {
}
// Does each type in expect with the given prefix match the corresponding type in actual?
-func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, prefix string) bool {
+func matchParams(expect []string, actual *types.Tuple, prefix string) bool {
for i, x := range expect {
if !strings.HasPrefix(x, prefix) {
continue
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go
index 19c72d2cf9..0cbae68898 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go
@@ -14,7 +14,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
@@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stringintconv",
- Doc: analysisinternal.MustExtractDoc(doc, "stringintconv"),
+ Doc: analyzerutil.MustExtractDoc(doc, "stringintconv"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go
index eba4e56bb0..e38c266afe 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -30,7 +30,7 @@ func init() {
var Analyzer = &analysis.Analyzer{
Name: "testinggoroutine",
- Doc: analysisinternal.MustExtractDoc(doc, "testinggoroutine"),
+ Doc: analyzerutil.MustExtractDoc(doc, "testinggoroutine"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go
index db2e5f76d1..4b68a789cf 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/util.go
@@ -36,7 +36,7 @@ func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) *
// isMethodNamed returns true if f is a method defined
// in package with the path pkgPath with a name in names.
//
-// (Unlike [analysisinternal.IsMethodNamed], it ignores the receiver type name.)
+// (Unlike [analysis.IsMethodNamed], it ignores the receiver type name.)
func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
@@ -44,7 +44,7 @@ func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
return false
}
- if f.Type().(*types.Signature).Recv() == nil {
+ if f.Signature().Recv() == nil {
return false
}
return slices.Contains(names, f.Name())
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go
index a0ed5ab14e..1f33df8403 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go
@@ -15,7 +15,8 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -24,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "tests",
- Doc: analysisinternal.MustExtractDoc(doc, "tests"),
+ Doc: analyzerutil.MustExtractDoc(doc, "tests"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests",
Run: run,
}
@@ -464,7 +465,7 @@ func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 {
// Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters.
// We have currently decided to also warn before compilation/package loading. This can help users in IDEs.
- pass.ReportRangef(analysisinternal.Range(tparams.Opening, tparams.Closing),
+ pass.ReportRangef(astutil.RangeOf(tparams.Opening, tparams.Closing),
"%s has type parameters: it will not be run by go test as a %sXXX function",
fn.Name.Name, prefix)
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go
index 45b6822c17..8353c1efa9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go
@@ -18,7 +18,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -30,7 +30,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "timeformat",
- Doc: analysisinternal.MustExtractDoc(doc, "timeformat"),
+ Doc: analyzerutil.MustExtractDoc(doc, "timeformat"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -39,7 +39,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (any, error) {
// Note: (time.Time).Format is a method and can be a typeutil.Callee
// without directly importing "time". So we cannot just skip this package
- // when !analysisinternal.Imports(pass.Pkg, "time").
+ // when !analysis.Imports(pass.Pkg, "time").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
index 4de48c8393..38eb0b1063 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
@@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -22,7 +22,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unmarshal",
- Doc: analysisinternal.MustExtractDoc(doc, "unmarshal"),
+ Doc: analyzerutil.MustExtractDoc(doc, "unmarshal"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -39,7 +39,7 @@ func run(pass *analysis.Pass) (any, error) {
// Note: (*"encoding/json".Decoder).Decode, (* "encoding/gob".Decoder).Decode
// and (* "encoding/xml".Decoder).Decode are methods and can be a typeutil.Callee
// without directly importing their packages. So we cannot just skip this package
- // when !analysisinternal.Imports(pass.Pkg, "encoding/...").
+ // when !analysis.Imports(pass.Pkg, "encoding/...").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
@@ -57,7 +57,7 @@ func run(pass *analysis.Pass) (any, error) {
// Classify the callee (without allocating memory).
argidx := -1
- recv := fn.Type().(*types.Signature).Recv()
+ recv := fn.Signature().Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
// "encoding/xml".Unmarshal
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go
index 668a335299..532f38fe91 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
)
@@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unreachable",
- Doc: analysisinternal.MustExtractDoc(doc, "unreachable"),
+ Doc: analyzerutil.MustExtractDoc(doc, "unreachable"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go
index 24ff723390..ce785725e3 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unsafeptr",
- Doc: analysisinternal.MustExtractDoc(doc, "unsafeptr"),
+ Doc: analyzerutil.MustExtractDoc(doc, "unsafeptr"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
index 57ad4f0769..bd32d58690 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
@@ -25,7 +25,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/astutil"
)
//go:embed doc.go
@@ -33,7 +34,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
- Doc: analysisinternal.MustExtractDoc(doc, "unusedresult"),
+ Doc: analyzerutil.MustExtractDoc(doc, "unusedresult"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@@ -149,11 +150,11 @@ func run(pass *analysis.Pass) (any, error) {
if !ok {
return // e.g. var or builtin
}
- if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
+ if sig := fn.Signature(); sig.Recv() != nil {
// method (e.g. foo.String())
if types.Identical(sig, sigNoArgsStringResult) {
if stringMethods[fn.Name()] {
- pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
+ pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of (%s).%s call not used",
sig.Recv().Type(), fn.Name())
}
@@ -161,7 +162,7 @@ func run(pass *analysis.Pass) (any, error) {
} else {
// package-level function (e.g. fmt.Errorf)
if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
- pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
+ pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of %s.%s call not used",
fn.Pkg().Path(), fn.Name())
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go
index 88e4cc8677..c2e20521e9 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go
@@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "waitgroup",
- Doc: analysisinternal.MustExtractDoc(doc, "waitgroup"),
+ Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
index b407bc7791..0180a341e5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
@@ -49,7 +49,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
- "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysis/driverutil"
"golang.org/x/tools/internal/facts"
)
@@ -183,16 +183,18 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
// but apply all fixes from the root actions.
// Convert results to form needed by ApplyFixes.
- fixActions := make([]analysisflags.FixAction, len(results))
+ fixActions := make([]driverutil.FixAction, len(results))
for i, res := range results {
- fixActions[i] = analysisflags.FixAction{
+ fixActions[i] = driverutil.FixAction{
Name: res.a.Name,
+ Pkg: res.pkg,
+ Files: res.files,
FileSet: fset,
- ReadFileFunc: os.ReadFile,
+ ReadFileFunc: os.ReadFile, // TODO(adonovan): respect overlays
Diagnostics: res.diagnostics,
}
}
- if err := analysisflags.ApplyFixes(fixActions, false); err != nil {
+ if err := driverutil.ApplyFixes(fixActions, analysisflags.Diff, false); err != nil {
// Fail when applying fixes failed.
log.Print(err)
exit = 1
@@ -209,7 +211,7 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
if analysisflags.JSON {
// JSON output
- tree := make(analysisflags.JSONTree)
+ tree := make(driverutil.JSONTree)
for _, res := range results {
tree.Add(fset, id, res.a.Name, res.diagnostics, res.err)
}
@@ -225,7 +227,7 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
}
for _, res := range results {
for _, diag := range res.diagnostics {
- analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
+ driverutil.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
exit = 1
}
}
@@ -428,7 +430,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
ResultOf: inputs,
Report: func(d analysis.Diagnostic) {
// Unitchecker doesn't apply fixes, but it does report them in the JSON output.
- if err := analysisinternal.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
+ if err := driverutil.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
// Since we have diagnostics, the exit code will be nonzero,
// so logging these errors is sufficient.
log.Println(err)
@@ -444,14 +446,14 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
Module: module,
}
- pass.ReadFile = analysisinternal.CheckedReadFile(pass, os.ReadFile)
+ pass.ReadFile = driverutil.CheckedReadFile(pass, os.ReadFile)
t0 := time.Now()
act.result, act.err = a.Run(pass)
if act.err == nil { // resolve URLs on diagnostics.
for i := range act.diagnostics {
- if url, uerr := analysisflags.ResolveURL(a, act.diagnostics[i]); uerr == nil {
+ if url, uerr := driverutil.ResolveURL(a, act.diagnostics[i]); uerr == nil {
act.diagnostics[i].URL = url
} else {
act.err = uerr // keep the last error
@@ -482,9 +484,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
results := make([]result, len(analyzers))
for i, a := range analyzers {
act := actions[a]
- results[i].a = a
- results[i].err = act.err
- results[i].diagnostics = act.diagnostics
+ results[i] = result{pkg, files, a, act.diagnostics, act.err}
}
data := facts.Encode()
@@ -499,6 +499,8 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
}
type result struct {
+ pkg *types.Package
+ files []*ast.File
a *analysis.Analyzer
diagnostics []analysis.Diagnostic
err error
diff --git a/src/cmd/vendor/golang.org/x/tools/go/ast/inspector/cursor.go b/src/cmd/vendor/golang.org/x/tools/go/ast/inspector/cursor.go
index 7e72d3c284..fc9bbc714c 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/ast/inspector/cursor.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/ast/inspector/cursor.go
@@ -467,7 +467,9 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
// This algorithm could be implemented using c.Inspect,
// but it is about 2.5x slower.
- best := int32(-1) // push index of latest (=innermost) node containing range
+ // best is the push-index of the latest (=innermost) node containing range.
+ // (Beware: latest is not always innermost because FuncDecl.{Name,Type} overlap.)
+ best := int32(-1)
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
@@ -481,6 +483,19 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
continue
}
} else {
+ // Edge case: FuncDecl.Name and .Type overlap:
+ // Don't update best from Name to FuncDecl.Type.
+ //
+ // The condition can be read as:
+ // - n is FuncType
+ // - n.parent is FuncDecl
+ // - best is strictly beneath the FuncDecl
+ if ev.typ == 1< ev.parent {
+ continue
+ }
+
nodeEnd = n.End()
if n.Pos() > start {
break // disjoint, after; stop
diff --git a/src/cmd/vendor/golang.org/x/tools/go/cfg/builder.go b/src/cmd/vendor/golang.org/x/tools/go/cfg/builder.go
index ac4d63c400..f16cd42309 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/cfg/builder.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/cfg/builder.go
@@ -13,7 +13,7 @@ import (
)
type builder struct {
- cfg *CFG
+ blocks []*Block
mayReturn func(*ast.CallExpr) bool
current *Block
lblocks map[string]*lblock // labeled blocks
@@ -32,12 +32,18 @@ start:
*ast.SendStmt,
*ast.IncDecStmt,
*ast.GoStmt,
- *ast.DeferStmt,
*ast.EmptyStmt,
*ast.AssignStmt:
// No effect on control flow.
b.add(s)
+ case *ast.DeferStmt:
+ b.add(s)
+ // Assume conservatively that this behaves like:
+ // defer func() { recover() }
+ // so any subsequent panic may act like a return.
+ b.current.returns = true
+
case *ast.ExprStmt:
b.add(s)
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
@@ -64,6 +70,7 @@ start:
goto start // effectively: tailcall stmt(g, s.Stmt, label)
case *ast.ReturnStmt:
+ b.current.returns = true
b.add(s)
b.current = b.newBlock(KindUnreachable, s)
@@ -483,14 +490,13 @@ func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock
// It does not automatically become the current block.
// comment is an optional string for more readable debugging output.
func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
- g := b.cfg
block := &Block{
- Index: int32(len(g.Blocks)),
+ Index: int32(len(b.blocks)),
Kind: kind,
Stmt: stmt,
}
block.Succs = block.succs2[:0]
- g.Blocks = append(g.Blocks, block)
+ b.blocks = append(b.blocks, block)
return block
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go b/src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go
index 29a39f698c..38aba77c29 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/cfg/cfg.go
@@ -47,13 +47,16 @@ import (
"go/ast"
"go/format"
"go/token"
+
+ "golang.org/x/tools/internal/cfginternal"
)
// A CFG represents the control-flow graph of a single function.
//
// The entry point is Blocks[0]; there may be multiple return blocks.
type CFG struct {
- Blocks []*Block // block[0] is entry; order otherwise undefined
+ Blocks []*Block // block[0] is entry; order otherwise undefined
+ noreturn bool // function body lacks a reachable return statement
}
// A Block represents a basic block: a list of statements and
@@ -67,12 +70,13 @@ type CFG struct {
// an [ast.Expr], Succs[0] is the successor if the condition is true, and
// Succs[1] is the successor if the condition is false.
type Block struct {
- Nodes []ast.Node // statements, expressions, and ValueSpecs
- Succs []*Block // successor nodes in the graph
- Index int32 // index within CFG.Blocks
- Live bool // block is reachable from entry
- Kind BlockKind // block kind
- Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
+ Nodes []ast.Node // statements, expressions, and ValueSpecs
+ Succs []*Block // successor nodes in the graph
+ Index int32 // index within CFG.Blocks
+ Live bool // block is reachable from entry
+ returns bool // block contains return or defer (which may recover and return)
+ Kind BlockKind // block kind
+ Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
succs2 [2]*Block // underlying array for Succs
}
@@ -141,14 +145,14 @@ func (kind BlockKind) String() string {
func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
b := builder{
mayReturn: mayReturn,
- cfg: new(CFG),
}
b.current = b.newBlock(KindBody, body)
b.stmt(body)
- // Compute liveness (reachability from entry point), breadth-first.
- q := make([]*Block, 0, len(b.cfg.Blocks))
- q = append(q, b.cfg.Blocks[0]) // entry point
+ // Compute liveness (reachability from entry point),
+ // breadth-first, marking Block.Live flags.
+ q := make([]*Block, 0, len(b.blocks))
+ q = append(q, b.blocks[0]) // entry point
for len(q) > 0 {
b := q[len(q)-1]
q = q[:len(q)-1]
@@ -162,12 +166,30 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
// Does control fall off the end of the function's body?
// Make implicit return explicit.
if b.current != nil && b.current.Live {
+ b.current.returns = true
b.add(&ast.ReturnStmt{
Return: body.End() - 1,
})
}
- return b.cfg
+ // Is any return (or defer+recover) block reachable?
+ noreturn := true
+ for _, bl := range b.blocks {
+ if bl.Live && bl.returns {
+ noreturn = false
+ break
+ }
+ }
+
+ return &CFG{Blocks: b.blocks, noreturn: noreturn}
+}
+
+// isNoReturn reports whether the function has no reachable return.
+// TODO(adonovan): add (*CFG).NoReturn to public API.
+func isNoReturn(_cfg any) bool { return _cfg.(*CFG).noreturn }
+
+func init() {
+ cfginternal.IsNoReturn = isNoReturn // expose to ctrlflow analyzer
}
func (b *Block) String() string {
@@ -187,6 +209,14 @@ func (b *Block) comment(fset *token.FileSet) string {
//
// When control falls off the end of the function, the ReturnStmt is synthetic
// and its [ast.Node.End] position may be beyond the end of the file.
+//
+// A function that contains no return statement (explicit or implied)
+// may yet return normally, and may even return a nonzero value. For example:
+//
+// func() (res any) {
+// defer func() { res = recover() }()
+// panic(123)
+// }
func (b *Block) Return() (ret *ast.ReturnStmt) {
if len(b.Nodes) > 0 {
ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt)
diff --git a/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
index 6c0c74968f..6646bf5508 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
@@ -249,7 +249,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
case *types.Func:
// A func, if not package-level, must be a method.
- if recv := obj.Type().(*types.Signature).Recv(); recv == nil {
+ if recv := obj.Signature().Recv(); recv == nil {
return "", fmt.Errorf("func is not a method: %v", obj)
}
@@ -405,7 +405,7 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
return "", false
}
- _, named := typesinternal.ReceiverNamed(meth.Type().(*types.Signature).Recv())
+ _, named := typesinternal.ReceiverNamed(meth.Signature().Recv())
if named == nil {
return "", false
}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/types/typeutil/map.go b/src/cmd/vendor/golang.org/x/tools/go/types/typeutil/map.go
index f035a0b6be..36624572a6 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/types/typeutil/map.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/types/typeutil/map.go
@@ -304,8 +304,7 @@ func (h hasher) hash(t types.Type) uint32 {
case *types.Named:
hash := h.hashTypeName(t.Obj())
targs := t.TypeArgs()
- for i := 0; i < targs.Len(); i++ {
- targ := targs.At(i)
+ for targ := range targs.Types() {
hash += 2 * h.hash(targ)
}
return hash
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/doc.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/doc.go
new file mode 100644
index 0000000000..74a2a1c815
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/doc.go
@@ -0,0 +1,6 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package analyzerutil provides implementation helpers for analyzers.
+package analyzerutil
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/extractdoc.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/extractdoc.go
new file mode 100644
index 0000000000..772a0300da
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/extractdoc.go
@@ -0,0 +1,113 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package analyzerutil
+
+import (
+ "fmt"
+ "go/parser"
+ "go/token"
+ "strings"
+)
+
+// MustExtractDoc is like [ExtractDoc] but it panics on error.
+//
+// To use, define a doc.go file such as:
+//
+// // Package halting defines an analyzer of program termination.
+// //
+// // # Analyzer halting
+// //
+// // halting: reports whether execution will halt.
+// //
+// // The halting analyzer reports a diagnostic for functions
+// // that run forever. To suppress the diagnostics, try inserting
+// // a 'break' statement into each loop.
+// package halting
+//
+// import _ "embed"
+//
+// //go:embed doc.go
+// var doc string
+//
+// And declare your analyzer as:
+//
+// var Analyzer = &analysis.Analyzer{
+// Name: "halting",
+// Doc: analyzerutil.MustExtractDoc(doc, "halting"),
+// ...
+// }
+func MustExtractDoc(content, name string) string {
+ doc, err := ExtractDoc(content, name)
+ if err != nil {
+ panic(err)
+ }
+ return doc
+}
+
+// ExtractDoc extracts a section of a package doc comment from the
+// provided contents of an analyzer package's doc.go file.
+//
+// A section is a portion of the comment between one heading and
+// the next, using this form:
+//
+// # Analyzer NAME
+//
+// NAME: SUMMARY
+//
+// Full description...
+//
+// where NAME matches the name argument, and SUMMARY is a brief
+// verb-phrase that describes the analyzer. The following lines, up
+// until the next heading or the end of the comment, contain the full
+// description. ExtractDoc returns the portion following the colon,
+// which is the form expected by Analyzer.Doc.
+//
+// Example:
+//
+// # Analyzer printf
+//
+// printf: checks consistency of calls to printf
+//
+// The printf analyzer checks consistency of calls to printf.
+// Here is the complete description...
+//
+// This notation allows a single doc comment to provide documentation
+// for multiple analyzers, each in its own section.
+// The HTML anchors generated for each heading are predictable.
+//
+// It returns an error if the content was not a valid Go source file
+// containing a package doc comment with a heading of the required
+// form.
+//
+// This machinery enables the package documentation (typically
+// accessible via the web at https://pkg.go.dev/) and the command
+// documentation (typically printed to a terminal) to be derived from
+// the same source and formatted appropriately.
+func ExtractDoc(content, name string) (string, error) {
+ if content == "" {
+ return "", fmt.Errorf("empty Go source file")
+ }
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly)
+ if err != nil {
+ return "", fmt.Errorf("not a Go source file")
+ }
+ if f.Doc == nil {
+ return "", fmt.Errorf("Go source file has no package doc comment")
+ }
+ for section := range strings.SplitSeq(f.Doc.Text(), "\n# ") {
+ if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
+ body != "" &&
+ body[0] == '\r' || body[0] == '\n' {
+ body = strings.TrimSpace(body)
+ rest := strings.TrimPrefix(body, name+":")
+ if rest == body {
+ return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name)
+ }
+ return strings.TrimSpace(rest), nil
+ }
+ }
+ return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name)
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/readfile.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/readfile.go
new file mode 100644
index 0000000000..ecc30cae04
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/readfile.go
@@ -0,0 +1,30 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package analyzerutil
+
+// This file defines helpers for calling [analysis.Pass.ReadFile].
+
+import (
+ "go/token"
+ "os"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// ReadFile reads a file and adds it to the FileSet in pass
+// so that we can report errors against it using lineStart.
+func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
+ readFile := pass.ReadFile
+ if readFile == nil {
+ readFile = os.ReadFile
+ }
+ content, err := readFile(filename)
+ if err != nil {
+ return nil, nil, err
+ }
+ tf := pass.Fset.AddFile(filename, -1, len(content))
+ tf.SetLinesForContent(content)
+ return content, tf, nil
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/version.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/version.go
new file mode 100644
index 0000000000..0b9bcc37b6
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/analyzerutil/version.go
@@ -0,0 +1,42 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package analyzerutil
+
+import (
+ "go/ast"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/internal/packagepath"
+ "golang.org/x/tools/internal/stdlib"
+ "golang.org/x/tools/internal/versions"
+)
+
+// FileUsesGoVersion reports whether the specified file may use features of the
+// specified version of Go (e.g. "go1.24").
+//
+// Tip: we recommend using this check "late", just before calling
+// pass.Report, rather than "early" (when entering each ast.File, or
+// each candidate node of interest, during the traversal), because the
+// operation is not free, yet is not a highly selective filter: the
+// fraction of files that pass most version checks is high and
+// increases over time.
+func FileUsesGoVersion(pass *analysis.Pass, file *ast.File, version string) (_res bool) {
+ fileVersion := pass.TypesInfo.FileVersions[file]
+
+ // Standard packages that are part of toolchain bootstrapping
+ // are not considered to use a version of Go later than the
+ // current bootstrap toolchain version.
+ // The bootstrap rule does not cover tests,
+ // and some tests (e.g. debug/elf/file_test.go) rely on this.
+ pkgpath := pass.Pkg.Path()
+ if packagepath.IsStdPackage(pkgpath) &&
+ stdlib.IsBootstrapPackage(pkgpath) && // (excludes "*_test" external test packages)
+ !strings.HasSuffix(pass.Fset.File(file.Pos()).Name(), "_test.go") { // (excludes all tests)
+ fileVersion = stdlib.BootstrapVersion.String() // package must bootstrap
+ }
+
+ return !versions.Before(fileVersion, version)
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go
new file mode 100644
index 0000000000..ef06cf9bde
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go
@@ -0,0 +1,449 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package driverutil defines implementation helper functions for
+// analysis drivers such as unitchecker, {single,multi}checker, and
+// analysistest.
+package driverutil
+
+// This file defines the -fix logic common to unitchecker and
+// {single,multi}checker.
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "go/types"
+ "log"
+ "maps"
+ "os"
+ "sort"
+ "strconv"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/astutil/free"
+ "golang.org/x/tools/internal/diff"
+)
+
+// FixAction abstracts a checker action (running one analyzer on one
+// package) for the purposes of applying its diagnostics' fixes.
+type FixAction struct {
+ Name string // e.g. "analyzer@package"
+ Pkg *types.Package // (for import removal)
+ Files []*ast.File
+ FileSet *token.FileSet
+ ReadFileFunc ReadFileFunc
+ Diagnostics []analysis.Diagnostic
+}
+
+// ApplyFixes attempts to apply the first suggested fix associated
+// with each diagnostic reported by the specified actions.
+// All fixes must have been validated by [ValidateFixes].
+//
+// Each fix is treated as an independent change; fixes are merged in
+// an arbitrary deterministic order as if by a three-way diff tool
+// such as the UNIX diff3 command or 'git merge'. Any fix that cannot be
+// cleanly merged is discarded, in which case the final summary tells
+// the user to re-run the tool.
+// TODO(adonovan): make the checker tool re-run the analysis itself.
+//
+// When the same file is analyzed as a member of both a primary
+// package "p" and a test-augmented package "p [p.test]", there may be
+// duplicate diagnostics and fixes. One set of fixes will be applied
+// and the other will be discarded; but re-running the tool may then
+// show zero fixes, which may cause the confused user to wonder what
+// happened to the other ones.
+// TODO(adonovan): consider pre-filtering completely identical fixes.
+//
+// A common reason for overlapping fixes is duplicate additions of the
+// same import. The merge algorithm may often cleanly resolve such
+// fixes, coalescing identical edits, but the merge may sometimes be
+// confused by nearby changes.
+//
+// Even when merging succeeds, there is no guarantee that the
+// composition of the two fixes is semantically correct. Coalescing
+// identical edits is appropriate for imports, but not for, say,
+// increments to a counter variable; the correct resolution in that
+// case might be to increment it twice.
+//
+// Or consider two fixes that each delete the penultimate reference to
+// a local variable: each fix is sound individually, and they may be
+// textually distant from each other, but when both are applied, the
+// program is no longer valid because it has an unreferenced local
+// variable. (ApplyFixes solves the analogous problem for imports by
+// eliminating imports whose name is unreferenced in the remainder of
+// the fixed file.)
+//
+// Merging depends on both the order of fixes and they order of edits
+// within them. For example, if three fixes add import "a" twice and
+// import "b" once, the two imports of "a" may be combined if they
+// appear in order [a, a, b], or not if they appear as [a, b, a].
+// TODO(adonovan): investigate an algebraic approach to imports;
+// that is, for fixes to Go source files, convert changes within the
+// import(...) portion of the file into semantic edits, compose those
+// edits algebraically, then convert the result back to edits.
+//
+// applyFixes returns success if all fixes are valid, could be cleanly
+// merged, and the corresponding files were successfully updated.
+//
+// If printDiff (from the -diff flag) is set, instead of updating the
+// files it display the final patch composed of all the cleanly merged
+// fixes.
+//
+// TODO(adonovan): handle file-system level aliases such as symbolic
+// links using robustio.FileID.
+func ApplyFixes(actions []FixAction, printDiff, verbose bool) error {
+ generated := make(map[*token.File]bool)
+
+ // Select fixes to apply.
+ //
+ // If there are several for a given Diagnostic, choose the first.
+ // Preserve the order of iteration, for determinism.
+ type fixact struct {
+ fix *analysis.SuggestedFix
+ act FixAction
+ }
+ var fixes []*fixact
+ for _, act := range actions {
+ for _, file := range act.Files {
+ tokFile := act.FileSet.File(file.FileStart)
+ // Memoize, since there may be many actions
+ // for the same package (list of files).
+ if _, seen := generated[tokFile]; !seen {
+ generated[tokFile] = ast.IsGenerated(file)
+ }
+ }
+
+ for _, diag := range act.Diagnostics {
+ for i := range diag.SuggestedFixes {
+ fix := &diag.SuggestedFixes[i]
+ if i == 0 {
+ fixes = append(fixes, &fixact{fix, act})
+ } else {
+ // TODO(adonovan): abstract the logger.
+ log.Printf("%s: ignoring alternative fix %q", act.Name, fix.Message)
+ }
+ }
+ }
+ }
+
+ // Read file content on demand, from the virtual
+ // file system that fed the analyzer (see #62292).
+ //
+ // This cache assumes that all successful reads for the same
+ // file name return the same content.
+ // (It is tempting to group fixes by package and do the
+ // merge/apply/format steps one package at a time, but
+ // packages are not disjoint, due to test variants, so this
+ // would not really address the issue.)
+ baselineContent := make(map[string][]byte)
+ getBaseline := func(readFile ReadFileFunc, filename string) ([]byte, error) {
+ content, ok := baselineContent[filename]
+ if !ok {
+ var err error
+ content, err = readFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ baselineContent[filename] = content
+ }
+ return content, nil
+ }
+
+ // Apply each fix, updating the current state
+ // only if the entire fix can be cleanly merged.
+ var (
+ accumulatedEdits = make(map[string][]diff.Edit)
+ filePkgs = make(map[string]*types.Package) // maps each file to an arbitrary package that includes it
+
+ goodFixes = 0 // number of fixes cleanly applied
+ skippedFixes = 0 // number of fixes skipped (because e.g. edits a generated file)
+ )
+fixloop:
+ for _, fixact := range fixes {
+ // Skip a fix if any of its edits touch a generated file.
+ for _, edit := range fixact.fix.TextEdits {
+ file := fixact.act.FileSet.File(edit.Pos)
+ if generated[file] {
+ skippedFixes++
+ continue fixloop
+ }
+ }
+
+ // Convert analysis.TextEdits to diff.Edits, grouped by file.
+ // Precondition: a prior call to validateFix succeeded.
+ fileEdits := make(map[string][]diff.Edit)
+ for _, edit := range fixact.fix.TextEdits {
+ file := fixact.act.FileSet.File(edit.Pos)
+
+ filePkgs[file.Name()] = fixact.act.Pkg
+
+ baseline, err := getBaseline(fixact.act.ReadFileFunc, file.Name())
+ if err != nil {
+ log.Printf("skipping fix to file %s: %v", file.Name(), err)
+ continue fixloop
+ }
+
+ // We choose to treat size mismatch as a serious error,
+ // as it indicates a concurrent write to at least one file,
+ // and possibly others (consider a git checkout, for example).
+ if file.Size() != len(baseline) {
+ return fmt.Errorf("concurrent file modification detected in file %s (size changed from %d -> %d bytes); aborting fix",
+ file.Name(), file.Size(), len(baseline))
+ }
+
+ fileEdits[file.Name()] = append(fileEdits[file.Name()], diff.Edit{
+ Start: file.Offset(edit.Pos),
+ End: file.Offset(edit.End),
+ New: string(edit.NewText),
+ })
+ }
+
+ // Apply each set of edits by merging atop
+ // the previous accumulated state.
+ after := make(map[string][]diff.Edit)
+ for file, edits := range fileEdits {
+ if prev := accumulatedEdits[file]; len(prev) > 0 {
+ merged, ok := diff.Merge(prev, edits)
+ if !ok {
+ // debugging
+ if false {
+ log.Printf("%s: fix %s conflicts", fixact.act.Name, fixact.fix.Message)
+ }
+ continue fixloop // conflict
+ }
+ edits = merged
+ }
+ after[file] = edits
+ }
+
+ // The entire fix applied cleanly; commit it.
+ goodFixes++
+ maps.Copy(accumulatedEdits, after)
+ // debugging
+ if false {
+ log.Printf("%s: fix %s applied", fixact.act.Name, fixact.fix.Message)
+ }
+ }
+ badFixes := len(fixes) - goodFixes - skippedFixes // number of fixes that could not be applied
+
+ // Show diff or update files to final state.
+ var files []string
+ for file := range accumulatedEdits {
+ files = append(files, file)
+ }
+ sort.Strings(files) // for deterministic -diff
+ var filesUpdated, totalFiles int
+ for _, file := range files {
+ edits := accumulatedEdits[file]
+ if len(edits) == 0 {
+ continue // the diffs annihilated (a miracle?)
+ }
+
+ // Apply accumulated fixes.
+ baseline := baselineContent[file] // (cache hit)
+ final, err := diff.ApplyBytes(baseline, edits)
+ if err != nil {
+ log.Fatalf("internal error in diff.ApplyBytes: %v", err)
+ }
+
+ // Attempt to format each file.
+ if formatted, err := FormatSourceRemoveImports(filePkgs[file], final); err == nil {
+ final = formatted
+ }
+
+ if printDiff {
+ // Since we formatted the file, we need to recompute the diff.
+ unified := diff.Unified(file+" (old)", file+" (new)", string(baseline), string(final))
+ // TODO(adonovan): abstract the I/O.
+ os.Stdout.WriteString(unified)
+
+ } else {
+ // write
+ totalFiles++
+ // TODO(adonovan): abstract the I/O.
+ if err := os.WriteFile(file, final, 0644); err != nil {
+ log.Println(err)
+ continue
+ }
+ filesUpdated++
+ }
+ }
+
+ // TODO(adonovan): consider returning a structured result that
+ // maps each SuggestedFix to its status:
+ // - invalid
+ // - secondary, not selected
+ // - applied
+ // - had conflicts.
+ // and a mapping from each affected file to:
+ // - its final/original content pair, and
+ // - whether formatting was successful.
+ // Then file writes and the UI can be applied by the caller
+ // in whatever form they like.
+
+ // If victory was incomplete, report an error that indicates partial progress.
+ //
+ // badFixes > 0 indicates that we decided not to attempt some
+ // fixes due to conflicts or failure to read the source; still
+ // it's a relatively benign situation since the user can
+ // re-run the tool, and we may still make progress.
+ //
+ // filesUpdated < totalFiles indicates that some file updates
+ // failed. This should be rare, but is a serious error as it
+ // may apply half a fix, or leave the files in a bad state.
+ //
+ // These numbers are potentially misleading:
+ // The denominator includes duplicate conflicting fixes due to
+ // common files in packages "p" and "p [p.test]", which may
+ // have been fixed and won't appear in the re-run.
+ // TODO(adonovan): eliminate identical fixes as an initial
+ // filtering step.
+ //
+ // TODO(adonovan): should we log that n files were updated in case of total victory?
+ if badFixes > 0 || filesUpdated < totalFiles {
+ if printDiff {
+ return fmt.Errorf("%d of %s skipped (e.g. due to conflicts)",
+ badFixes,
+ plural(len(fixes), "fix", "fixes"))
+ } else {
+ return fmt.Errorf("applied %d of %s; %s updated. (Re-run the command to apply more.)",
+ goodFixes,
+ plural(len(fixes), "fix", "fixes"),
+ plural(filesUpdated, "file", "files"))
+ }
+ }
+
+ if verbose {
+ if skippedFixes > 0 {
+ log.Printf("skipped %s that would edit generated files",
+ plural(skippedFixes, "fix", "fixes"))
+ }
+ log.Printf("applied %s, updated %s",
+ plural(len(fixes), "fix", "fixes"),
+ plural(filesUpdated, "file", "files"))
+ }
+
+ return nil
+}
+
+// FormatSourceRemoveImports is a variant of [format.Source] that
+// removes imports that became redundant when fixes were applied.
+//
+// Import removal is necessarily heuristic since we do not have type
+// information for the fixed file and thus cannot accurately tell
+// whether k is among the free names of T{k: 0}, which requires
+// knowledge of whether T is a struct type.
+func FormatSourceRemoveImports(pkg *types.Package, src []byte) ([]byte, error) {
+ // This function was reduced from the "strict entire file"
+ // path through [format.Source].
+
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, "fixed.go", src, parser.ParseComments|parser.SkipObjectResolution)
+ if err != nil {
+ return nil, err
+ }
+
+ ast.SortImports(fset, file)
+
+ removeUnneededImports(fset, pkg, file)
+
+ // printerNormalizeNumbers means to canonicalize number literal prefixes
+ // and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
+ //
+ // This value is defined in go/printer specifically for go/format and cmd/gofmt.
+ const printerNormalizeNumbers = 1 << 30
+ cfg := &printer.Config{
+ Mode: printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers,
+ Tabwidth: 8,
+ }
+ var buf bytes.Buffer
+ if err := cfg.Fprint(&buf, fset, file); err != nil {
+ return nil, err
+ }
+ return buf.Bytes(), nil
+}
+
+// removeUnneededImports removes import specs that are not referenced
+// within the fixed file. It uses [free.Names] to heuristically
+// approximate the set of imported names needed by the body of the
+// file based only on syntax.
+//
+// pkg provides type information about the unmodified package, in
+// particular the name that would implicitly be declared by a
+// non-renaming import of a given existing dependency.
+func removeUnneededImports(fset *token.FileSet, pkg *types.Package, file *ast.File) {
+ // Map each existing dependency to its default import name.
+ // (We'll need this to interpret non-renaming imports.)
+ packageNames := make(map[string]string)
+ for _, imp := range pkg.Imports() {
+ packageNames[imp.Path()] = imp.Name()
+ }
+
+ // Compute the set of free names of the file,
+ // ignoring its import decls.
+ freenames := make(map[string]bool)
+ for _, decl := range file.Decls {
+ if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
+ continue // skip import
+ }
+
+ // TODO(adonovan): we could do better than includeComplitIdents=false
+ // since we have type information about the unmodified package,
+ // which is a good source of heuristics.
+ const includeComplitIdents = false
+ maps.Copy(freenames, free.Names(decl, includeComplitIdents))
+ }
+
+ // Check whether each import's declared name is free (referenced) by the file.
+ var deletions []func()
+ for _, spec := range file.Imports {
+ path, err := strconv.Unquote(spec.Path.Value)
+ if err != nil {
+ continue // malformed import; ignore
+ }
+ explicit := "" // explicit PkgName, if any
+ if spec.Name != nil {
+ explicit = spec.Name.Name
+ }
+ name := explicit // effective PkgName
+ if name == "" {
+ // Non-renaming import: use package's default name.
+ name = packageNames[path]
+ }
+ switch name {
+ case "":
+ continue // assume it's a new import
+ case ".":
+ continue // dot imports are tricky
+ case "_":
+ continue // keep blank imports
+ }
+ if !freenames[name] {
+ // Import's effective name is not free in (not used by) the file.
+ // Enqueue it for deletion after the loop.
+ deletions = append(deletions, func() {
+ astutil.DeleteNamedImport(fset, file, explicit, path)
+ })
+ }
+ }
+
+ // Apply the deletions.
+ for _, del := range deletions {
+ del()
+ }
+}
+
+// plural returns "n nouns", selecting the plural form as approriate.
+func plural(n int, singular, plural string) string {
+ if n == 1 {
+ return "1 " + singular
+ } else {
+ return fmt.Sprintf("%d %s", n, plural)
+ }
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go
new file mode 100644
index 0000000000..7fc42a5ef7
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/print.go
@@ -0,0 +1,161 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driverutil
+
+// This file defined output helpers common to all drivers.
+
+import (
+ "encoding/json"
+ "fmt"
+ "go/token"
+ "io"
+ "log"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// TODO(adonovan): don't accept an io.Writer if we don't report errors.
+// Either accept a bytes.Buffer (infallible), or return a []byte.
+
+// PrintPlain prints a diagnostic in plain text form.
+// If contextLines is nonnegative, it also prints the
+// offending line plus this many lines of context.
+func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
+ print := func(pos, end token.Pos, message string) {
+ posn := fset.Position(pos)
+ fmt.Fprintf(out, "%s: %s\n", posn, message)
+
+ // show offending line plus N lines of context.
+ if contextLines >= 0 {
+ end := fset.Position(end)
+ if !end.IsValid() {
+ end = posn
+ }
+ // TODO(adonovan): highlight the portion of the line indicated
+ // by pos...end using ASCII art, terminal colors, etc?
+ data, _ := os.ReadFile(posn.Filename)
+ lines := strings.Split(string(data), "\n")
+ for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
+ if 1 <= i && i <= len(lines) {
+ fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
+ }
+ }
+ }
+ }
+
+ print(diag.Pos, diag.End, diag.Message)
+ for _, rel := range diag.Related {
+ print(rel.Pos, rel.End, "\t"+rel.Message)
+ }
+}
+
+// A JSONTree is a mapping from package ID to analysis name to result.
+// Each result is either a jsonError or a list of JSONDiagnostic.
+type JSONTree map[string]map[string]any
+
+// A TextEdit describes the replacement of a portion of a file.
+// Start and End are zero-based half-open indices into the original byte
+// sequence of the file, and New is the new text.
+type JSONTextEdit struct {
+ Filename string `json:"filename"`
+ Start int `json:"start"`
+ End int `json:"end"`
+ New string `json:"new"`
+}
+
+// A JSONSuggestedFix describes an edit that should be applied as a whole or not
+// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
+// consists of multiple non-contiguous edits.
+type JSONSuggestedFix struct {
+ Message string `json:"message"`
+ Edits []JSONTextEdit `json:"edits"`
+}
+
+// A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
+//
+// TODO(matloob): include End position if present.
+type JSONDiagnostic struct {
+ Category string `json:"category,omitempty"`
+ Posn string `json:"posn"` // e.g. "file.go:line:column"
+ Message string `json:"message"`
+ SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
+ Related []JSONRelatedInformation `json:"related,omitempty"`
+}
+
+// A JSONRelated describes a secondary position and message related to
+// a primary diagnostic.
+//
+// TODO(adonovan): include End position if present.
+type JSONRelatedInformation struct {
+ Posn string `json:"posn"` // e.g. "file.go:line:column"
+ Message string `json:"message"`
+}
+
+// Add adds the result of analysis 'name' on package 'id'.
+// The result is either a list of diagnostics or an error.
+func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
+ var v any
+ if err != nil {
+ type jsonError struct {
+ Err string `json:"error"`
+ }
+ v = jsonError{err.Error()}
+ } else if len(diags) > 0 {
+ diagnostics := make([]JSONDiagnostic, 0, len(diags))
+ for _, f := range diags {
+ var fixes []JSONSuggestedFix
+ for _, fix := range f.SuggestedFixes {
+ var edits []JSONTextEdit
+ for _, edit := range fix.TextEdits {
+ edits = append(edits, JSONTextEdit{
+ Filename: fset.Position(edit.Pos).Filename,
+ Start: fset.Position(edit.Pos).Offset,
+ End: fset.Position(edit.End).Offset,
+ New: string(edit.NewText),
+ })
+ }
+ fixes = append(fixes, JSONSuggestedFix{
+ Message: fix.Message,
+ Edits: edits,
+ })
+ }
+ var related []JSONRelatedInformation
+ for _, r := range f.Related {
+ related = append(related, JSONRelatedInformation{
+ Posn: fset.Position(r.Pos).String(),
+ Message: r.Message,
+ })
+ }
+ jdiag := JSONDiagnostic{
+ Category: f.Category,
+ Posn: fset.Position(f.Pos).String(),
+ Message: f.Message,
+ SuggestedFixes: fixes,
+ Related: related,
+ }
+ diagnostics = append(diagnostics, jdiag)
+ }
+ v = diagnostics
+ }
+ if v != nil {
+ m, ok := tree[id]
+ if !ok {
+ m = make(map[string]any)
+ tree[id] = m
+ }
+ m[name] = v
+ }
+}
+
+func (tree JSONTree) Print(out io.Writer) error {
+ data, err := json.MarshalIndent(tree, "", "\t")
+ if err != nil {
+ log.Panicf("internal error: JSON marshaling failed: %v", err)
+ }
+ _, err = fmt.Fprintf(out, "%s\n", data)
+ return err
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/readfile.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/readfile.go
new file mode 100644
index 0000000000..dc1d54dd8b
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/readfile.go
@@ -0,0 +1,43 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driverutil
+
+// This file defines helpers for implementing [analysis.Pass.ReadFile].
+
+import (
+ "fmt"
+ "slices"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// A ReadFileFunc is a function that returns the
+// contents of a file, such as [os.ReadFile].
+type ReadFileFunc = func(filename string) ([]byte, error)
+
+// CheckedReadFile returns a wrapper around a Pass.ReadFile
+// function that performs the appropriate checks.
+func CheckedReadFile(pass *analysis.Pass, readFile ReadFileFunc) ReadFileFunc {
+ return func(filename string) ([]byte, error) {
+ if err := CheckReadable(pass, filename); err != nil {
+ return nil, err
+ }
+ return readFile(filename)
+ }
+}
+
+// CheckReadable enforces the access policy defined by the ReadFile field of [analysis.Pass].
+func CheckReadable(pass *analysis.Pass, filename string) error {
+ if slices.Contains(pass.OtherFiles, filename) ||
+ slices.Contains(pass.IgnoredFiles, filename) {
+ return nil
+ }
+ for _, f := range pass.Files {
+ if pass.Fset.File(f.FileStart).Name() == filename {
+ return nil
+ }
+ }
+ return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/url.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/url.go
new file mode 100644
index 0000000000..93b3ecfd49
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/url.go
@@ -0,0 +1,33 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driverutil
+
+import (
+ "fmt"
+ "net/url"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// ResolveURL resolves the URL field for a Diagnostic from an Analyzer
+// and returns the URL. See Diagnostic.URL for details.
+func ResolveURL(a *analysis.Analyzer, d analysis.Diagnostic) (string, error) {
+ if d.URL == "" && d.Category == "" && a.URL == "" {
+ return "", nil // do nothing
+ }
+ raw := d.URL
+ if d.URL == "" && d.Category != "" {
+ raw = "#" + d.Category
+ }
+ u, err := url.Parse(raw)
+ if err != nil {
+ return "", fmt.Errorf("invalid Diagnostic.URL %q: %s", raw, err)
+ }
+ base, err := url.Parse(a.URL)
+ if err != nil {
+ return "", fmt.Errorf("invalid Analyzer.URL %q: %s", a.URL, err)
+ }
+ return base.ResolveReference(u).String(), nil
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/validatefix.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/validatefix.go
new file mode 100644
index 0000000000..7efc4197d6
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/validatefix.go
@@ -0,0 +1,118 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package driverutil
+
+// This file defines the validation of SuggestedFixes.
+
+import (
+ "cmp"
+ "fmt"
+ "go/token"
+ "slices"
+
+ "golang.org/x/tools/go/analysis"
+)
+
+// ValidateFixes validates the set of fixes for a single diagnostic.
+// Any error indicates a bug in the originating analyzer.
+//
+// It updates fixes so that fixes[*].End.IsValid().
+//
+// It may be used as part of an analysis driver implementation.
+func ValidateFixes(fset *token.FileSet, a *analysis.Analyzer, fixes []analysis.SuggestedFix) error {
+ fixMessages := make(map[string]bool)
+ for i := range fixes {
+ fix := &fixes[i]
+ if fixMessages[fix.Message] {
+ return fmt.Errorf("analyzer %q suggests two fixes with same Message (%s)", a.Name, fix.Message)
+ }
+ fixMessages[fix.Message] = true
+ if err := validateFix(fset, fix); err != nil {
+ return fmt.Errorf("analyzer %q suggests invalid fix (%s): %v", a.Name, fix.Message, err)
+ }
+ }
+ return nil
+}
+
+// validateFix validates a single fix.
+// Any error indicates a bug in the originating analyzer.
+//
+// It updates fix so that fix.End.IsValid().
+func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
+
+ // Stably sort edits by Pos. This ordering puts insertions
+ // (end = start) before deletions (end > start) at the same
+ // point, but uses a stable sort to preserve the order of
+ // multiple insertions at the same point.
+ slices.SortStableFunc(fix.TextEdits, func(x, y analysis.TextEdit) int {
+ if sign := cmp.Compare(x.Pos, y.Pos); sign != 0 {
+ return sign
+ }
+ return cmp.Compare(x.End, y.End)
+ })
+
+ var prev *analysis.TextEdit
+ for i := range fix.TextEdits {
+ edit := &fix.TextEdits[i]
+
+ // Validate edit individually.
+ start := edit.Pos
+ file := fset.File(start)
+ if file == nil {
+ return fmt.Errorf("no token.File for TextEdit.Pos (%v)", edit.Pos)
+ }
+ fileEnd := token.Pos(file.Base() + file.Size())
+ if end := edit.End; end.IsValid() {
+ if end < start {
+ return fmt.Errorf("TextEdit.Pos (%v) > TextEdit.End (%v)", edit.Pos, edit.End)
+ }
+ endFile := fset.File(end)
+ if endFile != file && end < fileEnd+10 {
+ // Relax the checks below in the special case when the end position
+ // is only slightly beyond EOF, as happens when End is computed
+ // (as in ast.{Struct,Interface}Type) rather than based on
+ // actual token positions. In such cases, truncate end to EOF.
+ //
+ // This is a workaround for #71659; see:
+ // https://github.com/golang/go/issues/71659#issuecomment-2651606031
+ // A better fix would be more faithful recording of token
+ // positions (or their absence) in the AST.
+ edit.End = fileEnd
+ continue
+ }
+ if endFile == nil {
+ return fmt.Errorf("no token.File for TextEdit.End (%v; File(start).FileEnd is %d)", end, file.Base()+file.Size())
+ }
+ if endFile != file {
+ return fmt.Errorf("edit #%d spans files (%v and %v)",
+ i, file.Position(edit.Pos), endFile.Position(edit.End))
+ }
+ } else {
+ edit.End = start // update the SuggestedFix
+ }
+ if eof := fileEnd; edit.End > eof {
+ return fmt.Errorf("end is (%v) beyond end of file (%v)", edit.End, eof)
+ }
+
+ // Validate the sequence of edits:
+ // properly ordered, no overlapping deletions
+ if prev != nil && edit.Pos < prev.End {
+ xpos := fset.Position(prev.Pos)
+ xend := fset.Position(prev.End)
+ ypos := fset.Position(edit.Pos)
+ yend := fset.Position(edit.End)
+ return fmt.Errorf("overlapping edits to %s (%d:%d-%d:%d and %d:%d-%d:%d)",
+ xpos.Filename,
+ xpos.Line, xpos.Column,
+ xend.Line, xend.Column,
+ ypos.Line, ypos.Column,
+ yend.Line, yend.Column,
+ )
+ }
+ prev = edit
+ }
+
+ return nil
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/typeindex/typeindex.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/typeindex/typeindex.go
new file mode 100644
index 0000000000..41146d9abb
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/typeindex/typeindex.go
@@ -0,0 +1,33 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package typeindex defines an analyzer that provides a
+// [golang.org/x/tools/internal/typesinternal/typeindex.Index].
+//
+// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
+// intended to be used as a helper by other analyzers; it reports no
+// diagnostics of its own.
+package typeindex
+
+import (
+ "reflect"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
+)
+
+var Analyzer = &analysis.Analyzer{
+ Name: "typeindex",
+ Doc: "indexes of type information for later passes",
+ URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysis/typeindex",
+ Run: func(pass *analysis.Pass) (any, error) {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
+ },
+ RunDespiteErrors: true,
+ Requires: []*analysis.Analyzer{inspect.Analyzer},
+ ResultType: reflect.TypeFor[*typeindex.Index](),
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
deleted file mode 100644
index 970d7507f0..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
+++ /dev/null
@@ -1,180 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package analysisinternal provides helper functions for use in both
-// the analysis drivers in go/analysis and gopls, and in various
-// analyzers.
-//
-// TODO(adonovan): this is not ideal as it may lead to unnecessary
-// dependencies between drivers and analyzers. Split into analyzerlib
-// and driverlib?
-package analysisinternal
-
-import (
- "cmp"
- "fmt"
- "go/token"
- "os"
- "slices"
-
- "golang.org/x/tools/go/analysis"
-)
-
-// ReadFile reads a file and adds it to the FileSet in pass
-// so that we can report errors against it using lineStart.
-func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
- readFile := pass.ReadFile
- if readFile == nil {
- readFile = os.ReadFile
- }
- content, err := readFile(filename)
- if err != nil {
- return nil, nil, err
- }
- tf := pass.Fset.AddFile(filename, -1, len(content))
- tf.SetLinesForContent(content)
- return content, tf, nil
-}
-
-// A ReadFileFunc is a function that returns the
-// contents of a file, such as [os.ReadFile].
-type ReadFileFunc = func(filename string) ([]byte, error)
-
-// CheckedReadFile returns a wrapper around a Pass.ReadFile
-// function that performs the appropriate checks.
-func CheckedReadFile(pass *analysis.Pass, readFile ReadFileFunc) ReadFileFunc {
- return func(filename string) ([]byte, error) {
- if err := CheckReadable(pass, filename); err != nil {
- return nil, err
- }
- return readFile(filename)
- }
-}
-
-// CheckReadable enforces the access policy defined by the ReadFile field of [analysis.Pass].
-func CheckReadable(pass *analysis.Pass, filename string) error {
- if slices.Contains(pass.OtherFiles, filename) ||
- slices.Contains(pass.IgnoredFiles, filename) {
- return nil
- }
- for _, f := range pass.Files {
- if pass.Fset.File(f.FileStart).Name() == filename {
- return nil
- }
- }
- return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
-}
-
-// ValidateFixes validates the set of fixes for a single diagnostic.
-// Any error indicates a bug in the originating analyzer.
-//
-// It updates fixes so that fixes[*].End.IsValid().
-//
-// It may be used as part of an analysis driver implementation.
-func ValidateFixes(fset *token.FileSet, a *analysis.Analyzer, fixes []analysis.SuggestedFix) error {
- fixMessages := make(map[string]bool)
- for i := range fixes {
- fix := &fixes[i]
- if fixMessages[fix.Message] {
- return fmt.Errorf("analyzer %q suggests two fixes with same Message (%s)", a.Name, fix.Message)
- }
- fixMessages[fix.Message] = true
- if err := validateFix(fset, fix); err != nil {
- return fmt.Errorf("analyzer %q suggests invalid fix (%s): %v", a.Name, fix.Message, err)
- }
- }
- return nil
-}
-
-// validateFix validates a single fix.
-// Any error indicates a bug in the originating analyzer.
-//
-// It updates fix so that fix.End.IsValid().
-func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
-
- // Stably sort edits by Pos. This ordering puts insertions
- // (end = start) before deletions (end > start) at the same
- // point, but uses a stable sort to preserve the order of
- // multiple insertions at the same point.
- slices.SortStableFunc(fix.TextEdits, func(x, y analysis.TextEdit) int {
- if sign := cmp.Compare(x.Pos, y.Pos); sign != 0 {
- return sign
- }
- return cmp.Compare(x.End, y.End)
- })
-
- var prev *analysis.TextEdit
- for i := range fix.TextEdits {
- edit := &fix.TextEdits[i]
-
- // Validate edit individually.
- start := edit.Pos
- file := fset.File(start)
- if file == nil {
- return fmt.Errorf("no token.File for TextEdit.Pos (%v)", edit.Pos)
- }
- fileEnd := token.Pos(file.Base() + file.Size())
- if end := edit.End; end.IsValid() {
- if end < start {
- return fmt.Errorf("TextEdit.Pos (%v) > TextEdit.End (%v)", edit.Pos, edit.End)
- }
- endFile := fset.File(end)
- if endFile != file && end < fileEnd+10 {
- // Relax the checks below in the special case when the end position
- // is only slightly beyond EOF, as happens when End is computed
- // (as in ast.{Struct,Interface}Type) rather than based on
- // actual token positions. In such cases, truncate end to EOF.
- //
- // This is a workaround for #71659; see:
- // https://github.com/golang/go/issues/71659#issuecomment-2651606031
- // A better fix would be more faithful recording of token
- // positions (or their absence) in the AST.
- edit.End = fileEnd
- continue
- }
- if endFile == nil {
- return fmt.Errorf("no token.File for TextEdit.End (%v; File(start).FileEnd is %d)", end, file.Base()+file.Size())
- }
- if endFile != file {
- return fmt.Errorf("edit #%d spans files (%v and %v)",
- i, file.Position(edit.Pos), endFile.Position(edit.End))
- }
- } else {
- edit.End = start // update the SuggestedFix
- }
- if eof := fileEnd; edit.End > eof {
- return fmt.Errorf("end is (%v) beyond end of file (%v)", edit.End, eof)
- }
-
- // Validate the sequence of edits:
- // properly ordered, no overlapping deletions
- if prev != nil && edit.Pos < prev.End {
- xpos := fset.Position(prev.Pos)
- xend := fset.Position(prev.End)
- ypos := fset.Position(edit.Pos)
- yend := fset.Position(edit.End)
- return fmt.Errorf("overlapping edits to %s (%d:%d-%d:%d and %d:%d-%d:%d)",
- xpos.Filename,
- xpos.Line, xpos.Column,
- xend.Line, xend.Column,
- ypos.Line, ypos.Column,
- yend.Line, yend.Column,
- )
- }
- prev = edit
- }
-
- return nil
-}
-
-// Range returns an [analysis.Range] for the specified start and end positions.
-func Range(pos, end token.Pos) analysis.Range {
- return tokenRange{pos, end}
-}
-
-// tokenRange is an implementation of the [analysis.Range] interface.
-type tokenRange struct{ StartPos, EndPos token.Pos }
-
-func (r tokenRange) Pos() token.Pos { return r.StartPos }
-func (r tokenRange) End() token.Pos { return r.EndPos }
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go
deleted file mode 100644
index c6cdf5997e..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2023 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package analysisinternal
-
-import (
- "fmt"
- "go/parser"
- "go/token"
- "strings"
-)
-
-// MustExtractDoc is like [ExtractDoc] but it panics on error.
-//
-// To use, define a doc.go file such as:
-//
-// // Package halting defines an analyzer of program termination.
-// //
-// // # Analyzer halting
-// //
-// // halting: reports whether execution will halt.
-// //
-// // The halting analyzer reports a diagnostic for functions
-// // that run forever. To suppress the diagnostics, try inserting
-// // a 'break' statement into each loop.
-// package halting
-//
-// import _ "embed"
-//
-// //go:embed doc.go
-// var doc string
-//
-// And declare your analyzer as:
-//
-// var Analyzer = &analysis.Analyzer{
-// Name: "halting",
-// Doc: analysisinternal.MustExtractDoc(doc, "halting"),
-// ...
-// }
-func MustExtractDoc(content, name string) string {
- doc, err := ExtractDoc(content, name)
- if err != nil {
- panic(err)
- }
- return doc
-}
-
-// ExtractDoc extracts a section of a package doc comment from the
-// provided contents of an analyzer package's doc.go file.
-//
-// A section is a portion of the comment between one heading and
-// the next, using this form:
-//
-// # Analyzer NAME
-//
-// NAME: SUMMARY
-//
-// Full description...
-//
-// where NAME matches the name argument, and SUMMARY is a brief
-// verb-phrase that describes the analyzer. The following lines, up
-// until the next heading or the end of the comment, contain the full
-// description. ExtractDoc returns the portion following the colon,
-// which is the form expected by Analyzer.Doc.
-//
-// Example:
-//
-// # Analyzer printf
-//
-// printf: checks consistency of calls to printf
-//
-// The printf analyzer checks consistency of calls to printf.
-// Here is the complete description...
-//
-// This notation allows a single doc comment to provide documentation
-// for multiple analyzers, each in its own section.
-// The HTML anchors generated for each heading are predictable.
-//
-// It returns an error if the content was not a valid Go source file
-// containing a package doc comment with a heading of the required
-// form.
-//
-// This machinery enables the package documentation (typically
-// accessible via the web at https://pkg.go.dev/) and the command
-// documentation (typically printed to a terminal) to be derived from
-// the same source and formatted appropriately.
-func ExtractDoc(content, name string) (string, error) {
- if content == "" {
- return "", fmt.Errorf("empty Go source file")
- }
- fset := token.NewFileSet()
- f, err := parser.ParseFile(fset, "", content, parser.ParseComments|parser.PackageClauseOnly)
- if err != nil {
- return "", fmt.Errorf("not a Go source file")
- }
- if f.Doc == nil {
- return "", fmt.Errorf("Go source file has no package doc comment")
- }
- for section := range strings.SplitSeq(f.Doc.Text(), "\n# ") {
- if body := strings.TrimPrefix(section, "Analyzer "+name); body != section &&
- body != "" &&
- body[0] == '\r' || body[0] == '\n' {
- body = strings.TrimSpace(body)
- rest := strings.TrimPrefix(body, name+":")
- if rest == body {
- return "", fmt.Errorf("'Analyzer %s' heading not followed by '%s: summary...' line", name, name)
- }
- return strings.TrimSpace(rest), nil
- }
- }
- return "", fmt.Errorf("package doc comment contains no 'Analyzer %s' heading", name)
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/generated/generated.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/generated/generated.go
deleted file mode 100644
index 13e1b69021..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/generated/generated.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2025 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package generated defines an analyzer whose result makes it
-// convenient to skip diagnostics within generated files.
-package generated
-
-import (
- "go/ast"
- "go/token"
- "reflect"
-
- "golang.org/x/tools/go/analysis"
-)
-
-var Analyzer = &analysis.Analyzer{
- Name: "generated",
- Doc: "detect which Go files are generated",
- URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/generated",
- ResultType: reflect.TypeFor[*Result](),
- Run: func(pass *analysis.Pass) (any, error) {
- set := make(map[*token.File]bool)
- for _, file := range pass.Files {
- if ast.IsGenerated(file) {
- set[pass.Fset.File(file.FileStart)] = true
- }
- }
- return &Result{fset: pass.Fset, generatedFiles: set}, nil
- },
-}
-
-type Result struct {
- fset *token.FileSet
- generatedFiles map[*token.File]bool
-}
-
-// IsGenerated reports whether the position is within a generated file.
-func (r *Result) IsGenerated(pos token.Pos) bool {
- return r.generatedFiles[r.fset.File(pos)]
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go b/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go
deleted file mode 100644
index bba21c6ea0..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/typeindex/typeindex.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2025 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package typeindex defines an analyzer that provides a
-// [golang.org/x/tools/internal/typesinternal/typeindex.Index].
-//
-// Like [golang.org/x/tools/go/analysis/passes/inspect], it is
-// intended to be used as a helper by other analyzers; it reports no
-// diagnostics of its own.
-package typeindex
-
-import (
- "reflect"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/typesinternal/typeindex"
-)
-
-var Analyzer = &analysis.Analyzer{
- Name: "typeindex",
- Doc: "indexes of type information for later passes",
- URL: "https://pkg.go.dev/golang.org/x/tools/internal/analysisinternal/typeindex",
- Run: func(pass *analysis.Pass) (any, error) {
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil
- },
- RunDespiteErrors: true,
- Requires: []*analysis.Analyzer{inspect.Analyzer},
- ResultType: reflect.TypeOf(new(typeindex.Index)),
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/astutil/free/free.go b/src/cmd/vendor/golang.org/x/tools/internal/astutil/free/free.go
new file mode 100644
index 0000000000..2c4d2c4e52
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/astutil/free/free.go
@@ -0,0 +1,418 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package free defines utilities for computing the free variables of
+// a syntax tree without type information. This is inherently
+// heuristic because of the T{f: x} ambiguity, in which f may or may
+// not be a lexical reference depending on whether T is a struct type.
+package free
+
+import (
+ "go/ast"
+ "go/token"
+)
+
+// Copied, with considerable changes, from go/parser/resolver.go
+// at af53bd2c03.
+
+// Names computes an approximation to the set of free names of the AST
+// at node n based solely on syntax.
+//
+// In the absence of composite literals, the set of free names is exact. Composite
+// literals introduce an ambiguity that can only be resolved with type information:
+// whether F is a field name or a value in `T{F: ...}`.
+// If includeComplitIdents is true, this function conservatively assumes
+// T is not a struct type, so freeishNames overapproximates: the resulting
+// set may contain spurious entries that are not free lexical references
+// but are references to struct fields.
+// If includeComplitIdents is false, this function assumes that T *is*
+// a struct type, so freeishNames underapproximates: the resulting set
+// may omit names that are free lexical references.
+//
+// TODO(adonovan): includeComplitIdents is a crude hammer: the caller
+// may have partial or heuristic information about whether a given T
+// is struct type. Replace includeComplitIdents with a hook to query
+// the caller.
+//
+// The code is based on go/parser.resolveFile, but heavily simplified. Crucial
+// differences are:
+// - Instead of resolving names to their objects, this function merely records
+// whether they are free.
+// - Labels are ignored: they do not refer to values.
+// - This is never called on ImportSpecs, so the function panics if it sees one.
+func Names(n ast.Node, includeComplitIdents bool) map[string]bool {
+ v := &freeVisitor{
+ free: make(map[string]bool),
+ includeComplitIdents: includeComplitIdents,
+ }
+ // Begin with a scope, even though n might not be a form that establishes a scope.
+ // For example, n might be:
+ // x := ...
+ // Then we need to add the first x to some scope.
+ v.openScope()
+ ast.Walk(v, n)
+ v.closeScope()
+ if v.scope != nil {
+ panic("unbalanced scopes")
+ }
+ return v.free
+}
+
+// A freeVisitor holds state for a free-name analysis.
+type freeVisitor struct {
+ scope *scope // the current innermost scope
+ free map[string]bool // free names seen so far
+ includeComplitIdents bool // include identifier key in composite literals
+}
+
+// scope contains all the names defined in a lexical scope.
+// It is like ast.Scope, but without deprecation warnings.
+type scope struct {
+ names map[string]bool
+ outer *scope
+}
+
+func (s *scope) defined(name string) bool {
+ for ; s != nil; s = s.outer {
+ if s.names[name] {
+ return true
+ }
+ }
+ return false
+}
+
+func (v *freeVisitor) Visit(n ast.Node) ast.Visitor {
+ switch n := n.(type) {
+
+ // Expressions.
+ case *ast.Ident:
+ v.use(n)
+
+ case *ast.FuncLit:
+ v.openScope()
+ defer v.closeScope()
+ v.walkFuncType(nil, n.Type)
+ v.walkBody(n.Body)
+
+ case *ast.SelectorExpr:
+ v.walk(n.X)
+ // Skip n.Sel: it cannot be free.
+
+ case *ast.StructType:
+ v.openScope()
+ defer v.closeScope()
+ v.walkFieldList(n.Fields)
+
+ case *ast.FuncType:
+ v.openScope()
+ defer v.closeScope()
+ v.walkFuncType(nil, n)
+
+ case *ast.CompositeLit:
+ v.walk(n.Type)
+ for _, e := range n.Elts {
+ if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
+ if ident, _ := kv.Key.(*ast.Ident); ident != nil {
+ // It is not possible from syntax alone to know whether
+ // an identifier used as a composite literal key is
+ // a struct field (if n.Type is a struct) or a value
+ // (if n.Type is a map, slice or array).
+ if v.includeComplitIdents {
+ // Over-approximate by treating both cases as potentially
+ // free names.
+ v.use(ident)
+ } else {
+ // Under-approximate by ignoring potentially free names.
+ }
+ } else {
+ v.walk(kv.Key)
+ }
+ v.walk(kv.Value)
+ } else {
+ v.walk(e)
+ }
+ }
+
+ case *ast.InterfaceType:
+ v.openScope()
+ defer v.closeScope()
+ v.walkFieldList(n.Methods)
+
+ // Statements
+ case *ast.AssignStmt:
+ walkSlice(v, n.Rhs)
+ if n.Tok == token.DEFINE {
+ v.shortVarDecl(n.Lhs)
+ } else {
+ walkSlice(v, n.Lhs)
+ }
+
+ case *ast.LabeledStmt:
+ // Ignore labels.
+ v.walk(n.Stmt)
+
+ case *ast.BranchStmt:
+ // Ignore labels.
+
+ case *ast.BlockStmt:
+ v.openScope()
+ defer v.closeScope()
+ walkSlice(v, n.List)
+
+ case *ast.IfStmt:
+ v.openScope()
+ defer v.closeScope()
+ v.walk(n.Init)
+ v.walk(n.Cond)
+ v.walk(n.Body)
+ v.walk(n.Else)
+
+ case *ast.CaseClause:
+ walkSlice(v, n.List)
+ v.openScope()
+ defer v.closeScope()
+ walkSlice(v, n.Body)
+
+ case *ast.SwitchStmt:
+ v.openScope()
+ defer v.closeScope()
+ v.walk(n.Init)
+ v.walk(n.Tag)
+ v.walkBody(n.Body)
+
+ case *ast.TypeSwitchStmt:
+ v.openScope()
+ defer v.closeScope()
+ if n.Init != nil {
+ v.walk(n.Init)
+ }
+ v.walk(n.Assign)
+ // We can use walkBody here because we don't track label scopes.
+ v.walkBody(n.Body)
+
+ case *ast.CommClause:
+ v.openScope()
+ defer v.closeScope()
+ v.walk(n.Comm)
+ walkSlice(v, n.Body)
+
+ case *ast.SelectStmt:
+ v.walkBody(n.Body)
+
+ case *ast.ForStmt:
+ v.openScope()
+ defer v.closeScope()
+ v.walk(n.Init)
+ v.walk(n.Cond)
+ v.walk(n.Post)
+ v.walk(n.Body)
+
+ case *ast.RangeStmt:
+ v.openScope()
+ defer v.closeScope()
+ v.walk(n.X)
+ var lhs []ast.Expr
+ if n.Key != nil {
+ lhs = append(lhs, n.Key)
+ }
+ if n.Value != nil {
+ lhs = append(lhs, n.Value)
+ }
+ if len(lhs) > 0 {
+ if n.Tok == token.DEFINE {
+ v.shortVarDecl(lhs)
+ } else {
+ walkSlice(v, lhs)
+ }
+ }
+ v.walk(n.Body)
+
+ // Declarations
+ case *ast.GenDecl:
+ switch n.Tok {
+ case token.CONST, token.VAR:
+ for _, spec := range n.Specs {
+ spec := spec.(*ast.ValueSpec)
+ walkSlice(v, spec.Values)
+ v.walk(spec.Type)
+ v.declare(spec.Names...)
+ }
+
+ case token.TYPE:
+ for _, spec := range n.Specs {
+ spec := spec.(*ast.TypeSpec)
+ // Go spec: The scope of a type identifier declared inside a
+ // function begins at the identifier in the TypeSpec and ends
+ // at the end of the innermost containing block.
+ v.declare(spec.Name)
+ if spec.TypeParams != nil {
+ v.openScope()
+ defer v.closeScope()
+ v.walkTypeParams(spec.TypeParams)
+ }
+ v.walk(spec.Type)
+ }
+
+ case token.IMPORT:
+ panic("encountered import declaration in free analysis")
+ }
+
+ case *ast.FuncDecl:
+ if n.Recv == nil && n.Name.Name != "init" { // package-level function
+ v.declare(n.Name)
+ }
+ v.openScope()
+ defer v.closeScope()
+ v.walkTypeParams(n.Type.TypeParams)
+ v.walkFuncType(n.Recv, n.Type)
+ v.walkBody(n.Body)
+
+ default:
+ return v
+ }
+
+ return nil
+}
+
+func (v *freeVisitor) openScope() {
+ v.scope = &scope{map[string]bool{}, v.scope}
+}
+
+func (v *freeVisitor) closeScope() {
+ v.scope = v.scope.outer
+}
+
+func (v *freeVisitor) walk(n ast.Node) {
+ if n != nil {
+ ast.Walk(v, n)
+ }
+}
+
+func (v *freeVisitor) walkFuncType(recv *ast.FieldList, typ *ast.FuncType) {
+ // First use field types...
+ v.walkRecvFieldType(recv)
+ v.walkFieldTypes(typ.Params)
+ v.walkFieldTypes(typ.Results)
+
+ // ...then declare field names.
+ v.declareFieldNames(recv)
+ v.declareFieldNames(typ.Params)
+ v.declareFieldNames(typ.Results)
+}
+
+// A receiver field is not like a param or result field because
+// "func (recv R[T]) method()" uses R but declares T.
+func (v *freeVisitor) walkRecvFieldType(list *ast.FieldList) {
+ if list == nil {
+ return
+ }
+ for _, f := range list.List { // valid => len=1
+ typ := f.Type
+ if ptr, ok := typ.(*ast.StarExpr); ok {
+ typ = ptr.X
+ }
+
+ // Analyze receiver type as Base[Index, ...]
+ var (
+ base ast.Expr
+ indices []ast.Expr
+ )
+ switch typ := typ.(type) {
+ case *ast.IndexExpr: // B[T]
+ base, indices = typ.X, []ast.Expr{typ.Index}
+ case *ast.IndexListExpr: // B[K, V]
+ base, indices = typ.X, typ.Indices
+ default: // B
+ base = typ
+ }
+ for _, expr := range indices {
+ if id, ok := expr.(*ast.Ident); ok {
+ v.declare(id)
+ }
+ }
+ v.walk(base)
+ }
+}
+
+// walkTypeParams is like walkFieldList, but declares type parameters eagerly so
+// that they may be resolved in the constraint expressions held in the field
+// Type.
+func (v *freeVisitor) walkTypeParams(list *ast.FieldList) {
+ v.declareFieldNames(list)
+ v.walkFieldTypes(list) // constraints
+}
+
+func (v *freeVisitor) walkBody(body *ast.BlockStmt) {
+ if body == nil {
+ return
+ }
+ walkSlice(v, body.List)
+}
+
+func (v *freeVisitor) walkFieldList(list *ast.FieldList) {
+ if list == nil {
+ return
+ }
+ v.walkFieldTypes(list) // .Type may contain references
+ v.declareFieldNames(list) // .Names declares names
+}
+
+func (v *freeVisitor) shortVarDecl(lhs []ast.Expr) {
+ // Go spec: A short variable declaration may redeclare variables provided
+ // they were originally declared in the same block with the same type, and
+ // at least one of the non-blank variables is new.
+ //
+ // However, it doesn't matter to free analysis whether a variable is declared
+ // fresh or redeclared.
+ for _, x := range lhs {
+ // In a well-formed program each expr must be an identifier,
+ // but be forgiving.
+ if id, ok := x.(*ast.Ident); ok {
+ v.declare(id)
+ }
+ }
+}
+
+func walkSlice[S ~[]E, E ast.Node](r *freeVisitor, list S) {
+ for _, e := range list {
+ r.walk(e)
+ }
+}
+
+// walkFieldTypes resolves the types of the walkFieldTypes in list.
+// The companion method declareFieldList declares the names of the walkFieldTypes.
+func (v *freeVisitor) walkFieldTypes(list *ast.FieldList) {
+ if list != nil {
+ for _, f := range list.List {
+ v.walk(f.Type)
+ }
+ }
+}
+
+// declareFieldNames declares the names of the fields in list.
+// (Names in a FieldList always establish new bindings.)
+// The companion method resolveFieldList resolves the types of the fields.
+func (v *freeVisitor) declareFieldNames(list *ast.FieldList) {
+ if list != nil {
+ for _, f := range list.List {
+ v.declare(f.Names...)
+ }
+ }
+}
+
+// use marks ident as free if it is not in scope.
+func (v *freeVisitor) use(ident *ast.Ident) {
+ if s := ident.Name; s != "_" && !v.scope.defined(s) {
+ v.free[s] = true
+ }
+}
+
+// declare adds each non-blank ident to the current scope.
+func (v *freeVisitor) declare(idents ...*ast.Ident) {
+ for _, id := range idents {
+ if id.Name != "_" {
+ v.scope.names[id.Name] = true
+ }
+ }
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/astutil/stringlit.go b/src/cmd/vendor/golang.org/x/tools/internal/astutil/stringlit.go
index 849d45d853..ce1e7de882 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/astutil/stringlit.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/astutil/stringlit.go
@@ -14,16 +14,16 @@ import (
// RangeInStringLiteral calculates the positional range within a string literal
// corresponding to the specified start and end byte offsets within the logical string.
-func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (token.Pos, token.Pos, error) {
+func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (Range, error) {
startPos, err := PosInStringLiteral(lit, start)
if err != nil {
- return 0, 0, fmt.Errorf("start: %v", err)
+ return Range{}, fmt.Errorf("start: %v", err)
}
endPos, err := PosInStringLiteral(lit, end)
if err != nil {
- return 0, 0, fmt.Errorf("end: %v", err)
+ return Range{}, fmt.Errorf("end: %v", err)
}
- return startPos, endPos, nil
+ return Range{startPos, endPos}, nil
}
// PosInStringLiteral returns the position within a string literal
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
index a1c0983504..7a02fca21e 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
@@ -5,6 +5,7 @@
package astutil
import (
+ "fmt"
"go/ast"
"go/printer"
"go/token"
@@ -50,28 +51,26 @@ func PreorderStack(root ast.Node, stack []ast.Node, f func(n ast.Node, stack []a
}
// NodeContains reports whether the Pos/End range of node n encloses
-// the given position pos.
+// the given range.
//
// It is inclusive of both end points, to allow hovering (etc) when
// the cursor is immediately after a node.
//
-// For unfortunate historical reasons, the Pos/End extent of an
-// ast.File runs from the start of its package declaration---excluding
-// copyright comments, build tags, and package documentation---to the
-// end of its last declaration, excluding any trailing comments. So,
-// as a special case, if n is an [ast.File], NodeContains uses
-// n.FileStart <= pos && pos <= n.FileEnd to report whether the
-// position lies anywhere within the file.
+// Like [NodeRange], it treats the range of an [ast.File] as the
+// file's complete extent.
//
// Precondition: n must not be nil.
-func NodeContains(n ast.Node, pos token.Pos) bool {
- var start, end token.Pos
- if file, ok := n.(*ast.File); ok {
- start, end = file.FileStart, file.FileEnd // entire file
- } else {
- start, end = n.Pos(), n.End()
- }
- return start <= pos && pos <= end
+func NodeContains(n ast.Node, rng Range) bool {
+ return NodeRange(n).Contains(rng)
+}
+
+// NodeContainPos reports whether the Pos/End range of node n encloses
+// the given pos.
+//
+// Like [NodeRange], it treats the range of an [ast.File] as the
+// file's complete extent.
+func NodeContainsPos(n ast.Node, pos token.Pos) bool {
+ return NodeRange(n).ContainsPos(pos)
}
// IsChildOf reports whether cur.ParentEdge is ek.
@@ -117,3 +116,118 @@ func Format(fset *token.FileSet, n ast.Node) string {
printer.Fprint(&buf, fset, n) // ignore errors
return buf.String()
}
+
+// -- Range --
+
+// Range is a Pos interval.
+// It implements [analysis.Range] and [ast.Node].
+type Range struct{ Start, EndPos token.Pos }
+
+// RangeOf constructs a Range.
+//
+// RangeOf exists to pacify the "unkeyed literal" (composites) vet
+// check. It would be nice if there were a way for a type to add
+// itself to the allowlist.
+func RangeOf(start, end token.Pos) Range { return Range{start, end} }
+
+// NodeRange returns the extent of node n as a Range.
+//
+// For unfortunate historical reasons, the Pos/End extent of an
+// ast.File runs from the start of its package declaration---excluding
+// copyright comments, build tags, and package documentation---to the
+// end of its last declaration, excluding any trailing comments. So,
+// as a special case, if n is an [ast.File], NodeContains uses
+// n.FileStart <= pos && pos <= n.FileEnd to report whether the
+// position lies anywhere within the file.
+func NodeRange(n ast.Node) Range {
+ if file, ok := n.(*ast.File); ok {
+ return Range{file.FileStart, file.FileEnd} // entire file
+ }
+ return Range{n.Pos(), n.End()}
+}
+
+func (r Range) Pos() token.Pos { return r.Start }
+func (r Range) End() token.Pos { return r.EndPos }
+
+// ContainsPos reports whether the range (inclusive of both end points)
+// includes the specified position.
+func (r Range) ContainsPos(pos token.Pos) bool {
+ return r.Contains(RangeOf(pos, pos))
+}
+
+// Contains reports whether the range (inclusive of both end points)
+// includes the specified range.
+func (r Range) Contains(rng Range) bool {
+ return r.Start <= rng.Start && rng.EndPos <= r.EndPos
+}
+
+// IsValid reports whether the range is valid.
+func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos }
+
+// --
+
+// Select returns the syntax nodes identified by a user's text
+// selection. It returns three nodes: the innermost node that wholly
+// encloses the selection; and the first and last nodes that are
+// wholly enclosed by the selection.
+//
+// For example, given this selection:
+//
+// { f(); g(); /* comment */ }
+// ~~~~~~~~~~~
+//
+// Select returns the enclosing BlockStmt, the f() CallExpr, and the g() CallExpr.
+//
+// Callers that require exactly one syntax tree (e.g. just f() or just
+// g()) should check that the returned start and end nodes are
+// identical.
+//
+// This function is intended to be called early in the handling of a
+// user's request, since it is tolerant of sloppy selection including
+// extraneous whitespace and comments. Use it in new code instead of
+// PathEnclosingInterval. When the exact extent of a node is known,
+// use [Cursor.FindByPos] instead.
+func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) {
+ curEnclosing, ok := curFile.FindByPos(start, end)
+ if !ok {
+ return noCursor, noCursor, noCursor, fmt.Errorf("invalid selection")
+ }
+
+ // Find the first and last node wholly within the (start, end) range.
+ // We'll narrow the effective selection to them, to exclude whitespace.
+ // (This matches the functionality of PathEnclosingInterval.)
+ var curStart, curEnd inspector.Cursor
+ rng := RangeOf(start, end)
+ for cur := range curEnclosing.Preorder() {
+ if rng.Contains(NodeRange(cur.Node())) {
+ // The start node has the least Pos.
+ if !CursorValid(curStart) {
+ curStart = cur
+ }
+ // The end node has the greatest End.
+ // End positions do not change monotonically,
+ // so we must compute the max.
+ if !CursorValid(curEnd) ||
+ cur.Node().End() > curEnd.Node().End() {
+ curEnd = cur
+ }
+ }
+ }
+ if !CursorValid(curStart) {
+ return noCursor, noCursor, noCursor, fmt.Errorf("no syntax selected")
+ }
+ return curEnclosing, curStart, curEnd, nil
+}
+
+// CursorValid reports whether the cursor is valid.
+//
+// A valid cursor may yet be the virtual root node,
+// cur.Inspector.Root(), which has no [Cursor.Node].
+//
+// TODO(adonovan): move to cursorutil package, and move that package into x/tools.
+// Ultimately, make this a method of Cursor. Needs a proposal.
+func CursorValid(cur inspector.Cursor) bool {
+ return cur.Inspector() != nil
+}
+
+var noCursor inspector.Cursor
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/cfginternal/cfginternal.go b/src/cmd/vendor/golang.org/x/tools/internal/cfginternal/cfginternal.go
new file mode 100644
index 0000000000..a9b6236f4d
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/internal/cfginternal/cfginternal.go
@@ -0,0 +1,16 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package cfginternal exposes internals of go/cfg.
+// It cannot actually depend on symbols from go/cfg.
+package cfginternal
+
+// IsNoReturn exposes (*cfg.CFG).noReturn to the ctrlflow analyzer.
+// TODO(adonovan): add CFG.NoReturn to the public API.
+//
+// You must link [golang.org/x/tools/go/cfg] into your application for
+// this function to be non-nil.
+var IsNoReturn = func(cfg any) bool {
+ panic("golang.org/x/tools/go/cfg not linked into application")
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go
index 7b7c5cc677..5acc68e1db 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go
@@ -16,10 +16,6 @@ type Diff struct {
ReplStart, ReplEnd int // offset of replacement text in B
}
-// DiffStrings returns the differences between two strings.
-// It does not respect rune boundaries.
-func DiffStrings(a, b string) []Diff { return diff(stringSeqs{a, b}) }
-
// DiffBytes returns the differences between two byte sequences.
// It does not respect rune boundaries.
func DiffBytes(a, b []byte) []Diff { return diff(bytesSeqs{a, b}) }
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/facts/imports.go b/src/cmd/vendor/golang.org/x/tools/internal/facts/imports.go
index cc9383e800..324010b475 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/facts/imports.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/facts/imports.go
@@ -53,8 +53,8 @@ func importMap(imports []*types.Package) map[string]*types.Package {
case typesinternal.NamedOrAlias: // *types.{Named,Alias}
// Add the type arguments if this is an instance.
if targs := T.TypeArgs(); targs.Len() > 0 {
- for i := 0; i < targs.Len(); i++ {
- addType(targs.At(i))
+ for t := range targs.Types() {
+ addType(t)
}
}
@@ -70,8 +70,8 @@ func importMap(imports []*types.Package) map[string]*types.Package {
// common aspects
addObj(T.Obj())
if tparams := T.TypeParams(); tparams.Len() > 0 {
- for i := 0; i < tparams.Len(); i++ {
- addType(tparams.At(i))
+ for tparam := range tparams.TypeParams() {
+ addType(tparam)
}
}
@@ -81,8 +81,8 @@ func importMap(imports []*types.Package) map[string]*types.Package {
addType(aliases.Rhs(T))
case *types.Named:
addType(T.Underlying())
- for i := 0; i < T.NumMethods(); i++ {
- addObj(T.Method(i))
+ for method := range T.Methods() {
+ addObj(method)
}
}
}
@@ -101,28 +101,28 @@ func importMap(imports []*types.Package) map[string]*types.Package {
addType(T.Params())
addType(T.Results())
if tparams := T.TypeParams(); tparams != nil {
- for i := 0; i < tparams.Len(); i++ {
- addType(tparams.At(i))
+ for tparam := range tparams.TypeParams() {
+ addType(tparam)
}
}
case *types.Struct:
- for i := 0; i < T.NumFields(); i++ {
- addObj(T.Field(i))
+ for field := range T.Fields() {
+ addObj(field)
}
case *types.Tuple:
- for i := 0; i < T.Len(); i++ {
- addObj(T.At(i))
+ for v := range T.Variables() {
+ addObj(v)
}
case *types.Interface:
- for i := 0; i < T.NumMethods(); i++ {
- addObj(T.Method(i))
+ for method := range T.Methods() {
+ addObj(method)
}
- for i := 0; i < T.NumEmbeddeds(); i++ {
- addType(T.EmbeddedType(i)) // walk Embedded for implicits
+ for etyp := range T.EmbeddedTypes() {
+ addType(etyp) // walk Embedded for implicits
}
case *types.Union:
- for i := 0; i < T.Len(); i++ {
- addType(T.Term(i).Type())
+ for term := range T.Terms() {
+ addType(term.Type())
}
case *types.TypeParam:
if !typs[T] {
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go b/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
index 2764a97fc7..bca4d8a0b0 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
@@ -12,4 +12,5 @@ var (
ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
+ StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
)
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
index 6df01d8ef9..9b96b1dbf1 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
@@ -331,109 +331,192 @@ func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []analysis.TextEd
}
}
+// find leftmost Pos bigger than start and rightmost less than end
+func filterPos(nds []*ast.Comment, start, end token.Pos) (token.Pos, token.Pos, bool) {
+ l, r := end, token.NoPos
+ ok := false
+ for _, n := range nds {
+ if n.Pos() > start && n.Pos() < l {
+ l = n.Pos()
+ ok = true
+ }
+ if n.End() <= end && n.End() > r {
+ r = n.End()
+ ok = true
+ }
+ }
+ return l, r, ok
+}
+
// DeleteStmt returns the edits to remove the [ast.Stmt] identified by
-// curStmt, if it is contained within a BlockStmt, CaseClause,
-// CommClause, or is the STMT in switch STMT; ... {...}. It returns nil otherwise.
-func DeleteStmt(tokFile *token.File, curStmt inspector.Cursor) []analysis.TextEdit {
- stmt := curStmt.Node().(ast.Stmt)
- // if the stmt is on a line by itself delete the whole line
- // otherwise just delete the statement.
-
- // this logic would be a lot simpler with the file contents, and somewhat simpler
- // if the cursors included the comments.
-
- lineOf := tokFile.Line
- stmtStartLine, stmtEndLine := lineOf(stmt.Pos()), lineOf(stmt.End())
-
- var from, to token.Pos
- // bounds of adjacent syntax/comments on same line, if any
- limits := func(left, right token.Pos) {
+// curStmt if it recognizes the context. It returns nil otherwise.
+// TODO(pjw, adonovan): it should not return nil, it should return an error
+//
+// DeleteStmt is called with just the AST so it has trouble deciding if
+// a comment is associated with the statement to be deleted. For instance,
+//
+// for /*A*/ init()/*B*/;/*C/cond()/*D/;/*E*/post() /*F*/ { /*G*/}
+//
+// comment B and C are indistinguishable, as are D and E. That is, as the
+// AST does not say where the semicolons are, B and C could go either
+// with the init() or the cond(), so cannot be removed safely. The same
+// is true for D, E, and the post(). (And there are other similar cases.)
+// But the other comments can be removed as they are unambiguously
+// associated with the statement being deleted. In particular,
+// it removes whole lines like
+//
+// stmt // comment
+func DeleteStmt(file *token.File, curStmt inspector.Cursor) []analysis.TextEdit {
+ // if the stmt is on a line by itself, or a range of lines, delete the whole thing
+ // including comments. Except for the heads of switches, type
+ // switches, and for-statements that's the usual case. Complexity occurs where
+ // there are multiple statements on the same line, and adjacent comments.
+
+ // In that case we remove some adjacent comments:
+ // In me()/*A*/;b(), comment A cannot be removed, because the ast
+ // is indistinguishable from me();/*A*/b()
+ // and the same for cases like switch me()/*A*/; x.(type) {
+
+ // this would be more precise with the file contents, or if the ast
+ // contained the location of semicolons
+ var (
+ stmt = curStmt.Node().(ast.Stmt)
+ tokFile = file
+ lineOf = tokFile.Line
+ stmtStartLine = lineOf(stmt.Pos())
+ stmtEndLine = lineOf(stmt.End())
+
+ leftSyntax, rightSyntax token.Pos // pieces of parent node on stmt{Start,End}Line
+ leftComments, rightComments []*ast.Comment // comments before/after stmt on the same line
+ )
+
+ // remember the Pos that are on the same line as stmt
+ use := func(left, right token.Pos) {
if lineOf(left) == stmtStartLine {
- from = left
+ leftSyntax = left
}
if lineOf(right) == stmtEndLine {
- to = right
+ rightSyntax = right
}
}
- // TODO(pjw): there are other places a statement might be removed:
- // IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
- // (removing the blocks requires more rewriting than this routine would do)
- // CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
- // (removing the stmt requires more rewriting, and it's unclear what the user means)
- switch parent := curStmt.Parent().Node().(type) {
- case *ast.SwitchStmt:
- limits(parent.Switch, parent.Body.Lbrace)
- case *ast.TypeSwitchStmt:
- limits(parent.Switch, parent.Body.Lbrace)
- if parent.Assign == stmt {
- return nil // don't let the user break the type switch
+
+ // find the comments, if any, on the same line
+Big:
+ for _, cg := range astutil.EnclosingFile(curStmt).Comments {
+ for _, co := range cg.List {
+ if lineOf(co.End()) < stmtStartLine {
+ continue
+ } else if lineOf(co.Pos()) > stmtEndLine {
+ break Big // no more are possible
+ }
+ if lineOf(co.End()) == stmtStartLine && co.End() <= stmt.Pos() {
+ // comment is before the statement
+ leftComments = append(leftComments, co)
+ } else if lineOf(co.Pos()) == stmtEndLine && co.Pos() >= stmt.End() {
+ // comment is after the statement
+ rightComments = append(rightComments, co)
+ }
}
+ }
+
+ // find any other syntax on the same line
+ var (
+ leftStmt, rightStmt token.Pos // end/start positions of sibling statements in a []Stmt list
+ inStmtList = false
+ curParent = curStmt.Parent()
+ )
+ switch parent := curParent.Node().(type) {
case *ast.BlockStmt:
- limits(parent.Lbrace, parent.Rbrace)
+ use(parent.Lbrace, parent.Rbrace)
+ inStmtList = true
+ case *ast.CaseClause:
+ use(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
+ inStmtList = true
case *ast.CommClause:
- limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
if parent.Comm == stmt {
return nil // maybe the user meant to remove the entire CommClause?
}
- case *ast.CaseClause:
- limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
+ use(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
+ inStmtList = true
case *ast.ForStmt:
- limits(parent.For, parent.Body.Lbrace)
-
+ use(parent.For, parent.Body.Lbrace)
+ // special handling, as init;cond;post BlockStmt is not a statment list
+ if parent.Init != nil && parent.Cond != nil && stmt == parent.Init && lineOf(parent.Cond.Pos()) == lineOf(stmt.End()) {
+ rightStmt = parent.Cond.Pos()
+ } else if parent.Post != nil && parent.Cond != nil && stmt == parent.Post && lineOf(parent.Cond.End()) == lineOf(stmt.Pos()) {
+ leftStmt = parent.Cond.End()
+ }
+ case *ast.IfStmt:
+ switch stmt {
+ case parent.Init:
+ use(parent.If, parent.Body.Lbrace)
+ case parent.Else:
+ // stmt is the {...} in "if cond {} else {...}" and removing
+ // it would require removing the 'else' keyword, but the ast
+ // does not contain its position.
+ return nil
+ }
+ case *ast.SwitchStmt:
+ use(parent.Switch, parent.Body.Lbrace)
+ case *ast.TypeSwitchStmt:
+ if stmt == parent.Assign {
+ return nil // don't remove .(type)
+ }
+ use(parent.Switch, parent.Body.Lbrace)
default:
return nil // not one of ours
}
- if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine {
- from = prev.Node().End() // preceding statement ends on same line
- }
- if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine {
- to = next.Node().Pos() // following statement begins on same line
- }
- // and now for the comments
-Outer:
- for _, cg := range astutil.EnclosingFile(curStmt).Comments {
- for _, co := range cg.List {
- if lineOf(co.End()) < stmtStartLine {
- continue
- } else if lineOf(co.Pos()) > stmtEndLine {
- break Outer // no more are possible
- }
- if lineOf(co.End()) == stmtStartLine && co.End() < stmt.Pos() {
- if !from.IsValid() || co.End() > from {
- from = co.End()
- continue // maybe there are more
- }
- }
- if lineOf(co.Pos()) == stmtEndLine && co.Pos() > stmt.End() {
- if !to.IsValid() || co.Pos() < to {
- to = co.Pos()
- continue // maybe there are more
- }
+ if inStmtList {
+ // find the siblings, if any, on the same line
+ if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine {
+ if _, ok := prev.Node().(ast.Stmt); ok {
+ leftStmt = prev.Node().End() // preceding statement ends on same line
}
}
+ if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine {
+ rightStmt = next.Node().Pos() // following statement begins on same line
+ }
}
- // if either from or to is valid, just remove the statement
- // otherwise remove the line
- edit := analysis.TextEdit{Pos: stmt.Pos(), End: stmt.End()}
- if from.IsValid() || to.IsValid() {
- // remove just the statement.
- // we can't tell if there is a ; or whitespace right after the statement
- // ideally we'd like to remove the former and leave the latter
- // (if gofmt has run, there likely won't be a ;)
- // In type switches we know there's a semicolon somewhere after the statement,
- // but the extra work for this special case is not worth it, as gofmt will fix it.
- return []analysis.TextEdit{edit}
- }
- // remove the whole line
- for lineOf(edit.Pos) == stmtStartLine {
- edit.Pos--
+
+ // compute the left and right limits of the edit
+ var leftEdit, rightEdit token.Pos
+ if leftStmt.IsValid() {
+ leftEdit = stmt.Pos() // can't remove preceding comments: a()/*A*/; me()
+ } else if leftSyntax.IsValid() {
+ // remove intervening leftComments
+ if a, _, ok := filterPos(leftComments, leftSyntax, stmt.Pos()); ok {
+ leftEdit = a
+ } else {
+ leftEdit = stmt.Pos()
+ }
+ } else { // remove whole line
+ for leftEdit = stmt.Pos(); lineOf(leftEdit) == stmtStartLine; leftEdit-- {
+ }
+ if leftEdit < stmt.Pos() {
+ leftEdit++ // beginning of line
+ }
}
- edit.Pos++ // get back tostmtStartLine
- for lineOf(edit.End) == stmtEndLine {
- edit.End++
+ if rightStmt.IsValid() {
+ rightEdit = stmt.End() // can't remove following comments
+ } else if rightSyntax.IsValid() {
+ // remove intervening rightComments
+ if _, b, ok := filterPos(rightComments, stmt.End(), rightSyntax); ok {
+ rightEdit = b
+ } else {
+ rightEdit = stmt.End()
+ }
+ } else { // remove whole line
+ fend := token.Pos(file.Base()) + token.Pos(file.Size())
+ for rightEdit = stmt.End(); fend >= rightEdit && lineOf(rightEdit) == stmtEndLine; rightEdit++ {
+ }
+ // don't remove \n if there was other stuff earlier
+ if leftSyntax.IsValid() || leftStmt.IsValid() {
+ rightEdit--
+ }
}
- return []analysis.TextEdit{edit}
+
+ return []analysis.TextEdit{{Pos: leftEdit, End: rightEdit}}
}
// DeleteUnusedVars computes the edits required to delete the
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
index b46340c66a..ce5beb2724 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
@@ -36,6 +36,7 @@ type gobCallee struct {
// results of type analysis (does not reach go/types data structures)
PkgPath string // package path of declaring package
Name string // user-friendly name for error messages
+ GoVersion string // version of Go effective in callee file
Unexported []string // names of free objects that are unexported
FreeRefs []freeRef // locations of references to free objects
FreeObjs []object // descriptions of free objects
@@ -114,6 +115,24 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
return nil, fmt.Errorf("cannot inline function %s as it has no body", name)
}
+ // Record the file's Go goVersion so that we don't
+ // inline newer code into file using an older dialect.
+ //
+ // Using the file version is overly conservative.
+ // A more precise solution would be for the type checker to
+ // record which language features the callee actually needs;
+ // see https://go.dev/issue/75726.
+ //
+ // We don't have the ast.File handy, so instead of a
+ // lookup we must scan the entire FileVersions map.
+ var goVersion string
+ for file, v := range info.FileVersions {
+ if file.Pos() < decl.Pos() && decl.Pos() < file.End() {
+ goVersion = v
+ break
+ }
+ }
+
// Record the location of all free references in the FuncDecl.
// (Parameters are not free by this definition.)
var (
@@ -342,6 +361,7 @@ func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Pa
Content: content,
PkgPath: pkg.Path(),
Name: name,
+ GoVersion: goVersion,
Unexported: unexported,
FreeObjs: freeObjs,
FreeRefs: freeRefs,
@@ -421,11 +441,11 @@ func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.I
if sig.Recv() != nil {
params = append(params, newParamInfo(sig.Recv(), false))
}
- for i := 0; i < sig.Params().Len(); i++ {
- params = append(params, newParamInfo(sig.Params().At(i), false))
+ for v := range sig.Params().Variables() {
+ params = append(params, newParamInfo(v, false))
}
- for i := 0; i < sig.Results().Len(); i++ {
- results = append(results, newParamInfo(sig.Results().At(i), true))
+ for v := range sig.Results().Variables() {
+ results = append(results, newParamInfo(v, true))
}
}
@@ -497,8 +517,8 @@ func analyzeTypeParams(_ logger, fset *token.FileSet, info *types.Info, decl *as
paramInfos := make(map[*types.TypeName]*paramInfo)
var params []*paramInfo
collect := func(tpl *types.TypeParamList) {
- for i := range tpl.Len() {
- typeName := tpl.At(i).Obj()
+ for tparam := range tpl.TypeParams() {
+ typeName := tparam.Obj()
info := ¶mInfo{Name: typeName.Name()}
params = append(params, info)
paramInfos[typeName] = info
@@ -639,8 +659,7 @@ func analyzeAssignment(info *types.Info, stack []ast.Node) (assignable, ifaceAss
return true, types.IsInterface(under.Elem()), false
case *types.Struct: // Struct{k: expr}
if id, _ := kv.Key.(*ast.Ident); id != nil {
- for fi := range under.NumFields() {
- field := under.Field(fi)
+ for field := range under.Fields() {
if info.Uses[id] == field {
return true, types.IsInterface(field.Type()), false
}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/free.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/free.go
deleted file mode 100644
index e3cf313a8a..0000000000
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/free.go
+++ /dev/null
@@ -1,382 +0,0 @@
-// Copyright 2025 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Copied, with considerable changes, from go/parser/resolver.go
-// at af53bd2c03.
-
-package inline
-
-import (
- "go/ast"
- "go/token"
-)
-
-// freeishNames computes an approximation to the free names of the AST
-// at node n based solely on syntax, inserting values into the map.
-//
-// In the absence of composite literals, the set of free names is exact. Composite
-// literals introduce an ambiguity that can only be resolved with type information:
-// whether F is a field name or a value in `T{F: ...}`.
-// If includeComplitIdents is true, this function conservatively assumes
-// T is not a struct type, so freeishNames overapproximates: the resulting
-// set may contain spurious entries that are not free lexical references
-// but are references to struct fields.
-// If includeComplitIdents is false, this function assumes that T *is*
-// a struct type, so freeishNames underapproximates: the resulting set
-// may omit names that are free lexical references.
-//
-// The code is based on go/parser.resolveFile, but heavily simplified. Crucial
-// differences are:
-// - Instead of resolving names to their objects, this function merely records
-// whether they are free.
-// - Labels are ignored: they do not refer to values.
-// - This is never called on FuncDecls or ImportSpecs, so the function
-// panics if it sees one.
-func freeishNames(free map[string]bool, n ast.Node, includeComplitIdents bool) {
- v := &freeVisitor{free: free, includeComplitIdents: includeComplitIdents}
- // Begin with a scope, even though n might not be a form that establishes a scope.
- // For example, n might be:
- // x := ...
- // Then we need to add the first x to some scope.
- v.openScope()
- ast.Walk(v, n)
- v.closeScope()
- assert(v.scope == nil, "unbalanced scopes")
-}
-
-// A freeVisitor holds state for a free-name analysis.
-type freeVisitor struct {
- scope *scope // the current innermost scope
- free map[string]bool // free names seen so far
- includeComplitIdents bool // include identifier key in composite literals
-}
-
-// scope contains all the names defined in a lexical scope.
-// It is like ast.Scope, but without deprecation warnings.
-type scope struct {
- names map[string]bool
- outer *scope
-}
-
-func (s *scope) defined(name string) bool {
- for ; s != nil; s = s.outer {
- if s.names[name] {
- return true
- }
- }
- return false
-}
-
-func (v *freeVisitor) Visit(n ast.Node) ast.Visitor {
- switch n := n.(type) {
-
- // Expressions.
- case *ast.Ident:
- v.resolve(n)
-
- case *ast.FuncLit:
- v.openScope()
- defer v.closeScope()
- v.walkFuncType(n.Type)
- v.walkBody(n.Body)
-
- case *ast.SelectorExpr:
- v.walk(n.X)
- // Skip n.Sel: it cannot be free.
-
- case *ast.StructType:
- v.openScope()
- defer v.closeScope()
- v.walkFieldList(n.Fields)
-
- case *ast.FuncType:
- v.openScope()
- defer v.closeScope()
- v.walkFuncType(n)
-
- case *ast.CompositeLit:
- v.walk(n.Type)
- for _, e := range n.Elts {
- if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
- if ident, _ := kv.Key.(*ast.Ident); ident != nil {
- // It is not possible from syntax alone to know whether
- // an identifier used as a composite literal key is
- // a struct field (if n.Type is a struct) or a value
- // (if n.Type is a map, slice or array).
- if v.includeComplitIdents {
- // Over-approximate by treating both cases as potentially
- // free names.
- v.resolve(ident)
- } else {
- // Under-approximate by ignoring potentially free names.
- }
- } else {
- v.walk(kv.Key)
- }
- v.walk(kv.Value)
- } else {
- v.walk(e)
- }
- }
-
- case *ast.InterfaceType:
- v.openScope()
- defer v.closeScope()
- v.walkFieldList(n.Methods)
-
- // Statements
- case *ast.AssignStmt:
- walkSlice(v, n.Rhs)
- if n.Tok == token.DEFINE {
- v.shortVarDecl(n.Lhs)
- } else {
- walkSlice(v, n.Lhs)
- }
-
- case *ast.LabeledStmt:
- // ignore labels
- // TODO(jba): consider labels?
- v.walk(n.Stmt)
-
- case *ast.BranchStmt:
- // Ignore labels.
- // TODO(jba): consider labels?
-
- case *ast.BlockStmt:
- v.openScope()
- defer v.closeScope()
- walkSlice(v, n.List)
-
- case *ast.IfStmt:
- v.openScope()
- defer v.closeScope()
- v.walk(n.Init)
- v.walk(n.Cond)
- v.walk(n.Body)
- v.walk(n.Else)
-
- case *ast.CaseClause:
- walkSlice(v, n.List)
- v.openScope()
- defer v.closeScope()
- walkSlice(v, n.Body)
-
- case *ast.SwitchStmt:
- v.openScope()
- defer v.closeScope()
- v.walk(n.Init)
- v.walk(n.Tag)
- v.walkBody(n.Body)
-
- case *ast.TypeSwitchStmt:
- if n.Init != nil {
- v.openScope()
- defer v.closeScope()
- v.walk(n.Init)
- }
- v.openScope()
- defer v.closeScope()
- v.walk(n.Assign)
- // We can use walkBody here because we don't track label scopes.
- v.walkBody(n.Body)
-
- case *ast.CommClause:
- v.openScope()
- defer v.closeScope()
- v.walk(n.Comm)
- walkSlice(v, n.Body)
-
- case *ast.SelectStmt:
- v.walkBody(n.Body)
-
- case *ast.ForStmt:
- v.openScope()
- defer v.closeScope()
- v.walk(n.Init)
- v.walk(n.Cond)
- v.walk(n.Post)
- v.walk(n.Body)
-
- case *ast.RangeStmt:
- v.openScope()
- defer v.closeScope()
- v.walk(n.X)
- var lhs []ast.Expr
- if n.Key != nil {
- lhs = append(lhs, n.Key)
- }
- if n.Value != nil {
- lhs = append(lhs, n.Value)
- }
- if len(lhs) > 0 {
- if n.Tok == token.DEFINE {
- v.shortVarDecl(lhs)
- } else {
- walkSlice(v, lhs)
- }
- }
- v.walk(n.Body)
-
- // Declarations
- case *ast.GenDecl:
- switch n.Tok {
- case token.CONST, token.VAR:
- for _, spec := range n.Specs {
- spec := spec.(*ast.ValueSpec)
- walkSlice(v, spec.Values)
- if spec.Type != nil {
- v.walk(spec.Type)
- }
- v.declare(spec.Names...)
- }
- case token.TYPE:
- for _, spec := range n.Specs {
- spec := spec.(*ast.TypeSpec)
- // Go spec: The scope of a type identifier declared inside a
- // function begins at the identifier in the TypeSpec and ends
- // at the end of the innermost containing block.
- v.declare(spec.Name)
- if spec.TypeParams != nil {
- v.openScope()
- defer v.closeScope()
- v.walkTypeParams(spec.TypeParams)
- }
- v.walk(spec.Type)
- }
-
- case token.IMPORT:
- panic("encountered import declaration in free analysis")
- }
-
- case *ast.FuncDecl:
- panic("encountered top-level function declaration in free analysis")
-
- default:
- return v
- }
-
- return nil
-}
-
-func (r *freeVisitor) openScope() {
- r.scope = &scope{map[string]bool{}, r.scope}
-}
-
-func (r *freeVisitor) closeScope() {
- r.scope = r.scope.outer
-}
-
-func (r *freeVisitor) walk(n ast.Node) {
- if n != nil {
- ast.Walk(r, n)
- }
-}
-
-// walkFuncType walks a function type. It is used for explicit
-// function types, like this:
-//
-// type RunFunc func(context.Context) error
-//
-// and function literals, like this:
-//
-// func(a, b int) int { return a + b}
-//
-// neither of which have type parameters.
-// Function declarations do involve type parameters, but we don't
-// handle them.
-func (r *freeVisitor) walkFuncType(typ *ast.FuncType) {
- // The order here doesn't really matter, because names in
- // a field list cannot appear in types.
- // (The situation is different for type parameters, for which
- // see [freeVisitor.walkTypeParams].)
- r.resolveFieldList(typ.Params)
- r.resolveFieldList(typ.Results)
- r.declareFieldList(typ.Params)
- r.declareFieldList(typ.Results)
-}
-
-// walkTypeParams is like walkFieldList, but declares type parameters eagerly so
-// that they may be resolved in the constraint expressions held in the field
-// Type.
-func (r *freeVisitor) walkTypeParams(list *ast.FieldList) {
- r.declareFieldList(list)
- r.resolveFieldList(list)
-}
-
-func (r *freeVisitor) walkBody(body *ast.BlockStmt) {
- if body == nil {
- return
- }
- walkSlice(r, body.List)
-}
-
-func (r *freeVisitor) walkFieldList(list *ast.FieldList) {
- if list == nil {
- return
- }
- r.resolveFieldList(list) // .Type may contain references
- r.declareFieldList(list) // .Names declares names
-}
-
-func (r *freeVisitor) shortVarDecl(lhs []ast.Expr) {
- // Go spec: A short variable declaration may redeclare variables provided
- // they were originally declared in the same block with the same type, and
- // at least one of the non-blank variables is new.
- //
- // However, it doesn't matter to free analysis whether a variable is declared
- // fresh or redeclared.
- for _, x := range lhs {
- // In a well-formed program each expr must be an identifier,
- // but be forgiving.
- if id, ok := x.(*ast.Ident); ok {
- r.declare(id)
- }
- }
-}
-
-func walkSlice[S ~[]E, E ast.Node](r *freeVisitor, list S) {
- for _, e := range list {
- r.walk(e)
- }
-}
-
-// resolveFieldList resolves the types of the fields in list.
-// The companion method declareFieldList declares the names of the fields.
-func (r *freeVisitor) resolveFieldList(list *ast.FieldList) {
- if list == nil {
- return
- }
- for _, f := range list.List {
- r.walk(f.Type)
- }
-}
-
-// declareFieldList declares the names of the fields in list.
-// (Names in a FieldList always establish new bindings.)
-// The companion method resolveFieldList resolves the types of the fields.
-func (r *freeVisitor) declareFieldList(list *ast.FieldList) {
- if list == nil {
- return
- }
- for _, f := range list.List {
- r.declare(f.Names...)
- }
-}
-
-// resolve marks ident as free if it is not in scope.
-// TODO(jba): rename: no resolution is happening.
-func (r *freeVisitor) resolve(ident *ast.Ident) {
- if s := ident.Name; s != "_" && !r.scope.defined(s) {
- r.free[s] = true
- }
-}
-
-// declare adds each non-blank ident to the current scope.
-func (r *freeVisitor) declare(idents ...*ast.Ident) {
- for _, id := range idents {
- if id.Name != "_" {
- r.scope.names[id.Name] = true
- }
- }
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
index 2443504da7..03ef0714e0 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
@@ -24,9 +24,11 @@ import (
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/types/typeutil"
internalastutil "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/astutil/free"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
// A Caller describes the function call and its enclosing context.
@@ -424,12 +426,35 @@ func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee
// For simplicity we ignore existing dot imports, so that a qualified
// identifier (QI) in the callee is always represented by a QI in the caller,
// allowing us to treat a QI like a selection on a package name.
- is := &importState{
+ ist := &importState{
logf: logf,
caller: caller,
importMap: make(map[string][]string),
}
+ // Build an index of used-once PkgNames.
+ type pkgNameUse struct {
+ count int
+ id *ast.Ident // an arbitrary use
+ }
+ pkgNameUses := make(map[*types.PkgName]pkgNameUse)
+ for id, obj := range caller.Info.Uses {
+ if pkgname, ok := obj.(*types.PkgName); ok {
+ u := pkgNameUses[pkgname]
+ u.id = id
+ u.count++
+ pkgNameUses[pkgname] = u
+ }
+ }
+ // soleUse returns the ident that refers to pkgname, if there is exactly one.
+ soleUse := func(pkgname *types.PkgName) *ast.Ident {
+ u := pkgNameUses[pkgname]
+ if u.count == 1 {
+ return u.id
+ }
+ return nil
+ }
+
for _, imp := range caller.File.Imports {
if pkgName, ok := importedPkgName(caller.Info, imp); ok &&
pkgName.Name() != "." &&
@@ -448,7 +473,7 @@ func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee
// need this import. Doing so eagerly simplifies the resulting logic.
needed := true
sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr)
- if ok && soleUse(caller.Info, pkgName) == sel.X {
+ if ok && soleUse(pkgName) == sel.X {
needed = false // no longer needed by caller
// Check to see if any of the inlined free objects need this package.
for _, obj := range callee.FreeObjs {
@@ -463,13 +488,13 @@ func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee
// return value holds these.
if needed {
path := pkgName.Imported().Path()
- is.importMap[path] = append(is.importMap[path], pkgName.Name())
+ ist.importMap[path] = append(ist.importMap[path], pkgName.Name())
} else {
- is.oldImports = append(is.oldImports, oldImport{pkgName: pkgName, spec: imp})
+ ist.oldImports = append(ist.oldImports, oldImport{pkgName: pkgName, spec: imp})
}
}
}
- return is
+ return ist
}
// importName finds an existing import name to use in a particular shadowing
@@ -575,9 +600,8 @@ func (i *importState) localName(pkgPath, pkgName string, shadow shadowMap) strin
// Since they are not relevant to removing unused imports, we instruct
// freeishNames to omit composite-literal keys that are identifiers.
func trimNewImports(newImports []newImport, new ast.Node) []newImport {
- free := map[string]bool{}
const omitComplitIdents = false
- freeishNames(free, new, omitComplitIdents)
+ free := free.Names(new, omitComplitIdents)
var res []newImport
for _, ni := range newImports {
if free[ni.pkgName] {
@@ -677,6 +701,15 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
callee.Name, callee.Unexported[0])
}
+ // Reject cross-file inlining if callee requires a newer dialect of Go (#75726).
+ // (Versions default to types.Config.GoVersion, which is unset in many tests,
+ // though should be populated by an analysis driver.)
+ callerGoVersion := caller.Info.FileVersions[caller.File]
+ if callerGoVersion != "" && callee.GoVersion != "" && versions.Before(callerGoVersion, callee.GoVersion) {
+ return nil, fmt.Errorf("cannot inline call to %s (declared using %s) into a file using %s",
+ callee.Name, callee.GoVersion, callerGoVersion)
+ }
+
// -- analyze callee's free references in caller context --
// Compute syntax path enclosing Call, innermost first (Path[0]=Call),
@@ -2019,7 +2052,7 @@ func checkFalconConstraints(logf logger, params []*parameter, args []*argument,
pkg.Scope().Insert(types.NewTypeName(token.NoPos, pkg, typ.Name, types.Typ[typ.Kind]))
}
- // Declared constants and variables for for parameters.
+ // Declared constants and variables for parameters.
nconst := 0
for i, param := range params {
name := param.info.Name
@@ -2388,14 +2421,13 @@ func createBindingDecl(logf logger, caller *Caller, args []*argument, calleeDecl
// (caller syntax), so we can use type info.
// But Type is the untyped callee syntax,
// so we have to use a syntax-only algorithm.
- free := make(map[string]bool)
+ const includeComplitIdents = true
+ free := free.Names(spec.Type, includeComplitIdents)
for _, value := range spec.Values {
for name := range freeVars(caller.Info, value) {
free[name] = true
}
}
- const includeComplitIdents = true
- freeishNames(free, spec.Type, includeComplitIdents)
for name := range free {
if names[name] {
logf("binding decl would shadow free name %q", name)
@@ -3456,7 +3488,7 @@ func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Ex
assert(callIdx == -1, "malformed (duplicative) AST")
callIdx = i
for j, returnOperand := range returnOperands {
- freeishNames(freeNames, returnOperand, includeComplitIdents)
+ maps.Copy(freeNames, free.Names(returnOperand, includeComplitIdents))
rhs = append(rhs, returnOperand)
if resultInfo[j]&nonTrivialResult != 0 {
nonTrivial[i+j] = true
@@ -3469,7 +3501,7 @@ func (st *state) assignStmts(callerStmt *ast.AssignStmt, returnOperands []ast.Ex
// We must clone before clearing positions, since e came from the caller.
expr = internalastutil.CloneNode(expr)
clearPositions(expr)
- freeishNames(freeNames, expr, includeComplitIdents)
+ maps.Copy(freeNames, free.Names(expr, includeComplitIdents))
rhs = append(rhs, expr)
}
}
@@ -3727,18 +3759,4 @@ func hasNonTrivialReturn(returnInfo [][]returnOperandFlags) bool {
return false
}
-// soleUse returns the ident that refers to obj, if there is exactly one.
-func soleUse(info *types.Info, obj types.Object) (sole *ast.Ident) {
- // This is not efficient, but it is called infrequently.
- for id, obj2 := range info.Uses {
- if obj2 == obj {
- if sole != nil {
- return nil // not unique
- }
- sole = id
- }
- }
- return sole
-}
-
type unit struct{} // for representing sets as maps
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/util.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/util.go
index 205e5b6aad..5f895cce57 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/util.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/util.go
@@ -97,6 +97,7 @@ func checkInfoFields(info *types.Info) {
assert(info.Selections != nil, "types.Info.Selections is nil")
assert(info.Types != nil, "types.Info.Types is nil")
assert(info.Uses != nil, "types.Info.Uses is nil")
+ assert(info.FileVersions != nil, "types.Info.FileVersions is nil")
}
// intersects reports whether the maps' key sets intersect.
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go
index 27b9750896..26bc079808 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go
@@ -17,6 +17,11 @@ import (
// FreshName returns the name of an identifier that is undefined
// at the specified position, based on the preferred name.
+//
+// TODO(adonovan): refine this to choose a fresh name only when there
+// would be a conflict with the existing declaration: it's fine to
+// redeclare a name in a narrower scope so long as there are no free
+// references to the outer name from within the narrower scope.
func FreshName(scope *types.Scope, pos token.Pos, preferred string) string {
newName := preferred
for i := 0; ; i++ {
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go
index 96ad6c5821..581784da43 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/deps.go
@@ -12,354 +12,508 @@ type pkginfo struct {
}
var deps = [...]pkginfo{
- {"archive/tar", "\x03k\x03E;\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"},
- {"archive/zip", "\x02\x04a\a\x03\x12\x021;\x01+\x05\x01\x0f\x03\x02\x0e\x04"},
- {"bufio", "\x03k\x83\x01D\x14"},
- {"bytes", "n*Y\x03\fG\x02\x02"},
+ {"archive/tar", "\x03n\x03E<\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"},
+ {"archive/zip", "\x02\x04d\a\x03\x12\x021<\x01+\x05\x01\x0f\x03\x02\x0e\x04"},
+ {"bufio", "\x03n\x84\x01D\x14"},
+ {"bytes", "q*Z\x03\fG\x02\x02"},
{"cmp", ""},
- {"compress/bzip2", "\x02\x02\xed\x01A"},
- {"compress/flate", "\x02l\x03\x80\x01\f\x033\x01\x03"},
- {"compress/gzip", "\x02\x04a\a\x03\x14lT"},
- {"compress/lzw", "\x02l\x03\x80\x01"},
- {"compress/zlib", "\x02\x04a\a\x03\x12\x01m"},
- {"container/heap", "\xb3\x02"},
+ {"compress/bzip2", "\x02\x02\xf1\x01A"},
+ {"compress/flate", "\x02o\x03\x81\x01\f\x033\x01\x03"},
+ {"compress/gzip", "\x02\x04d\a\x03\x14mT"},
+ {"compress/lzw", "\x02o\x03\x81\x01"},
+ {"compress/zlib", "\x02\x04d\a\x03\x12\x01n"},
+ {"container/heap", "\xb7\x02"},
{"container/list", ""},
{"container/ring", ""},
- {"context", "n\\m\x01\r"},
- {"crypto", "\x83\x01nC"},
- {"crypto/aes", "\x10\n\a\x93\x02"},
- {"crypto/cipher", "\x03\x1e\x01\x01\x1e\x11\x1c+X"},
- {"crypto/des", "\x10\x13\x1e-+\x9b\x01\x03"},
- {"crypto/dsa", "A\x04)\x83\x01\r"},
- {"crypto/ecdh", "\x03\v\f\x0e\x04\x15\x04\r\x1c\x83\x01"},
- {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x0e\a\v\x05\x01\x04\f\x01\x1c\x83\x01\r\x05K\x01"},
- {"crypto/ed25519", "\x0e\x1c\x11\x06\n\a\x1c\x83\x01C"},
- {"crypto/elliptic", "0>\x83\x01\r9"},
- {"crypto/fips140", " \x05"},
- {"crypto/hkdf", "-\x13\x01-\x15"},
- {"crypto/hmac", "\x1a\x14\x12\x01\x111"},
- {"crypto/internal/boring", "\x0e\x02\rf"},
- {"crypto/internal/boring/bbig", "\x1a\xe4\x01M"},
- {"crypto/internal/boring/bcache", "\xb8\x02\x13"},
+ {"context", "q[o\x01\r"},
+ {"crypto", "\x86\x01oC"},
+ {"crypto/aes", "\x10\n\t\x95\x02"},
+ {"crypto/cipher", "\x03 \x01\x01\x1f\x11\x1c+Y"},
+ {"crypto/des", "\x10\x15\x1f-+\x9c\x01\x03"},
+ {"crypto/dsa", "D\x04)\x84\x01\r"},
+ {"crypto/ecdh", "\x03\v\f\x10\x04\x16\x04\r\x1c\x84\x01"},
+ {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\a\v\x06\x01\x04\f\x01\x1c\x84\x01\r\x05K\x01"},
+ {"crypto/ed25519", "\x0e\x1e\x11\a\n\a\x1c\x84\x01C"},
+ {"crypto/elliptic", "2?\x84\x01\r9"},
+ {"crypto/fips140", "\"\x05"},
+ {"crypto/hkdf", "/\x14\x01-\x15"},
+ {"crypto/hmac", "\x1a\x16\x13\x01\x111"},
+ {"crypto/internal/boring", "\x0e\x02\ri"},
+ {"crypto/internal/boring/bbig", "\x1a\xe8\x01M"},
+ {"crypto/internal/boring/bcache", "\xbc\x02\x13"},
{"crypto/internal/boring/sig", ""},
- {"crypto/internal/cryptotest", "\x03\r\n\x06$\x0e\x19\x06\x12\x12 \x04\a\t\x16\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"},
- {"crypto/internal/entropy", "F"},
- {"crypto/internal/fips140", "?/\x15\xa7\x01\v\x16"},
- {"crypto/internal/fips140/aes", "\x03\x1d\x03\x02\x13\x05\x01\x01\x05*\x92\x014"},
- {"crypto/internal/fips140/aes/gcm", " \x01\x02\x02\x02\x11\x05\x01\x06*\x8f\x01"},
- {"crypto/internal/fips140/alias", "\xcb\x02"},
- {"crypto/internal/fips140/bigmod", "%\x18\x01\x06*\x92\x01"},
- {"crypto/internal/fips140/check", " \x0e\x06\t\x02\xb2\x01Z"},
- {"crypto/internal/fips140/check/checktest", "%\x85\x02!"},
- {"crypto/internal/fips140/drbg", "\x03\x1c\x01\x01\x04\x13\x05\b\x01(\x83\x01\x0f7"},
- {"crypto/internal/fips140/ecdh", "\x03\x1d\x05\x02\t\r1\x83\x01\x0f7"},
- {"crypto/internal/fips140/ecdsa", "\x03\x1d\x04\x01\x02\a\x02\x068\x15nF"},
- {"crypto/internal/fips140/ed25519", "\x03\x1d\x05\x02\x04\v8\xc6\x01\x03"},
- {"crypto/internal/fips140/edwards25519", "%\a\f\x051\x92\x017"},
- {"crypto/internal/fips140/edwards25519/field", "%\x13\x051\x92\x01"},
- {"crypto/internal/fips140/hkdf", "\x03\x1d\x05\t\x06:\x15"},
- {"crypto/internal/fips140/hmac", "\x03\x1d\x14\x01\x018\x15"},
- {"crypto/internal/fips140/mlkem", "\x03\x1d\x05\x02\x0e\x03\x051"},
- {"crypto/internal/fips140/nistec", "%\f\a\x051\x92\x01*\r\x14"},
- {"crypto/internal/fips140/nistec/fiat", "%\x136\x92\x01"},
- {"crypto/internal/fips140/pbkdf2", "\x03\x1d\x05\t\x06:\x15"},
- {"crypto/internal/fips140/rsa", "\x03\x1d\x04\x01\x02\r\x01\x01\x026\x15nF"},
- {"crypto/internal/fips140/sha256", "\x03\x1d\x1d\x01\x06*\x15}"},
- {"crypto/internal/fips140/sha3", "\x03\x1d\x18\x05\x010\x92\x01K"},
- {"crypto/internal/fips140/sha512", "\x03\x1d\x1d\x01\x06*\x15}"},
- {"crypto/internal/fips140/ssh", "%^"},
- {"crypto/internal/fips140/subtle", "#\x1a\xc3\x01"},
- {"crypto/internal/fips140/tls12", "\x03\x1d\x05\t\x06\x028\x15"},
- {"crypto/internal/fips140/tls13", "\x03\x1d\x05\b\a\t1\x15"},
- {"crypto/internal/fips140cache", "\xaa\x02\r&"},
+ {"crypto/internal/constanttime", ""},
+ {"crypto/internal/cryptotest", "\x03\r\n\b%\x0e\x19\x06\x12\x12 \x04\x06\t\x18\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"},
+ {"crypto/internal/entropy", "I"},
+ {"crypto/internal/entropy/v1.0.0", "B/\x93\x018\x13"},
+ {"crypto/internal/fips140", "A0\xbd\x01\v\x16"},
+ {"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x13\x05\x01\x01\x06*\x93\x014"},
+ {"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x11\x05\x01\a*\x90\x01"},
+ {"crypto/internal/fips140/alias", "\xcf\x02"},
+ {"crypto/internal/fips140/bigmod", "'\x18\x01\a*\x93\x01"},
+ {"crypto/internal/fips140/check", "\"\x0e\x06\t\x02\xb4\x01Z"},
+ {"crypto/internal/fips140/check/checktest", "'\x87\x02!"},
+ {"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x13\x05\t\x01(\x84\x01\x0f7\x01"},
+ {"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\t\r2\x84\x01\x0f7"},
+ {"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x02\x069\x15oF"},
+ {"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\v9\xc7\x01\x03"},
+ {"crypto/internal/fips140/edwards25519", "\x1e\t\a\x112\x93\x017"},
+ {"crypto/internal/fips140/edwards25519/field", "'\x13\x052\x93\x01"},
+ {"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\x06;\x15"},
+ {"crypto/internal/fips140/hmac", "\x03\x1f\x14\x01\x019\x15"},
+ {"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0e\x03\x052\xca\x01"},
+ {"crypto/internal/fips140/nistec", "\x1e\t\f\f2\x93\x01*\r\x14"},
+ {"crypto/internal/fips140/nistec/fiat", "'\x137\x93\x01"},
+ {"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\x06;\x15"},
+ {"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\r\x01\x01\x027\x15oF"},
+ {"crypto/internal/fips140/sha256", "\x03\x1f\x1d\x01\a*\x15~"},
+ {"crypto/internal/fips140/sha3", "\x03\x1f\x18\x05\x011\x93\x01K"},
+ {"crypto/internal/fips140/sha512", "\x03\x1f\x1d\x01\a*\x15~"},
+ {"crypto/internal/fips140/ssh", "'_"},
+ {"crypto/internal/fips140/subtle", "\x1e\a\x1a\xc5\x01"},
+ {"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\x06\x029\x15"},
+ {"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\a\t2\x15"},
+ {"crypto/internal/fips140cache", "\xae\x02\r&"},
{"crypto/internal/fips140deps", ""},
- {"crypto/internal/fips140deps/byteorder", "\x99\x01"},
- {"crypto/internal/fips140deps/cpu", "\xae\x01\a"},
- {"crypto/internal/fips140deps/godebug", "\xb6\x01"},
- {"crypto/internal/fips140hash", "5\x1b3\xc8\x01"},
- {"crypto/internal/fips140only", "'\r\x01\x01M3;"},
+ {"crypto/internal/fips140deps/byteorder", "\x9c\x01"},
+ {"crypto/internal/fips140deps/cpu", "\xb1\x01\a"},
+ {"crypto/internal/fips140deps/godebug", "\xb9\x01"},
+ {"crypto/internal/fips140deps/time", "\xc9\x02"},
+ {"crypto/internal/fips140hash", "7\x1c3\xc9\x01"},
+ {"crypto/internal/fips140only", ")\r\x01\x01N3<"},
{"crypto/internal/fips140test", ""},
- {"crypto/internal/hpke", "\x0e\x01\x01\x03\x053#+gM"},
- {"crypto/internal/impl", "\xb5\x02"},
- {"crypto/internal/randutil", "\xf1\x01\x12"},
- {"crypto/internal/sysrand", "nn! \r\r\x01\x01\f\x06"},
- {"crypto/internal/sysrand/internal/seccomp", "n"},
- {"crypto/md5", "\x0e3-\x15\x16g"},
- {"crypto/mlkem", "/"},
- {"crypto/pbkdf2", "2\x0e\x01-\x15"},
- {"crypto/rand", "\x1a\x06\a\x1a\x04\x01(\x83\x01\rM"},
- {"crypto/rc4", "#\x1e-\xc6\x01"},
- {"crypto/rsa", "\x0e\f\x01\t\x0f\r\x01\x04\x06\a\x1c\x03\x123;\f\x01"},
- {"crypto/sha1", "\x0e\f'\x03*\x15\x16\x15R"},
- {"crypto/sha256", "\x0e\f\x1aO"},
- {"crypto/sha3", "\x0e'N\xc8\x01"},
- {"crypto/sha512", "\x0e\f\x1cM"},
- {"crypto/subtle", "8\x9b\x01W"},
- {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\a\x01\r\n\x01\t\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x12\x16\x15\b;\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"},
- {"crypto/tls/internal/fips140tls", "\x17\xa1\x02"},
- {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x012\x05\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x038\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\x02\x05\n\x01\x02\x0e\x02\x01\x01\x02\x03\x01"},
- {"crypto/x509/pkix", "d\x06\a\x8d\x01G"},
- {"database/sql", "\x03\nK\x16\x03\x80\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"},
- {"database/sql/driver", "\ra\x03\xb4\x01\x0f\x11"},
- {"debug/buildinfo", "\x03X\x02\x01\x01\b\a\x03e\x19\x02\x01+\x0f\x1f"},
- {"debug/dwarf", "\x03d\a\x03\x80\x011\x11\x01\x01"},
- {"debug/elf", "\x03\x06Q\r\a\x03e\x1a\x01,\x17\x01\x16"},
- {"debug/gosym", "\x03d\n\xc2\x01\x01\x01\x02"},
- {"debug/macho", "\x03\x06Q\r\ne\x1b,\x17\x01"},
- {"debug/pe", "\x03\x06Q\r\a\x03e\x1b,\x17\x01\x16"},
- {"debug/plan9obj", "g\a\x03e\x1b,"},
- {"embed", "n*@\x19\x01S"},
+ {"crypto/internal/hpke", "\x0e\x01\x01\x03\x056#+hM"},
+ {"crypto/internal/impl", "\xb9\x02"},
+ {"crypto/internal/randutil", "\xf5\x01\x12"},
+ {"crypto/internal/sysrand", "qo! \r\r\x01\x01\f\x06"},
+ {"crypto/internal/sysrand/internal/seccomp", "q"},
+ {"crypto/md5", "\x0e6-\x15\x16h"},
+ {"crypto/mlkem", "1"},
+ {"crypto/pbkdf2", "4\x0f\x01-\x15"},
+ {"crypto/rand", "\x1a\b\a\x1b\x04\x01(\x84\x01\rM"},
+ {"crypto/rc4", "%\x1f-\xc7\x01"},
+ {"crypto/rsa", "\x0e\f\x01\v\x0f\x0e\x01\x04\x06\a\x1c\x03\x123<\f\x01"},
+ {"crypto/sha1", "\x0e\f*\x03*\x15\x16\x15S"},
+ {"crypto/sha256", "\x0e\f\x1cP"},
+ {"crypto/sha3", "\x0e)O\xc9\x01"},
+ {"crypto/sha512", "\x0e\f\x1eN"},
+ {"crypto/subtle", "\x1e\x1c\x9c\x01X"},
+ {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\r\n\x01\n\x05\x03\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x12\x16\x15\b<\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"},
+ {"crypto/tls/internal/fips140tls", "\x17\xa5\x02"},
+ {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x015\x05\x01\x01\x02\x05\x0e\x06\x02\x02\x03E\x039\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\x02\x05\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"},
+ {"crypto/x509/pkix", "g\x06\a\x8e\x01G"},
+ {"database/sql", "\x03\nN\x16\x03\x81\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"},
+ {"database/sql/driver", "\rd\x03\xb5\x01\x0f\x11"},
+ {"debug/buildinfo", "\x03[\x02\x01\x01\b\a\x03e\x1a\x02\x01+\x0f\x1f"},
+ {"debug/dwarf", "\x03g\a\x03\x81\x011\x11\x01\x01"},
+ {"debug/elf", "\x03\x06T\r\a\x03e\x1b\x01\f \x17\x01\x16"},
+ {"debug/gosym", "\x03g\n\xc3\x01\x01\x01\x02"},
+ {"debug/macho", "\x03\x06T\r\ne\x1c,\x17\x01"},
+ {"debug/pe", "\x03\x06T\r\a\x03e\x1c,\x17\x01\x16"},
+ {"debug/plan9obj", "j\a\x03e\x1c,"},
+ {"embed", "q*A\x19\x01S"},
{"embed/internal/embedtest", ""},
{"encoding", ""},
- {"encoding/ascii85", "\xf1\x01C"},
- {"encoding/asn1", "\x03k\x03\x8c\x01\x01'\r\x02\x01\x10\x03\x01"},
- {"encoding/base32", "\xf1\x01A\x02"},
- {"encoding/base64", "\x99\x01XA\x02"},
- {"encoding/binary", "n\x83\x01\f(\r\x05"},
- {"encoding/csv", "\x02\x01k\x03\x80\x01D\x12\x02"},
- {"encoding/gob", "\x02`\x05\a\x03e\x1b\v\x01\x03\x1d\b\x12\x01\x0f\x02"},
- {"encoding/hex", "n\x03\x80\x01A\x03"},
- {"encoding/json", "\x03\x01^\x04\b\x03\x80\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"},
- {"encoding/pem", "\x03c\b\x83\x01A\x03"},
- {"encoding/xml", "\x02\x01_\f\x03\x80\x014\x05\n\x01\x02\x10\x02"},
- {"errors", "\xca\x01\x81\x01"},
- {"expvar", "kK?\b\v\x15\r\b\x02\x03\x01\x11"},
- {"flag", "b\f\x03\x80\x01,\b\x05\b\x02\x01\x10"},
- {"fmt", "nE>\f \b\r\x02\x03\x12"},
- {"go/ast", "\x03\x01m\x0e\x01q\x03)\b\r\x02\x01"},
- {"go/build", "\x02\x01k\x03\x01\x02\x02\a\x02\x01\x17\x1f\x04\x02\t\x19\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"},
- {"go/build/constraint", "n\xc6\x01\x01\x12\x02"},
- {"go/constant", "q\x0f}\x01\x024\x01\x02\x12"},
- {"go/doc", "\x04m\x01\x05\t>31\x10\x02\x01\x12\x02"},
- {"go/doc/comment", "\x03n\xc1\x01\x01\x01\x01\x12\x02"},
- {"go/format", "\x03n\x01\v\x01\x02qD"},
- {"go/importer", "s\a\x01\x01\x04\x01p9"},
- {"go/internal/gccgoimporter", "\x02\x01X\x13\x03\x04\v\x01n\x02,\x01\x05\x11\x01\f\b"},
- {"go/internal/gcimporter", "\x02o\x0f\x010\x05\x0e-,\x15\x03\x02"},
- {"go/internal/srcimporter", "q\x01\x01\n\x03\x01p,\x01\x05\x12\x02\x14"},
- {"go/parser", "\x03k\x03\x01\x02\v\x01q\x01+\x06\x12"},
- {"go/printer", "q\x01\x02\x03\tq\f \x15\x02\x01\x02\v\x05\x02"},
- {"go/scanner", "\x03n\x0fq2\x10\x01\x13\x02"},
- {"go/token", "\x04m\x83\x01>\x02\x03\x01\x0f\x02"},
- {"go/types", "\x03\x01\x06d\x03\x01\x03\b\x03\x02\x15\x1f\x061\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"},
- {"go/version", "\xbb\x01z"},
- {"hash", "\xf1\x01"},
- {"hash/adler32", "n\x15\x16"},
- {"hash/crc32", "n\x15\x16\x15\x89\x01\x01\x13"},
- {"hash/crc64", "n\x15\x16\x9e\x01"},
- {"hash/fnv", "n\x15\x16g"},
- {"hash/maphash", "\x83\x01\x11!\x03\x93\x01"},
- {"html", "\xb5\x02\x02\x12"},
- {"html/template", "\x03h\x06\x18-;\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"},
- {"image", "\x02l\x1ee\x0f4\x03\x01"},
+ {"encoding/ascii85", "\xf5\x01C"},
+ {"encoding/asn1", "\x03n\x03e(\x01'\r\x02\x01\x10\x03\x01"},
+ {"encoding/base32", "\xf5\x01A\x02"},
+ {"encoding/base64", "\x9c\x01YA\x02"},
+ {"encoding/binary", "q\x84\x01\f(\r\x05"},
+ {"encoding/csv", "\x02\x01n\x03\x81\x01D\x12\x02"},
+ {"encoding/gob", "\x02c\x05\a\x03e\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"},
+ {"encoding/hex", "q\x03\x81\x01A\x03"},
+ {"encoding/json", "\x03\x01a\x04\b\x03\x81\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"},
+ {"encoding/pem", "\x03f\b\x84\x01A\x03"},
+ {"encoding/xml", "\x02\x01b\f\x03\x81\x014\x05\n\x01\x02\x10\x02"},
+ {"errors", "\xcc\x01\x83\x01"},
+ {"expvar", "nK@\b\v\x15\r\b\x02\x03\x01\x11"},
+ {"flag", "e\f\x03\x81\x01,\b\x05\b\x02\x01\x10"},
+ {"fmt", "qE&\x19\f \b\r\x02\x03\x12"},
+ {"go/ast", "\x03\x01p\x0e\x01r\x03)\b\r\x02\x01\x12\x02"},
+ {"go/build", "\x02\x01n\x03\x01\x02\x02\a\x02\x01\x17\x1f\x04\x02\b\x1b\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"},
+ {"go/build/constraint", "q\xc7\x01\x01\x12\x02"},
+ {"go/constant", "t\x0f~\x01\x024\x01\x02\x12"},
+ {"go/doc", "\x04p\x01\x05\t=51\x10\x02\x01\x12\x02"},
+ {"go/doc/comment", "\x03q\xc2\x01\x01\x01\x01\x12\x02"},
+ {"go/format", "\x03q\x01\v\x01\x02rD"},
+ {"go/importer", "v\a\x01\x01\x04\x01q9"},
+ {"go/internal/gccgoimporter", "\x02\x01[\x13\x03\x04\v\x01o\x02,\x01\x05\x11\x01\f\b"},
+ {"go/internal/gcimporter", "\x02r\x0f\x010\x05\r/,\x15\x03\x02"},
+ {"go/internal/srcimporter", "t\x01\x01\n\x03\x01q,\x01\x05\x12\x02\x14"},
+ {"go/parser", "\x03n\x03\x01\x02\v\x01r\x01+\x06\x12"},
+ {"go/printer", "t\x01\x02\x03\tr\f \x15\x02\x01\x02\v\x05\x02"},
+ {"go/scanner", "\x03q\x0fr2\x10\x01\x13\x02"},
+ {"go/token", "\x04p\x84\x01>\x02\x03\x01\x0f\x02"},
+ {"go/types", "\x03\x01\x06g\x03\x01\x03\b\x03\x024\x062\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"},
+ {"go/version", "\xbe\x01{"},
+ {"hash", "\xf5\x01"},
+ {"hash/adler32", "q\x15\x16"},
+ {"hash/crc32", "q\x15\x16\x15\x8a\x01\x01\x13"},
+ {"hash/crc64", "q\x15\x16\x9f\x01"},
+ {"hash/fnv", "q\x15\x16h"},
+ {"hash/maphash", "\x86\x01\x11<|"},
+ {"html", "\xb9\x02\x02\x12"},
+ {"html/template", "\x03k\x06\x18-<\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"},
+ {"image", "\x02o\x1ef\x0f4\x03\x01"},
{"image/color", ""},
- {"image/color/palette", "\x8c\x01"},
- {"image/draw", "\x8b\x01\x01\x04"},
- {"image/gif", "\x02\x01\x05f\x03\x1a\x01\x01\x01\vX"},
- {"image/internal/imageutil", "\x8b\x01"},
- {"image/jpeg", "\x02l\x1d\x01\x04a"},
- {"image/png", "\x02\a^\n\x12\x02\x06\x01eC"},
- {"index/suffixarray", "\x03d\a\x83\x01\f+\n\x01"},
- {"internal/abi", "\xb5\x01\x96\x01"},
- {"internal/asan", "\xcb\x02"},
- {"internal/bisect", "\xaa\x02\r\x01"},
- {"internal/buildcfg", "qGe\x06\x02\x05\n\x01"},
- {"internal/bytealg", "\xae\x01\x9d\x01"},
+ {"image/color/palette", "\x8f\x01"},
+ {"image/draw", "\x8e\x01\x01\x04"},
+ {"image/gif", "\x02\x01\x05i\x03\x1a\x01\x01\x01\vY"},
+ {"image/internal/imageutil", "\x8e\x01"},
+ {"image/jpeg", "\x02o\x1d\x01\x04b"},
+ {"image/png", "\x02\aa\n\x12\x02\x06\x01fC"},
+ {"index/suffixarray", "\x03g\a\x84\x01\f+\n\x01"},
+ {"internal/abi", "\xb8\x01\x97\x01"},
+ {"internal/asan", "\xcf\x02"},
+ {"internal/bisect", "\xae\x02\r\x01"},
+ {"internal/buildcfg", "tGf\x06\x02\x05\n\x01"},
+ {"internal/bytealg", "\xb1\x01\x9e\x01"},
{"internal/byteorder", ""},
{"internal/cfg", ""},
- {"internal/cgrouptest", "q[Q\x06\x0f\x02\x01\x04\x01"},
- {"internal/chacha8rand", "\x99\x01\x15\a\x96\x01"},
+ {"internal/cgrouptest", "tZS\x06\x0f\x02\x01\x04\x01"},
+ {"internal/chacha8rand", "\x9c\x01\x15\a\x97\x01"},
{"internal/copyright", ""},
{"internal/coverage", ""},
{"internal/coverage/calloc", ""},
- {"internal/coverage/cfile", "k\x06\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01#\x02$,\x06\a\n\x01\x03\r\x06"},
- {"internal/coverage/cformat", "\x04m-\x04O\v6\x01\x02\r"},
- {"internal/coverage/cmerge", "q-_"},
- {"internal/coverage/decodecounter", "g\n-\v\x02F,\x17\x17"},
- {"internal/coverage/decodemeta", "\x02e\n\x16\x17\v\x02F,"},
- {"internal/coverage/encodecounter", "\x02e\n-\f\x01\x02D\v!\x15"},
- {"internal/coverage/encodemeta", "\x02\x01d\n\x12\x04\x17\r\x02D,."},
- {"internal/coverage/pods", "\x04m-\x7f\x06\x05\n\x02\x01"},
- {"internal/coverage/rtcov", "\xcb\x02"},
- {"internal/coverage/slicereader", "g\n\x80\x01Z"},
- {"internal/coverage/slicewriter", "q\x80\x01"},
- {"internal/coverage/stringtab", "q8\x04D"},
+ {"internal/coverage/cfile", "n\x06\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02&,\x06\a\n\x01\x03\r\x06"},
+ {"internal/coverage/cformat", "\x04p-\x04P\v6\x01\x02\r"},
+ {"internal/coverage/cmerge", "t-`"},
+ {"internal/coverage/decodecounter", "j\n-\v\x02G,\x17\x17"},
+ {"internal/coverage/decodemeta", "\x02h\n\x16\x17\v\x02G,"},
+ {"internal/coverage/encodecounter", "\x02h\n-\f\x01\x02E\v!\x15"},
+ {"internal/coverage/encodemeta", "\x02\x01g\n\x12\x04\x17\r\x02E,."},
+ {"internal/coverage/pods", "\x04p-\x80\x01\x06\x05\n\x02\x01"},
+ {"internal/coverage/rtcov", "\xcf\x02"},
+ {"internal/coverage/slicereader", "j\n\x81\x01Z"},
+ {"internal/coverage/slicewriter", "t\x81\x01"},
+ {"internal/coverage/stringtab", "t8\x04E"},
{"internal/coverage/test", ""},
{"internal/coverage/uleb128", ""},
- {"internal/cpu", "\xcb\x02"},
- {"internal/dag", "\x04m\xc1\x01\x03"},
- {"internal/diff", "\x03n\xc2\x01\x02"},
- {"internal/exportdata", "\x02\x01k\x03\x02c\x1b,\x01\x05\x11\x01\x02"},
- {"internal/filepathlite", "n*@\x1a@"},
- {"internal/fmtsort", "\x04\xa1\x02\r"},
- {"internal/fuzz", "\x03\nB\x18\x04\x03\x03\x01\v\x036;\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"},
+ {"internal/cpu", "\xcf\x02"},
+ {"internal/dag", "\x04p\xc2\x01\x03"},
+ {"internal/diff", "\x03q\xc3\x01\x02"},
+ {"internal/exportdata", "\x02\x01n\x03\x02c\x1c,\x01\x05\x11\x01\x02"},
+ {"internal/filepathlite", "q*A\x1a@"},
+ {"internal/fmtsort", "\x04\xa5\x02\r"},
+ {"internal/fuzz", "\x03\nE\x18\x04\x03\x03\x01\v\x036<\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"},
{"internal/goarch", ""},
- {"internal/godebug", "\x96\x01!\x80\x01\x01\x13"},
+ {"internal/godebug", "\x99\x01!\x81\x01\x01\x13"},
{"internal/godebugs", ""},
{"internal/goexperiment", ""},
{"internal/goos", ""},
- {"internal/goroot", "\x9d\x02\x01\x05\x12\x02"},
+ {"internal/goroot", "\xa1\x02\x01\x05\x12\x02"},
{"internal/gover", "\x04"},
{"internal/goversion", ""},
- {"internal/itoa", ""},
- {"internal/lazyregexp", "\x9d\x02\v\r\x02"},
- {"internal/lazytemplate", "\xf1\x01,\x18\x02\f"},
- {"internal/msan", "\xcb\x02"},
+ {"internal/lazyregexp", "\xa1\x02\v\r\x02"},
+ {"internal/lazytemplate", "\xf5\x01,\x18\x02\f"},
+ {"internal/msan", "\xcf\x02"},
{"internal/nettrace", ""},
- {"internal/obscuretestdata", "f\x8b\x01,"},
- {"internal/oserror", "n"},
- {"internal/pkgbits", "\x03L\x18\a\x03\x04\vq\r\x1f\r\n\x01"},
+ {"internal/obscuretestdata", "i\x8c\x01,"},
+ {"internal/oserror", "q"},
+ {"internal/pkgbits", "\x03O\x18\a\x03\x04\vr\r\x1f\r\n\x01"},
{"internal/platform", ""},
- {"internal/poll", "nO\x1f\x159\r\x01\x01\f\x06"},
- {"internal/profile", "\x03\x04g\x03\x80\x017\v\x01\x01\x10"},
+ {"internal/poll", "qj\x05\x159\r\x01\x01\f\x06"},
+ {"internal/profile", "\x03\x04j\x03\x81\x017\n\x01\x01\x01\x10"},
{"internal/profilerecord", ""},
- {"internal/race", "\x94\x01\xb7\x01"},
- {"internal/reflectlite", "\x94\x01!9\b\x13\x01\a\x03E;\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"},
- {"net/http/cgi", "\x02Q\x1b\x03\x80\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"},
- {"net/http/cookiejar", "\x04j\x03\x96\x01\x01\b\f\x16\x03\x02\x0e\x04"},
- {"net/http/fcgi", "\x02\x01\nZ\a\x03\x80\x01\x16\x01\x01\x14\x18\x02\x0e"},
- {"net/http/httptest", "\x02\x01\nF\x02\x1b\x01\x80\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"},
- {"net/http/httptrace", "\rFnF\x14\n "},
- {"net/http/httputil", "\x02\x01\na\x03\x80\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x0e\x0e"},
- {"net/http/internal", "\x02\x01k\x03\x80\x01"},
- {"net/http/internal/ascii", "\xb5\x02\x12"},
- {"net/http/internal/httpcommon", "\ra\x03\x9c\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"},
- {"net/http/internal/testcert", "\xb5\x02"},
- {"net/http/pprof", "\x02\x01\nd\x18-\x11*\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"},
+ {"log/slog/internal/benchmarks", "\rd\x03\x81\x01\x06\x03:\x11"},
+ {"log/slog/internal/buffer", "\xbb\x02"},
+ {"log/syslog", "q\x03\x85\x01\x12\x16\x18\x02\x0e"},
+ {"maps", "\xf8\x01W"},
+ {"math", "\xb1\x01SK"},
+ {"math/big", "\x03n\x03(\x15D\f\x03\x020\x02\x01\x02\x14"},
+ {"math/big/internal/asmgen", "\x03\x01p\x90\x012\x03"},
+ {"math/bits", "\xcf\x02"},
+ {"math/cmplx", "\x81\x02\x03"},
+ {"math/rand", "\xb9\x01H:\x01\x13"},
+ {"math/rand/v2", "q+\x03b\x03K"},
+ {"mime", "\x02\x01f\b\x03\x81\x01\v!\x15\x03\x02\x10\x02"},
+ {"mime/multipart", "\x02\x01K#\x03E<\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x16"},
+ {"mime/quotedprintable", "\x02\x01q\x81\x01"},
+ {"net", "\x04\td*\x1e\n\x05\x12\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\f\x06\a"},
+ {"net/http", "\x02\x01\x03\x01\x04\x02A\b\x13\x01\a\x03E<\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"},
+ {"net/http/cgi", "\x02T\x1b\x03\x81\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"},
+ {"net/http/cookiejar", "\x04m\x03\x97\x01\x01\b\f\x16\x03\x02\x0e\x04"},
+ {"net/http/fcgi", "\x02\x01\n]\a\x03\x81\x01\x16\x01\x01\x14\x18\x02\x0e"},
+ {"net/http/httptest", "\x02\x01\nI\x02\x1b\x01\x81\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"},
+ {"net/http/httptrace", "\rImH\x14\n "},
+ {"net/http/httputil", "\x02\x01\nd\x03\x81\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x0e\x0e"},
+ {"net/http/internal", "\x02\x01n\x03\x81\x01"},
+ {"net/http/internal/ascii", "\xb9\x02\x12"},
+ {"net/http/internal/httpcommon", "\rd\x03\x9d\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"},
+ {"net/http/internal/testcert", "\xb9\x02"},
+ {"net/http/pprof", "\x02\x01\ng\x18-\x02\x0e,\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"},
{"net/internal/cgotest", ""},
- {"net/internal/socktest", "q\xc6\x01\x02"},
- {"net/mail", "\x02l\x03\x80\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"},
- {"net/netip", "\x04j*\x01$@\x034\x16"},
- {"net/rpc", "\x02g\x05\x03\x0f\ng\x04\x12\x01\x1d\r\x03\x02"},
- {"net/rpc/jsonrpc", "k\x03\x03\x80\x01\x16\x11\x1f"},
- {"net/smtp", "\x19/\v\x13\b\x03\x80\x01\x16\x14\x1a"},
- {"net/textproto", "\x02\x01k\x03\x80\x01\f\n-\x01\x02\x14"},
- {"net/url", "n\x03\x8b\x01&\x10\x02\x01\x16"},
- {"os", "n*\x01\x19\x03\b\t\x12\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"},
- {"os/exec", "\x03\naH%\x01\x15\x01+\x06\a\n\x01\x04\f"},
- {"os/exec/internal/fdtest", "\xb9\x02"},
- {"os/signal", "\r\x90\x02\x15\x05\x02"},
- {"os/user", "\x02\x01k\x03\x80\x01,\r\n\x01\x02"},
- {"path", "n*\xb1\x01"},
- {"path/filepath", "n*\x1a@+\r\b\x03\x04\x10"},
- {"plugin", "n"},
- {"reflect", "n&\x04\x1d\b\f\x06\x04\x1b\x06\t-\n\x03\x10\x02\x02"},
+ {"net/internal/socktest", "t\xc7\x01\x02"},
+ {"net/mail", "\x02o\x03\x81\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"},
+ {"net/netip", "\x04m*\x01e\x034\x16"},
+ {"net/rpc", "\x02j\x05\x03\x0f\nh\x04\x12\x01\x1d\r\x03\x02"},
+ {"net/rpc/jsonrpc", "n\x03\x03\x81\x01\x16\x11\x1f"},
+ {"net/smtp", "\x192\v\x13\b\x03\x81\x01\x16\x14\x1a"},
+ {"net/textproto", "\x02\x01n\x03\x81\x01\f\n-\x01\x02\x14"},
+ {"net/url", "q\x03\xa7\x01\v\x10\x02\x01\x16"},
+ {"os", "q*\x01\x19\x03\x10\x13\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"},
+ {"os/exec", "\x03\ndH&\x01\x15\x01+\x06\a\n\x01\x04\f"},
+ {"os/exec/internal/fdtest", "\xbd\x02"},
+ {"os/signal", "\r\x94\x02\x15\x05\x02"},
+ {"os/user", "\x02\x01n\x03\x81\x01,\r\n\x01\x02"},
+ {"path", "q*\xb2\x01"},
+ {"path/filepath", "q*\x1aA+\r\b\x03\x04\x10"},
+ {"plugin", "q"},
+ {"reflect", "q&\x04\x1d\x13\b\x03\x05\x17\x06\t-\n\x03\x10\x02\x02"},
{"reflect/internal/example1", ""},
{"reflect/internal/example2", ""},
- {"regexp", "\x03\xee\x018\t\x02\x01\x02\x10\x02"},
- {"regexp/syntax", "\xb2\x02\x01\x01\x01\x02\x10\x02"},
- {"runtime", "\x94\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0f\x03\x01\x01\x01\x01\x01\x02\x01\x01\x04\x10c"},
- {"runtime/coverage", "\xa0\x01Q"},
- {"runtime/debug", "qUW\r\b\x02\x01\x10\x06"},
- {"runtime/metrics", "\xb7\x01F-!"},
- {"runtime/pprof", "\x02\x01\x01\x03\x06Z\a\x03#4)\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"},
- {"runtime/race", "\xb0\x02"},
+ {"regexp", "\x03\xf2\x018\t\x02\x01\x02\x10\x02"},
+ {"regexp/syntax", "\xb6\x02\x01\x01\x01\x02\x10\x02"},
+ {"runtime", "\x97\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x02\x01\x04\x01\x10c"},
+ {"runtime/coverage", "\xa3\x01R"},
+ {"runtime/debug", "tTY\r\b\x02\x01\x10\x06"},
+ {"runtime/metrics", "\xba\x01G-!"},
+ {"runtime/pprof", "\x02\x01\x01\x03\x06]\a\x03#$\x0f+\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"},
+ {"runtime/race", "\xb4\x02"},
{"runtime/race/internal/amd64v1", ""},
- {"runtime/trace", "\ra\x03w\t9\b\x05\x01\r\x06"},
- {"slices", "\x04\xf0\x01\fK"},
- {"sort", "\xca\x0162"},
- {"strconv", "n*@%\x03I"},
- {"strings", "n&\x04@\x19\x03\f7\x10\x02\x02"},
+ {"runtime/trace", "\rd\x03x\t9\b\x05\x01\r\x06"},
+ {"slices", "\x04\xf4\x01\fK"},
+ {"sort", "\xcc\x0182"},
+ {"strconv", "q*@\x01q"},
+ {"strings", "q&\x04A\x19\x03\f7\x10\x02\x02"},
{"structs", ""},
- {"sync", "\xc9\x01\x10\x01P\x0e\x13"},
- {"sync/atomic", "\xcb\x02"},
- {"syscall", "n'\x03\x01\x1c\b\x03\x03\x06\vV\b\x05\x01\x13"},
- {"testing", "\x03\na\x02\x01X\x14\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x02\x02"},
- {"testing/fstest", "n\x03\x80\x01\x01\n&\x10\x03\b\b"},
- {"testing/internal/testdeps", "\x02\v\xa7\x01-\x10,\x03\x05\x03\x06\a\x02\x0e"},
- {"testing/iotest", "\x03k\x03\x80\x01\x04"},
- {"testing/quick", "p\x01\x8c\x01\x05#\x10\x10"},
- {"testing/slogtest", "\ra\x03\x86\x01.\x05\x10\v"},
- {"testing/synctest", "\xda\x01`\x11"},
- {"text/scanner", "\x03n\x80\x01,*\x02"},
- {"text/tabwriter", "q\x80\x01X"},
- {"text/template", "n\x03B>\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"},
- {"text/template/parse", "\x03n\xb9\x01\n\x01\x12\x02"},
- {"time", "n*\x1e\"(*\r\x02\x12"},
- {"time/tzdata", "n\xcb\x01\x12"},
+ {"sync", "\xcb\x01\x12\x01P\x0e\x13"},
+ {"sync/atomic", "\xcf\x02"},
+ {"syscall", "q'\x03\x01\x1c\n\x03\x06\f\x04S\b\x05\x01\x13"},
+ {"testing", "\x03\nd\x02\x01W\x16\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x04"},
+ {"testing/fstest", "q\x03\x81\x01\x01\n&\x10\x03\b\b"},
+ {"testing/internal/testdeps", "\x02\v\xaa\x01.\x10,\x03\x05\x03\x06\a\x02\x0e"},
+ {"testing/iotest", "\x03n\x03\x81\x01\x04"},
+ {"testing/quick", "s\x01\x8d\x01\x05#\x10\x10"},
+ {"testing/slogtest", "\rd\x03\x87\x01.\x05\x10\v"},
+ {"testing/synctest", "\xde\x01`\x11"},
+ {"text/scanner", "\x03q\x81\x01,*\x02"},
+ {"text/tabwriter", "t\x81\x01X"},
+ {"text/template", "q\x03B?\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"},
+ {"text/template/parse", "\x03q\xba\x01\n\x01\x12\x02"},
+ {"time", "q*\x1e#(*\r\x02\x12"},
+ {"time/tzdata", "q\xcc\x01\x12"},
{"unicode", ""},
{"unicode/utf16", ""},
{"unicode/utf8", ""},
- {"unique", "\x94\x01!#\x01Q\r\x01\x13\x12"},
+ {"unique", "\x97\x01!$\x01Q\r\x01\x13\x12"},
{"unsafe", ""},
- {"vendor/golang.org/x/crypto/chacha20", "\x10W\a\x92\x01*&"},
- {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10W\a\xde\x01\x04\x01\a"},
- {"vendor/golang.org/x/crypto/cryptobyte", "d\n\x03\x8d\x01' \n"},
+ {"vendor/golang.org/x/crypto/chacha20", "\x10Z\a\x93\x01*&"},
+ {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10Z\a\xdf\x01\x04\x01\a"},
+ {"vendor/golang.org/x/crypto/cryptobyte", "g\n\x03\x8e\x01' \n"},
{"vendor/golang.org/x/crypto/cryptobyte/asn1", ""},
- {"vendor/golang.org/x/crypto/internal/alias", "\xcb\x02"},
- {"vendor/golang.org/x/crypto/internal/poly1305", "R\x15\x99\x01"},
- {"vendor/golang.org/x/net/dns/dnsmessage", "n"},
- {"vendor/golang.org/x/net/http/httpguts", "\x87\x02\x14\x1a\x14\r"},
- {"vendor/golang.org/x/net/http/httpproxy", "n\x03\x96\x01\x10\x05\x01\x18\x14\r"},
- {"vendor/golang.org/x/net/http2/hpack", "\x03k\x03\x80\x01F"},
- {"vendor/golang.org/x/net/idna", "q\x8c\x018\x14\x10\x02\x01"},
- {"vendor/golang.org/x/net/nettest", "\x03d\a\x03\x80\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"},
- {"vendor/golang.org/x/sys/cpu", "\x9d\x02\r\n\x01\x16"},
- {"vendor/golang.org/x/text/secure/bidirule", "n\xdb\x01\x11\x01"},
- {"vendor/golang.org/x/text/transform", "\x03k\x83\x01X"},
- {"vendor/golang.org/x/text/unicode/bidi", "\x03\bf\x84\x01>\x16"},
- {"vendor/golang.org/x/text/unicode/norm", "g\n\x80\x01F\x12\x11"},
- {"weak", "\x94\x01\x96\x01!"},
+ {"vendor/golang.org/x/crypto/internal/alias", "\xcf\x02"},
+ {"vendor/golang.org/x/crypto/internal/poly1305", "U\x15\x9a\x01"},
+ {"vendor/golang.org/x/net/dns/dnsmessage", "q"},
+ {"vendor/golang.org/x/net/http/httpguts", "\x8b\x02\x14\x1a\x14\r"},
+ {"vendor/golang.org/x/net/http/httpproxy", "q\x03\x97\x01\x10\x05\x01\x18\x14\r"},
+ {"vendor/golang.org/x/net/http2/hpack", "\x03n\x03\x81\x01F"},
+ {"vendor/golang.org/x/net/idna", "t\x8d\x018\x14\x10\x02\x01"},
+ {"vendor/golang.org/x/net/nettest", "\x03g\a\x03\x81\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"},
+ {"vendor/golang.org/x/sys/cpu", "\xa1\x02\r\n\x01\x16"},
+ {"vendor/golang.org/x/text/secure/bidirule", "q\xdc\x01\x11\x01"},
+ {"vendor/golang.org/x/text/transform", "\x03n\x84\x01X"},
+ {"vendor/golang.org/x/text/unicode/bidi", "\x03\bi\x85\x01>\x16"},
+ {"vendor/golang.org/x/text/unicode/norm", "j\n\x81\x01F\x12\x11"},
+ {"weak", "\x97\x01\x97\x01!"},
}
+
+// bootstrap is the list of bootstrap packages extracted from cmd/dist.
+var bootstrap = map[string]bool{
+ "cmp": true,
+ "cmd/asm": true,
+ "cmd/asm/internal/arch": true,
+ "cmd/asm/internal/asm": true,
+ "cmd/asm/internal/flags": true,
+ "cmd/asm/internal/lex": true,
+ "cmd/cgo": true,
+ "cmd/compile": true,
+ "cmd/compile/internal/abi": true,
+ "cmd/compile/internal/abt": true,
+ "cmd/compile/internal/amd64": true,
+ "cmd/compile/internal/arm": true,
+ "cmd/compile/internal/arm64": true,
+ "cmd/compile/internal/base": true,
+ "cmd/compile/internal/bitvec": true,
+ "cmd/compile/internal/compare": true,
+ "cmd/compile/internal/coverage": true,
+ "cmd/compile/internal/deadlocals": true,
+ "cmd/compile/internal/devirtualize": true,
+ "cmd/compile/internal/dwarfgen": true,
+ "cmd/compile/internal/escape": true,
+ "cmd/compile/internal/gc": true,
+ "cmd/compile/internal/importer": true,
+ "cmd/compile/internal/inline": true,
+ "cmd/compile/internal/inline/inlheur": true,
+ "cmd/compile/internal/inline/interleaved": true,
+ "cmd/compile/internal/ir": true,
+ "cmd/compile/internal/liveness": true,
+ "cmd/compile/internal/logopt": true,
+ "cmd/compile/internal/loong64": true,
+ "cmd/compile/internal/loopvar": true,
+ "cmd/compile/internal/mips": true,
+ "cmd/compile/internal/mips64": true,
+ "cmd/compile/internal/noder": true,
+ "cmd/compile/internal/objw": true,
+ "cmd/compile/internal/pgoir": true,
+ "cmd/compile/internal/pkginit": true,
+ "cmd/compile/internal/ppc64": true,
+ "cmd/compile/internal/rangefunc": true,
+ "cmd/compile/internal/reflectdata": true,
+ "cmd/compile/internal/riscv64": true,
+ "cmd/compile/internal/rttype": true,
+ "cmd/compile/internal/s390x": true,
+ "cmd/compile/internal/ssa": true,
+ "cmd/compile/internal/ssagen": true,
+ "cmd/compile/internal/staticdata": true,
+ "cmd/compile/internal/staticinit": true,
+ "cmd/compile/internal/syntax": true,
+ "cmd/compile/internal/test": true,
+ "cmd/compile/internal/typebits": true,
+ "cmd/compile/internal/typecheck": true,
+ "cmd/compile/internal/types": true,
+ "cmd/compile/internal/types2": true,
+ "cmd/compile/internal/walk": true,
+ "cmd/compile/internal/wasm": true,
+ "cmd/compile/internal/x86": true,
+ "cmd/internal/archive": true,
+ "cmd/internal/bio": true,
+ "cmd/internal/codesign": true,
+ "cmd/internal/dwarf": true,
+ "cmd/internal/edit": true,
+ "cmd/internal/gcprog": true,
+ "cmd/internal/goobj": true,
+ "cmd/internal/hash": true,
+ "cmd/internal/macho": true,
+ "cmd/internal/obj": true,
+ "cmd/internal/obj/arm": true,
+ "cmd/internal/obj/arm64": true,
+ "cmd/internal/obj/loong64": true,
+ "cmd/internal/obj/mips": true,
+ "cmd/internal/obj/ppc64": true,
+ "cmd/internal/obj/riscv": true,
+ "cmd/internal/obj/s390x": true,
+ "cmd/internal/obj/wasm": true,
+ "cmd/internal/obj/x86": true,
+ "cmd/internal/objabi": true,
+ "cmd/internal/par": true,
+ "cmd/internal/pgo": true,
+ "cmd/internal/pkgpath": true,
+ "cmd/internal/quoted": true,
+ "cmd/internal/src": true,
+ "cmd/internal/sys": true,
+ "cmd/internal/telemetry": true,
+ "cmd/internal/telemetry/counter": true,
+ "cmd/link": true,
+ "cmd/link/internal/amd64": true,
+ "cmd/link/internal/arm": true,
+ "cmd/link/internal/arm64": true,
+ "cmd/link/internal/benchmark": true,
+ "cmd/link/internal/dwtest": true,
+ "cmd/link/internal/ld": true,
+ "cmd/link/internal/loadelf": true,
+ "cmd/link/internal/loader": true,
+ "cmd/link/internal/loadmacho": true,
+ "cmd/link/internal/loadpe": true,
+ "cmd/link/internal/loadxcoff": true,
+ "cmd/link/internal/loong64": true,
+ "cmd/link/internal/mips": true,
+ "cmd/link/internal/mips64": true,
+ "cmd/link/internal/ppc64": true,
+ "cmd/link/internal/riscv64": true,
+ "cmd/link/internal/s390x": true,
+ "cmd/link/internal/sym": true,
+ "cmd/link/internal/wasm": true,
+ "cmd/link/internal/x86": true,
+ "compress/flate": true,
+ "compress/zlib": true,
+ "container/heap": true,
+ "debug/dwarf": true,
+ "debug/elf": true,
+ "debug/macho": true,
+ "debug/pe": true,
+ "go/build/constraint": true,
+ "go/constant": true,
+ "go/version": true,
+ "internal/abi": true,
+ "internal/coverage": true,
+ "cmd/internal/cov/covcmd": true,
+ "internal/bisect": true,
+ "internal/buildcfg": true,
+ "internal/exportdata": true,
+ "internal/goarch": true,
+ "internal/godebugs": true,
+ "internal/goexperiment": true,
+ "internal/goroot": true,
+ "internal/gover": true,
+ "internal/goversion": true,
+ "internal/lazyregexp": true,
+ "internal/pkgbits": true,
+ "internal/platform": true,
+ "internal/profile": true,
+ "internal/race": true,
+ "internal/runtime/gc": true,
+ "internal/saferio": true,
+ "internal/syscall/unix": true,
+ "internal/types/errors": true,
+ "internal/unsafeheader": true,
+ "internal/xcoff": true,
+ "internal/zstd": true,
+ "math/bits": true,
+ "sort": true,
+}
+
+// BootstrapVersion is the minor version of Go used during toolchain
+// bootstrapping. Packages for which [IsBootstrapPackage] must not use
+// features of Go newer than this version.
+const BootstrapVersion = Version(24) // go1.24.6
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/import.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/import.go
index f6909878a8..8ecc672b8b 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/import.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/import.go
@@ -87,3 +87,11 @@ func find(pkg string) (int, bool) {
return strings.Compare(p.name, n)
})
}
+
+// IsBootstrapPackage reports whether pkg is one of the low-level
+// packages in the Go distribution that must compile with the older
+// language version specified by [BootstrapVersion] during toolchain
+// bootstrapping; see golang.org/s/go15bootstrap.
+func IsBootstrapPackage(pkg string) bool {
+ return bootstrap[pkg]
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go
index c1faa50d36..362f23c436 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/stdlib/manifest.go
@@ -225,6 +225,7 @@ var PackageSymbols = map[string][]Symbol{
{"(*Buffer).Grow", Method, 1, ""},
{"(*Buffer).Len", Method, 0, ""},
{"(*Buffer).Next", Method, 0, ""},
+ {"(*Buffer).Peek", Method, 26, ""},
{"(*Buffer).Read", Method, 0, ""},
{"(*Buffer).ReadByte", Method, 0, ""},
{"(*Buffer).ReadBytes", Method, 0, ""},
@@ -1628,6 +1629,7 @@ var PackageSymbols = map[string][]Symbol{
{"ResultNoRows", Var, 0, ""},
{"Rows", Type, 0, ""},
{"RowsAffected", Type, 0, ""},
+ {"RowsColumnScanner", Type, 26, ""},
{"RowsColumnTypeDatabaseTypeName", Type, 8, ""},
{"RowsColumnTypeLength", Type, 8, ""},
{"RowsColumnTypeNullable", Type, 8, ""},
@@ -4953,6 +4955,7 @@ var PackageSymbols = map[string][]Symbol{
},
"errors": {
{"As", Func, 13, "func(err error, target any) bool"},
+ {"AsType", Func, 26, "func[E error](err error) (E, bool)"},
{"ErrUnsupported", Var, 21, ""},
{"Is", Func, 13, "func(err error, target error) bool"},
{"Join", Func, 20, "func(errs ...error) error"},
@@ -5090,7 +5093,7 @@ var PackageSymbols = map[string][]Symbol{
{"Append", Func, 19, "func(b []byte, a ...any) []byte"},
{"Appendf", Func, 19, "func(b []byte, format string, a ...any) []byte"},
{"Appendln", Func, 19, "func(b []byte, a ...any) []byte"},
- {"Errorf", Func, 0, "func(format string, a ...any) error"},
+ {"Errorf", Func, 0, "func(format string, a ...any) (err error)"},
{"FormatString", Func, 20, "func(state State, verb rune) string"},
{"Formatter", Type, 0, ""},
{"Fprint", Func, 0, "func(w io.Writer, a ...any) (n int, err error)"},
@@ -5155,6 +5158,9 @@ var PackageSymbols = map[string][]Symbol{
{"(*DeclStmt).Pos", Method, 0, ""},
{"(*DeferStmt).End", Method, 0, ""},
{"(*DeferStmt).Pos", Method, 0, ""},
+ {"(*Directive).End", Method, 26, ""},
+ {"(*Directive).ParseArgs", Method, 26, ""},
+ {"(*Directive).Pos", Method, 26, ""},
{"(*Ellipsis).End", Method, 0, ""},
{"(*Ellipsis).Pos", Method, 0, ""},
{"(*EmptyStmt).End", Method, 0, ""},
@@ -5320,6 +5326,15 @@ var PackageSymbols = map[string][]Symbol{
{"DeferStmt", Type, 0, ""},
{"DeferStmt.Call", Field, 0, ""},
{"DeferStmt.Defer", Field, 0, ""},
+ {"Directive", Type, 26, ""},
+ {"Directive.Args", Field, 26, ""},
+ {"Directive.ArgsPos", Field, 26, ""},
+ {"Directive.Name", Field, 26, ""},
+ {"Directive.Slash", Field, 26, ""},
+ {"Directive.Tool", Field, 26, ""},
+ {"DirectiveArg", Type, 26, ""},
+ {"DirectiveArg.Arg", Field, 26, ""},
+ {"DirectiveArg.Pos", Field, 26, ""},
{"Ellipsis", Type, 0, ""},
{"Ellipsis.Ellipsis", Field, 0, ""},
{"Ellipsis.Elt", Field, 0, ""},
@@ -5469,6 +5484,7 @@ var PackageSymbols = map[string][]Symbol{
{"ParenExpr.Lparen", Field, 0, ""},
{"ParenExpr.Rparen", Field, 0, ""},
{"ParenExpr.X", Field, 0, ""},
+ {"ParseDirective", Func, 26, "func(pos token.Pos, c string) (Directive, bool)"},
{"Pkg", Const, 0, ""},
{"Preorder", Func, 23, "func(root Node) iter.Seq[Node]"},
{"PreorderStack", Func, 25, "func(root Node, stack []Node, f func(n Node, stack []Node) bool)"},
@@ -7271,6 +7287,10 @@ var PackageSymbols = map[string][]Symbol{
{"(*Logger).WarnContext", Method, 21, ""},
{"(*Logger).With", Method, 21, ""},
{"(*Logger).WithGroup", Method, 21, ""},
+ {"(*MultiHandler).Enabled", Method, 26, ""},
+ {"(*MultiHandler).Handle", Method, 26, ""},
+ {"(*MultiHandler).WithAttrs", Method, 26, ""},
+ {"(*MultiHandler).WithGroup", Method, 26, ""},
{"(*Record).Add", Method, 21, ""},
{"(*Record).AddAttrs", Method, 21, ""},
{"(*TextHandler).Enabled", Method, 21, ""},
@@ -7358,9 +7378,11 @@ var PackageSymbols = map[string][]Symbol{
{"LogValuer", Type, 21, ""},
{"Logger", Type, 21, ""},
{"MessageKey", Const, 21, ""},
+ {"MultiHandler", Type, 26, ""},
{"New", Func, 21, "func(h Handler) *Logger"},
{"NewJSONHandler", Func, 21, "func(w io.Writer, opts *HandlerOptions) *JSONHandler"},
{"NewLogLogger", Func, 21, "func(h Handler, level Level) *log.Logger"},
+ {"NewMultiHandler", Func, 26, "func(handlers ...Handler) *MultiHandler"},
{"NewRecord", Func, 21, "func(t time.Time, level Level, msg string, pc uintptr) Record"},
{"NewTextHandler", Func, 21, "func(w io.Writer, opts *HandlerOptions) *TextHandler"},
{"Record", Type, 21, ""},
@@ -7515,7 +7537,7 @@ var PackageSymbols = map[string][]Symbol{
{"MinInt64", Const, 0, ""},
{"MinInt8", Const, 0, ""},
{"Mod", Func, 0, "func(x float64, y float64) float64"},
- {"Modf", Func, 0, "func(f float64) (int float64, frac float64)"},
+ {"Modf", Func, 0, "func(f float64) (integer float64, fractional float64)"},
{"NaN", Func, 0, "func() float64"},
{"Nextafter", Func, 0, "func(x float64, y float64) (r float64)"},
{"Nextafter32", Func, 4, "func(x float32, y float32) (r float32)"},
@@ -7972,6 +7994,10 @@ var PackageSymbols = map[string][]Symbol{
{"(*DNSError).Unwrap", Method, 23, ""},
{"(*Dialer).Dial", Method, 1, ""},
{"(*Dialer).DialContext", Method, 7, ""},
+ {"(*Dialer).DialIP", Method, 26, ""},
+ {"(*Dialer).DialTCP", Method, 26, ""},
+ {"(*Dialer).DialUDP", Method, 26, ""},
+ {"(*Dialer).DialUnix", Method, 26, ""},
{"(*Dialer).MultipathTCP", Method, 21, ""},
{"(*Dialer).SetMultipathTCP", Method, 21, ""},
{"(*IP).UnmarshalText", Method, 2, ""},
@@ -8457,6 +8483,7 @@ var PackageSymbols = map[string][]Symbol{
{"HTTP2Config.PermitProhibitedCipherSuites", Field, 24, ""},
{"HTTP2Config.PingTimeout", Field, 24, ""},
{"HTTP2Config.SendPingTimeout", Field, 24, ""},
+ {"HTTP2Config.StrictMaxConcurrentRequests", Field, 26, ""},
{"HTTP2Config.WriteByteTimeout", Field, 24, ""},
{"Handle", Func, 0, "func(pattern string, handler Handler)"},
{"HandleFunc", Func, 0, "func(pattern string, handler func(ResponseWriter, *Request))"},
@@ -8904,6 +8931,7 @@ var PackageSymbols = map[string][]Symbol{
{"(Prefix).AppendText", Method, 24, ""},
{"(Prefix).AppendTo", Method, 18, ""},
{"(Prefix).Bits", Method, 18, ""},
+ {"(Prefix).Compare", Method, 26, ""},
{"(Prefix).Contains", Method, 18, ""},
{"(Prefix).IsSingleIP", Method, 18, ""},
{"(Prefix).IsValid", Method, 18, ""},
@@ -9177,6 +9205,7 @@ var PackageSymbols = map[string][]Symbol{
{"(*Process).Release", Method, 0, ""},
{"(*Process).Signal", Method, 0, ""},
{"(*Process).Wait", Method, 0, ""},
+ {"(*Process).WithHandle", Method, 26, ""},
{"(*ProcessState).ExitCode", Method, 12, ""},
{"(*ProcessState).Exited", Method, 0, ""},
{"(*ProcessState).Pid", Method, 0, ""},
@@ -9234,6 +9263,7 @@ var PackageSymbols = map[string][]Symbol{
{"ErrExist", Var, 0, ""},
{"ErrInvalid", Var, 0, ""},
{"ErrNoDeadline", Var, 10, ""},
+ {"ErrNoHandle", Var, 26, ""},
{"ErrNotExist", Var, 0, ""},
{"ErrPermission", Var, 0, ""},
{"ErrProcessDone", Var, 16, ""},
@@ -9461,7 +9491,7 @@ var PackageSymbols = map[string][]Symbol{
{"ListSeparator", Const, 0, ""},
{"Localize", Func, 23, "func(path string) (string, error)"},
{"Match", Func, 0, "func(pattern string, name string) (matched bool, err error)"},
- {"Rel", Func, 0, "func(basepath string, targpath string) (string, error)"},
+ {"Rel", Func, 0, "func(basePath string, targPath string) (string, error)"},
{"Separator", Const, 0, ""},
{"SkipAll", Var, 20, ""},
{"SkipDir", Var, 0, ""},
@@ -9932,7 +9962,7 @@ var PackageSymbols = map[string][]Symbol{
{"PanicNilError", Type, 21, ""},
{"Pinner", Type, 21, ""},
{"ReadMemStats", Func, 0, "func(m *MemStats)"},
- {"ReadTrace", Func, 5, "func() []byte"},
+ {"ReadTrace", Func, 5, "func() (buf []byte)"},
{"SetBlockProfileRate", Func, 1, "func(rate int)"},
{"SetCPUProfileRate", Func, 0, "func(hz int)"},
{"SetCgoTraceback", Func, 7, "func(version int, traceback unsafe.Pointer, context unsafe.Pointer, symbolizer unsafe.Pointer)"},
@@ -16679,6 +16709,7 @@ var PackageSymbols = map[string][]Symbol{
{"ValueOf", Func, 0, ""},
},
"testing": {
+ {"(*B).ArtifactDir", Method, 26, ""},
{"(*B).Attr", Method, 25, ""},
{"(*B).Chdir", Method, 24, ""},
{"(*B).Cleanup", Method, 14, ""},
@@ -16713,6 +16744,7 @@ var PackageSymbols = map[string][]Symbol{
{"(*B).StopTimer", Method, 0, ""},
{"(*B).TempDir", Method, 15, ""},
{"(*F).Add", Method, 18, ""},
+ {"(*F).ArtifactDir", Method, 26, ""},
{"(*F).Attr", Method, 25, ""},
{"(*F).Chdir", Method, 24, ""},
{"(*F).Cleanup", Method, 18, ""},
@@ -16738,6 +16770,7 @@ var PackageSymbols = map[string][]Symbol{
{"(*F).TempDir", Method, 18, ""},
{"(*M).Run", Method, 4, ""},
{"(*PB).Next", Method, 3, ""},
+ {"(*T).ArtifactDir", Method, 26, ""},
{"(*T).Attr", Method, 25, ""},
{"(*T).Chdir", Method, 24, ""},
{"(*T).Cleanup", Method, 14, ""},
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typeparams/normalize.go b/src/cmd/vendor/golang.org/x/tools/internal/typeparams/normalize.go
index f49802b8ef..8d13f12147 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typeparams/normalize.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typeparams/normalize.go
@@ -160,8 +160,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
// The term set of an interface is the intersection of the term sets of its
// embedded types.
tset.terms = allTermlist
- for i := 0; i < u.NumEmbeddeds(); i++ {
- embedded := u.EmbeddedType(i)
+ for embedded := range u.EmbeddedTypes() {
if _, ok := embedded.Underlying().(*types.TypeParam); ok {
return nil, fmt.Errorf("invalid embedded type %T", embedded)
}
@@ -174,8 +173,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
case *types.Union:
// The term set of a union is the union of term sets of its terms.
tset.terms = nil
- for i := 0; i < u.Len(); i++ {
- t := u.Term(i)
+ for t := range u.Terms() {
var terms termlist
switch t.Type().Underlying().(type) {
case *types.Interface:
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/element.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/element.go
index 4957f02164..5fe4d8abcb 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/element.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/element.go
@@ -35,8 +35,8 @@ func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T type
// Recursion over signatures of each method.
tmset := msets.MethodSet(T)
- for i := 0; i < tmset.Len(); i++ {
- sig := tmset.At(i).Type().(*types.Signature)
+ for method := range tmset.Methods() {
+ sig := method.Type().(*types.Signature)
// It is tempting to call visit(sig, false)
// but, as noted in golang.org/cl/65450043,
// the Signature.Recv field is ignored by
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go
index f2affec4fb..e0d63c46c6 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go
@@ -48,7 +48,7 @@ func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
return ok &&
IsPackageLevel(obj) &&
f.Pkg().Path() == pkgPath &&
- f.Type().(*types.Signature).Recv() == nil &&
+ f.Signature().Recv() == nil &&
slices.Contains(names, f.Name())
}
@@ -60,7 +60,7 @@ func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
// which is important for the performance of syntax matching.
func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool {
if fn, ok := obj.(*types.Func); ok {
- if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
+ if recv := fn.Signature().Recv(); recv != nil {
_, T := ReceiverNamed(recv)
return T != nil &&
IsTypeNamed(T, pkgPath, typeName) &&
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go
index 64f47919f0..4e2756fc49 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/qualifier.go
@@ -19,7 +19,7 @@ import (
// TODO(adonovan): this function ignores the effect of shadowing. It
// should accept a [token.Pos] and a [types.Info] and compute only the
// set of imports that are not shadowed at that point, analogous to
-// [analysisinternal.AddImport]. It could also compute (as a side
+// [analysis.AddImport]. It could also compute (as a side
// effect) the set of additional imports required to ensure that there
// is an accessible import for each necessary package, making it
// converge even more closely with AddImport.
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go
index 453bba2ad5..d612a71029 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go
@@ -258,12 +258,12 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
case *types.Signature:
var params []*ast.Field
- for i := 0; i < t.Params().Len(); i++ {
+ for v := range t.Params().Variables() {
params = append(params, &ast.Field{
- Type: TypeExpr(t.Params().At(i).Type(), qual),
+ Type: TypeExpr(v.Type(), qual),
Names: []*ast.Ident{
{
- Name: t.Params().At(i).Name(),
+ Name: v.Name(),
},
},
})
@@ -273,9 +273,9 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt}
}
var returns []*ast.Field
- for i := 0; i < t.Results().Len(); i++ {
+ for v := range t.Results().Variables() {
returns = append(returns, &ast.Field{
- Type: TypeExpr(t.Results().At(i).Type(), qual),
+ Type: TypeExpr(v.Type(), qual),
})
}
return &ast.FuncType{
@@ -315,8 +315,8 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
if hasTypeArgs, ok := t.(interface{ TypeArgs() *types.TypeList }); ok {
if typeArgs := hasTypeArgs.TypeArgs(); typeArgs != nil && typeArgs.Len() > 0 {
var indices []ast.Expr
- for i := range typeArgs.Len() {
- indices = append(indices, TypeExpr(typeArgs.At(i), qual))
+ for t0 := range typeArgs.Types() {
+ indices = append(indices, TypeExpr(t0, qual))
}
expr = &ast.IndexListExpr{
X: expr,
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go b/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
index b53f178616..a5f4e3252c 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
@@ -7,13 +7,17 @@ package versions
// This file contains predicates for working with file versions to
// decide when a tool should consider a language feature enabled.
-// GoVersions that features in x/tools can be gated to.
+// named constants, to avoid misspelling
const (
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
Go1_21 = "go1.21"
Go1_22 = "go1.22"
+ Go1_23 = "go1.23"
+ Go1_24 = "go1.24"
+ Go1_25 = "go1.25"
+ Go1_26 = "go1.26"
)
// Future is an invalid unknown Go version sometime in the future.
diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt
index e92804a90d..80b23723bc 100644
--- a/src/cmd/vendor/modules.txt
+++ b/src/cmd/vendor/modules.txt
@@ -28,7 +28,7 @@ golang.org/x/arch/x86/x86asm
# golang.org/x/build v0.0.0-20250806225920-b7c66c047964
## explicit; go 1.23.0
golang.org/x/build/relnote
-# golang.org/x/mod v0.29.0
+# golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526
## explicit; go 1.24.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
@@ -39,16 +39,16 @@ golang.org/x/mod/sumdb/dirhash
golang.org/x/mod/sumdb/note
golang.org/x/mod/sumdb/tlog
golang.org/x/mod/zip
-# golang.org/x/sync v0.17.0
+# golang.org/x/sync v0.18.0
## explicit; go 1.24.0
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
-# golang.org/x/sys v0.37.0
+# golang.org/x/sys v0.38.0
## explicit; go 1.24.0
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
-# golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8
+# golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54
## explicit; go 1.24.0
golang.org/x/telemetry
golang.org/x/telemetry/counter
@@ -73,7 +73,7 @@ golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5
+# golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883
## explicit; go 1.24.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover
@@ -98,6 +98,7 @@ golang.org/x/tools/go/analysis/passes/httpresponse
golang.org/x/tools/go/analysis/passes/ifaceassert
golang.org/x/tools/go/analysis/passes/inline
golang.org/x/tools/go/analysis/passes/inspect
+golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal
golang.org/x/tools/go/analysis/passes/internal/gofixdirective
golang.org/x/tools/go/analysis/passes/loopclosure
golang.org/x/tools/go/analysis/passes/lostcancel
@@ -127,11 +128,13 @@ golang.org/x/tools/go/cfg
golang.org/x/tools/go/types/objectpath
golang.org/x/tools/go/types/typeutil
golang.org/x/tools/internal/aliases
-golang.org/x/tools/internal/analysisinternal
-golang.org/x/tools/internal/analysisinternal/generated
-golang.org/x/tools/internal/analysisinternal/typeindex
+golang.org/x/tools/internal/analysis/analyzerutil
+golang.org/x/tools/internal/analysis/driverutil
+golang.org/x/tools/internal/analysis/typeindex
golang.org/x/tools/internal/astutil
+golang.org/x/tools/internal/astutil/free
golang.org/x/tools/internal/bisect
+golang.org/x/tools/internal/cfginternal
golang.org/x/tools/internal/diff
golang.org/x/tools/internal/diff/lcs
golang.org/x/tools/internal/facts
diff --git a/src/go.mod b/src/go.mod
index c5e901b9ef..e6cb3d5b43 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -8,6 +8,6 @@ require (
)
require (
- golang.org/x/sys v0.37.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.30.0 // indirect
)
diff --git a/src/go.sum b/src/go.sum
index 4a52682161..fe184a8647 100644
--- a/src/go.sum
+++ b/src/go.sum
@@ -2,7 +2,7 @@ golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu.go b/src/vendor/golang.org/x/sys/cpu/cpu.go
index 63541994ef..34c9ae76ef 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu.go
@@ -92,6 +92,9 @@ var ARM64 struct {
HasSHA2 bool // SHA2 hardware implementation
HasCRC32 bool // CRC32 hardware implementation
HasATOMICS bool // Atomic memory operation instruction set
+ HasHPDS bool // Hierarchical permission disables in translations tables
+ HasLOR bool // Limited ordering regions
+ HasPAN bool // Privileged access never
HasFPHP bool // Half precision floating-point instruction set
HasASIMDHP bool // Advanced SIMD half precision instruction set
HasCPUID bool // CPUID identification scheme registers
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go
index af2aa99f9f..f449c679fe 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.go
@@ -65,10 +65,10 @@ func setMinimalFeatures() {
func readARM64Registers() {
Initialized = true
- parseARM64SystemRegisters(getisar0(), getisar1(), getpfr0())
+ parseARM64SystemRegisters(getisar0(), getisar1(), getmmfr1(), getpfr0())
}
-func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) {
+func parseARM64SystemRegisters(isar0, isar1, mmfr1, pfr0 uint64) {
// ID_AA64ISAR0_EL1
switch extractBits(isar0, 4, 7) {
case 1:
@@ -152,6 +152,22 @@ func parseARM64SystemRegisters(isar0, isar1, pfr0 uint64) {
ARM64.HasI8MM = true
}
+ // ID_AA64MMFR1_EL1
+ switch extractBits(mmfr1, 12, 15) {
+ case 1, 2:
+ ARM64.HasHPDS = true
+ }
+
+ switch extractBits(mmfr1, 16, 19) {
+ case 1:
+ ARM64.HasLOR = true
+ }
+
+ switch extractBits(mmfr1, 20, 23) {
+ case 1, 2, 3:
+ ARM64.HasPAN = true
+ }
+
// ID_AA64PFR0_EL1
switch extractBits(pfr0, 16, 19) {
case 0:
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s
index 22cc99844a..a4f24b3b0c 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_arm64.s
@@ -9,31 +9,34 @@
// func getisar0() uint64
TEXT ·getisar0(SB),NOSPLIT,$0-8
// get Instruction Set Attributes 0 into x0
- // mrs x0, ID_AA64ISAR0_EL1 = d5380600
- WORD $0xd5380600
+ MRS ID_AA64ISAR0_EL1, R0
MOVD R0, ret+0(FP)
RET
// func getisar1() uint64
TEXT ·getisar1(SB),NOSPLIT,$0-8
// get Instruction Set Attributes 1 into x0
- // mrs x0, ID_AA64ISAR1_EL1 = d5380620
- WORD $0xd5380620
+ MRS ID_AA64ISAR1_EL1, R0
+ MOVD R0, ret+0(FP)
+ RET
+
+// func getmmfr1() uint64
+TEXT ·getmmfr1(SB),NOSPLIT,$0-8
+ // get Memory Model Feature Register 1 into x0
+ MRS ID_AA64MMFR1_EL1, R0
MOVD R0, ret+0(FP)
RET
// func getpfr0() uint64
TEXT ·getpfr0(SB),NOSPLIT,$0-8
// get Processor Feature Register 0 into x0
- // mrs x0, ID_AA64PFR0_EL1 = d5380400
- WORD $0xd5380400
+ MRS ID_AA64PFR0_EL1, R0
MOVD R0, ret+0(FP)
RET
// func getzfr0() uint64
TEXT ·getzfr0(SB),NOSPLIT,$0-8
// get SVE Feature Register 0 into x0
- // mrs x0, ID_AA64ZFR0_EL1 = d5380480
- WORD $0xd5380480
+ MRS ID_AA64ZFR0_EL1, R0
MOVD R0, ret+0(FP)
RET
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go
index 6ac6e1efb2..e3fc5a8d31 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_gc_arm64.go
@@ -8,5 +8,6 @@ package cpu
func getisar0() uint64
func getisar1() uint64
+func getmmfr1() uint64
func getpfr0() uint64
func getzfr0() uint64
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go
index 7f1946780b..8df2079e15 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_gccgo_arm64.go
@@ -8,4 +8,5 @@ package cpu
func getisar0() uint64 { return 0 }
func getisar1() uint64 { return 0 }
+func getmmfr1() uint64 { return 0 }
func getpfr0() uint64 { return 0 }
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go
index ebfb3fc8e7..19aea0633e 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_netbsd_arm64.go
@@ -167,7 +167,7 @@ func doinit() {
setMinimalFeatures()
return
}
- parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64pfr0)
+ parseARM64SystemRegisters(cpuid.aa64isar0, cpuid.aa64isar1, cpuid.aa64mmfr1, cpuid.aa64pfr0)
Initialized = true
}
diff --git a/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go b/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go
index 85b64d5ccb..87fd3a7780 100644
--- a/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go
+++ b/src/vendor/golang.org/x/sys/cpu/cpu_openbsd_arm64.go
@@ -59,7 +59,7 @@ func doinit() {
if !ok {
return
}
- parseARM64SystemRegisters(isar0, isar1, 0)
+ parseARM64SystemRegisters(isar0, isar1, 0, 0)
Initialized = true
}
diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt
index f1e33686ed..bf7a797966 100644
--- a/src/vendor/modules.txt
+++ b/src/vendor/modules.txt
@@ -15,7 +15,7 @@ golang.org/x/net/http2/hpack
golang.org/x/net/idna
golang.org/x/net/lif
golang.org/x/net/nettest
-# golang.org/x/sys v0.37.0
+# golang.org/x/sys v0.38.0
## explicit; go 1.24.0
golang.org/x/sys/cpu
# golang.org/x/text v0.30.0
--
cgit v1.3
From d55ecea9e5a5a4cfba30c6f35d4841ae66e05ccd Mon Sep 17 00:00:00 2001
From: Michael Anthony Knyszek
Date: Fri, 14 Nov 2025 17:41:58 +0000
Subject: runtime: usleep before stealing runnext only if not in syscall
In the scheduler's steal path, we usleep(3) before stealing a _Prunning
P's runnext slot. Before CL 646198, we would not call usleep(3) if the P
was in _Psyscall. After CL 646198, Ps with Gs in syscalls stay in
_Prunning until stolen, meaning we might unnecessarily usleep(3) where
we didn't before. This probably isn't a huge deal in most cases, but can
cause some apparent slowdowns in microbenchmarks that frequently take
the steal path while there are syscalling goroutines.
Change-Id: I5bf3df10fe61cf8d7f0e9fe9522102de66faf344
Reviewed-on: https://go-review.googlesource.com/c/go/+/720441
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Pratt
---
src/runtime/proc.go | 47 ++++++++++++++++++++++++++++++-----------------
1 file changed, 30 insertions(+), 17 deletions(-)
(limited to 'src')
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 44dbb2fd44..61364d91ff 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -7507,23 +7507,36 @@ func runqgrab(pp *p, batch *[256]guintptr, batchHead uint32, stealRunNextG bool)
// Try to steal from pp.runnext.
if next := pp.runnext; next != 0 {
if pp.status == _Prunning {
- // Sleep to ensure that pp isn't about to run the g
- // we are about to steal.
- // The important use case here is when the g running
- // on pp ready()s another g and then almost
- // immediately blocks. Instead of stealing runnext
- // in this window, back off to give pp a chance to
- // schedule runnext. This will avoid thrashing gs
- // between different Ps.
- // A sync chan send/recv takes ~50ns as of time of
- // writing, so 3us gives ~50x overshoot.
- if !osHasLowResTimer {
- usleep(3)
- } else {
- // On some platforms system timer granularity is
- // 1-15ms, which is way too much for this
- // optimization. So just yield.
- osyield()
+ if mp := pp.m.ptr(); mp != nil {
+ if gp := mp.curg; gp == nil || readgstatus(gp)&^_Gscan != _Gsyscall {
+ // Sleep to ensure that pp isn't about to run the g
+ // we are about to steal.
+ // The important use case here is when the g running
+ // on pp ready()s another g and then almost
+ // immediately blocks. Instead of stealing runnext
+ // in this window, back off to give pp a chance to
+ // schedule runnext. This will avoid thrashing gs
+ // between different Ps.
+ // A sync chan send/recv takes ~50ns as of time of
+ // writing, so 3us gives ~50x overshoot.
+ // If curg is nil, we assume that the P is likely
+ // to be in the scheduler. If curg isn't nil and isn't
+ // in a syscall, then it's either running, waiting, or
+ // runnable. In this case we want to sleep because the
+ // P might either call into the scheduler soon (running),
+ // or already is (since we found a waiting or runnable
+ // goroutine hanging off of a running P, suggesting it
+ // either recently transitioned out of running, or will
+ // transition to running shortly).
+ if !osHasLowResTimer {
+ usleep(3)
+ } else {
+ // On some platforms system timer granularity is
+ // 1-15ms, which is way too much for this
+ // optimization. So just yield.
+ osyield()
+ }
+ }
}
}
if !pp.runnext.cas(next, 0) {
--
cgit v1.3
From c58d075e9a457fce92bdf60e2d1870c8c4df7dc5 Mon Sep 17 00:00:00 2001
From: Filippo Valsorda
Date: Sun, 7 Sep 2025 16:07:43 +0200
Subject: crypto/rsa: deprecate PKCS#1 v1.5 encryption
Fixes #75302
Change-Id: I6a6a6964c2b3b33bfb34b9677a57610b933bbfab
Reviewed-on: https://go-review.googlesource.com/c/go/+/701436
Reviewed-by: Daniel McCarney
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Filippo Valsorda
Reviewed-by: Michael Pratt
---
api/next/75302.txt | 4 +++
doc/next/6-stdlib/99-minor/crypto/rsa/75302.md | 2 ++
src/crypto/rsa/pkcs1v15.go | 37 +++++++++++++++++++-------
3 files changed, 34 insertions(+), 9 deletions(-)
create mode 100644 api/next/75302.txt
create mode 100644 doc/next/6-stdlib/99-minor/crypto/rsa/75302.md
(limited to 'src')
diff --git a/api/next/75302.txt b/api/next/75302.txt
new file mode 100644
index 0000000000..31474644b1
--- /dev/null
+++ b/api/next/75302.txt
@@ -0,0 +1,4 @@
+pkg crypto/rsa, func DecryptPKCS1v15 //deprecated #75302
+pkg crypto/rsa, func DecryptPKCS1v15SessionKey //deprecated #75302
+pkg crypto/rsa, func EncryptPKCS1v15 //deprecated #75302
+pkg crypto/rsa, type PKCS1v15DecryptOptions //deprecated #75302
diff --git a/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md b/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md
new file mode 100644
index 0000000000..611ba26158
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/rsa/75302.md
@@ -0,0 +1,2 @@
+Unsafe PKCS #1 v1.5 encryption padding (implemented by [EncryptPKCS1v15],
+[DecryptPKCS1v15], and [DecryptPKCS1v15SessionKey]) is now deprecated.
diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go
index f1e4ef48a4..76853a9445 100644
--- a/src/crypto/rsa/pkcs1v15.go
+++ b/src/crypto/rsa/pkcs1v15.go
@@ -18,6 +18,12 @@ import (
// PKCS1v15DecryptOptions is for passing options to PKCS #1 v1.5 decryption using
// the [crypto.Decrypter] interface.
+//
+// Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used.
+// See [draft-irtf-cfrg-rsa-guidance-05] for more information. Use
+// [EncryptOAEP] and [DecryptOAEP] instead.
+//
+// [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale
type PKCS1v15DecryptOptions struct {
// SessionKeyLen is the length of the session key that is being
// decrypted. If not zero, then a padding error during decryption will
@@ -37,8 +43,11 @@ type PKCS1v15DecryptOptions struct {
// deterministically on the bytes read from random, and may change
// between calls and/or between versions.
//
-// WARNING: use of this function to encrypt plaintexts other than
-// session keys is dangerous. Use RSA OAEP in new protocols.
+// Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used.
+// See [draft-irtf-cfrg-rsa-guidance-05] for more information. Use
+// [EncryptOAEP] and [DecryptOAEP] instead.
+//
+// [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale
func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
if fips140only.Enabled {
return nil, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode")
@@ -91,14 +100,17 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro
return rsa.Encrypt(fk, em)
}
-// DecryptPKCS1v15 decrypts a plaintext using RSA and the padding scheme from PKCS #1 v1.5.
-// The random parameter is legacy and ignored, and it can be nil.
+// DecryptPKCS1v15 decrypts a plaintext using RSA and the padding scheme from
+// PKCS #1 v1.5. The random parameter is legacy and ignored, and it can be nil.
//
-// Note that whether this function returns an error or not discloses secret
-// information. If an attacker can cause this function to run repeatedly and
-// learn whether each instance returned an error then they can decrypt and
-// forge signatures as if they had the private key. See
-// DecryptPKCS1v15SessionKey for a way of solving this problem.
+// Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used.
+// Whether this function returns an error or not discloses secret information.
+// If an attacker can cause this function to run repeatedly and learn whether
+// each instance returned an error then they can decrypt and forge signatures as
+// if they had the private key. See [draft-irtf-cfrg-rsa-guidance-05] for more
+// information. Use [EncryptOAEP] and [DecryptOAEP] instead.
+//
+// [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale
func DecryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error) {
if err := checkPublicKeySize(&priv.PublicKey); err != nil {
return nil, err
@@ -160,6 +172,13 @@ func DecryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]b
// Standard PKCS #1”, Daniel Bleichenbacher, Advances in Cryptology (Crypto '98)
// - [1] RFC 3218, Preventing the Million Message Attack on CMS,
// https://www.rfc-editor.org/rfc/rfc3218.html
+//
+// Deprecated: PKCS #1 v1.5 encryption is dangerous and should not be used. The
+// protections implemented by this function are limited and fragile, as
+// explained above. See [draft-irtf-cfrg-rsa-guidance-05] for more information.
+// Use [EncryptOAEP] and [DecryptOAEP] instead.
+//
+// [draft-irtf-cfrg-rsa-guidance-05]: https://www.ietf.org/archive/id/draft-irtf-cfrg-rsa-guidance-05.html#name-rationale
func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext []byte, key []byte) error {
if err := checkPublicKeySize(&priv.PublicKey); err != nil {
return err
--
cgit v1.3
From 594129b80cad4eb291a4185f3ac81699cfb3afd6 Mon Sep 17 00:00:00 2001
From: Jes Cok
Date: Sat, 15 Nov 2025 18:14:27 +0000
Subject: internal/runtime/maps: update doc for table.Clear
Change-Id: I9456b9fb7ed3bbf6a3c29de24951e02cf8f4635d
GitHub-Last-Rev: 2deaa1172570c8b477275dd636c092913692661b
GitHub-Pull-Request: golang/go#76311
Reviewed-on: https://go-review.googlesource.com/c/go/+/720761
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
Reviewed-by: Michael Pratt
---
src/internal/runtime/maps/table.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/internal/runtime/maps/table.go b/src/internal/runtime/maps/table.go
index fbce099655..8a1932e453 100644
--- a/src/internal/runtime/maps/table.go
+++ b/src/internal/runtime/maps/table.go
@@ -596,7 +596,7 @@ func (t *table) tombstones() uint16 {
return (t.capacity*maxAvgGroupLoad)/abi.MapGroupSlots - t.used - t.growthLeft
}
-// Clear deletes all entries from the map resulting in an empty map.
+// Clear deletes all entries from the table resulting in an empty table.
func (t *table) Clear(typ *abi.MapType) {
mgl := t.maxGrowthLeft()
if t.used == 0 && t.growthLeft == mgl { // no current entries and no tombstones
--
cgit v1.3
From 65c09eafdf316ef691f0f8eccbf860d2ef5f7c70 Mon Sep 17 00:00:00 2001
From: Archana Ravindar
Date: Fri, 7 Nov 2025 13:15:02 +0530
Subject: runtime: hoist invariant code out of heapBitsSmallForAddrInline
The first two instructions in heapBitsSmallForAddrInline are
invariant for a given span and object and are called in
a loop within ScanObjectsSmall which figures as a hot routine
in profiles of some benchmark runs within sweet benchmark suite
(x/benchmarks/sweet), Ideally it would have been great if the
compiler hoisted this code out of the loop, Moving it out of
inner loop manually gives gains (moving it entirely out of
nested loop does not improve performance, in some cases it
even regresses it perhaps due to the early loop exit).
Tested with AMD64, ARM64, PPC64LE and S390x
Fixes #76212
Change-Id: I49c3c826b9d7bf3125ffc42c8c174cce0ecc4cbf
Reviewed-on: https://go-review.googlesource.com/c/go/+/718680
Reviewed-by: Cherry Mui
Reviewed-by: Michael Knyszek
LUCI-TryBot-Result: Go LUCI
---
src/runtime/mgcmark_greenteagc.go | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
(limited to 'src')
diff --git a/src/runtime/mgcmark_greenteagc.go b/src/runtime/mgcmark_greenteagc.go
index 3594b33cfd..fa560f9966 100644
--- a/src/runtime/mgcmark_greenteagc.go
+++ b/src/runtime/mgcmark_greenteagc.go
@@ -978,7 +978,9 @@ func spanSetScans(spanBase uintptr, nelems uint16, imb *spanInlineMarkBits, toSc
}
func scanObjectSmall(spanBase, b, objSize uintptr, gcw *gcWork) {
- ptrBits := heapBitsSmallForAddrInline(spanBase, b, objSize)
+ hbitsBase, _ := spanHeapBitsRange(spanBase, gc.PageSize, objSize)
+ hbits := (*byte)(unsafe.Pointer(hbitsBase))
+ ptrBits := extractHeapBitsSmall(hbits, spanBase, b, objSize)
gcw.heapScanWork += int64(sys.Len64(uint64(ptrBits)) * goarch.PtrSize)
nptrs := 0
n := sys.OnesCount64(uint64(ptrBits))
@@ -1017,12 +1019,14 @@ func scanObjectsSmall(base, objSize uintptr, elems uint16, gcw *gcWork, scans *g
break
}
n := sys.OnesCount64(uint64(bits))
+ hbitsBase, _ := spanHeapBitsRange(base, gc.PageSize, objSize)
+ hbits := (*byte)(unsafe.Pointer(hbitsBase))
for range n {
j := sys.TrailingZeros64(uint64(bits))
bits &^= 1 << j
b := base + uintptr(i*(goarch.PtrSize*8)+j)*objSize
- ptrBits := heapBitsSmallForAddrInline(base, b, objSize)
+ ptrBits := extractHeapBitsSmall(hbits, base, b, objSize)
gcw.heapScanWork += int64(sys.Len64(uint64(ptrBits)) * goarch.PtrSize)
n := sys.OnesCount64(uint64(ptrBits))
@@ -1056,10 +1060,7 @@ func scanObjectsSmall(base, objSize uintptr, elems uint16, gcw *gcWork, scans *g
}
}
-func heapBitsSmallForAddrInline(spanBase, addr, elemsize uintptr) uintptr {
- hbitsBase, _ := spanHeapBitsRange(spanBase, gc.PageSize, elemsize)
- hbits := (*byte)(unsafe.Pointer(hbitsBase))
-
+func extractHeapBitsSmall(hbits *byte, spanBase, addr, elemsize uintptr) uintptr {
// These objects are always small enough that their bitmaps
// fit in a single word, so just load the word or two we need.
//
--
cgit v1.3
From 1297fae7081a3116d2097ce7cfcc0f89ba2cf0fc Mon Sep 17 00:00:00 2001
From: Alan Donovan
Date: Wed, 12 Nov 2025 18:17:35 -0500
Subject: go/token: add (*File).End method
Also, use it in a number of places.
+ test, api, relnote
Fixes #75849
Change-Id: I44acf5b8190b964fd3975009aa407d7c82cee19b
Reviewed-on: https://go-review.googlesource.com/c/go/+/720061
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Robert Griesemer
---
api/next/75849.txt | 1 +
doc/next/6-stdlib/99-minor/go/token/75849.md | 1 +
src/go/ast/issues_test.go | 4 ++--
src/go/parser/interface.go | 2 +-
src/go/token/position.go | 5 +++++
src/go/token/position_test.go | 10 +++++++++-
src/go/token/tree.go | 4 ++--
src/go/types/resolver.go | 2 +-
8 files changed, 22 insertions(+), 7 deletions(-)
create mode 100644 api/next/75849.txt
create mode 100644 doc/next/6-stdlib/99-minor/go/token/75849.md
(limited to 'src')
diff --git a/api/next/75849.txt b/api/next/75849.txt
new file mode 100644
index 0000000000..615d6158f1
--- /dev/null
+++ b/api/next/75849.txt
@@ -0,0 +1 @@
+pkg go/token, method (*File) End() Pos #75849
diff --git a/doc/next/6-stdlib/99-minor/go/token/75849.md b/doc/next/6-stdlib/99-minor/go/token/75849.md
new file mode 100644
index 0000000000..4b8a79ff9f
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/go/token/75849.md
@@ -0,0 +1 @@
+The new [File.End] convenience method returns the file's end position.
diff --git a/src/go/ast/issues_test.go b/src/go/ast/issues_test.go
index 28d6a30fbb..f5e26af462 100644
--- a/src/go/ast/issues_test.go
+++ b/src/go/ast/issues_test.go
@@ -30,10 +30,10 @@ func TestIssue33649(t *testing.T) {
tf = f
return true
})
- tfEnd := tf.Base() + tf.Size()
+ tfEnd := tf.End()
fd := f.Decls[len(f.Decls)-1].(*ast.FuncDecl)
- fdEnd := int(fd.End())
+ fdEnd := fd.End()
if fdEnd != tfEnd {
t.Errorf("%q: got fdEnd = %d; want %d (base = %d, size = %d)", src, fdEnd, tfEnd, tf.Base(), tf.Size())
diff --git a/src/go/parser/interface.go b/src/go/parser/interface.go
index a9a1cfb736..ec9cb469ed 100644
--- a/src/go/parser/interface.go
+++ b/src/go/parser/interface.go
@@ -120,7 +120,7 @@ func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast
// Ensure the start/end are consistent,
// whether parsing succeeded or not.
f.FileStart = token.Pos(file.Base())
- f.FileEnd = token.Pos(file.Base() + file.Size())
+ f.FileEnd = file.End()
p.errors.Sort()
err = p.errors.Err()
diff --git a/src/go/token/position.go b/src/go/token/position.go
index 39756f257d..37017d4374 100644
--- a/src/go/token/position.go
+++ b/src/go/token/position.go
@@ -127,6 +127,11 @@ func (f *File) Size() int {
return f.size
}
+// End returns the end position of file f as registered with AddFile.
+func (f *File) End() Pos {
+ return Pos(f.base + f.size)
+}
+
// LineCount returns the number of lines in file f.
func (f *File) LineCount() int {
f.mutex.Lock()
diff --git a/src/go/token/position_test.go b/src/go/token/position_test.go
index c588a34d3d..3d02068ebf 100644
--- a/src/go/token/position_test.go
+++ b/src/go/token/position_test.go
@@ -572,7 +572,7 @@ func fsetString(fset *FileSet) string {
buf.WriteRune('{')
sep := ""
fset.Iterate(func(f *File) bool {
- fmt.Fprintf(&buf, "%s%s:%d-%d", sep, f.Name(), f.Base(), f.Base()+f.Size())
+ fmt.Fprintf(&buf, "%s%s:%d-%d", sep, f.Name(), f.Base(), f.End())
sep = " "
return true
})
@@ -643,3 +643,11 @@ func TestRemovedFileFileReturnsNil(t *testing.T) {
}
}
}
+
+func TestFile_End(t *testing.T) {
+ f := NewFileSet().AddFile("a.go", 100, 42)
+ got := fmt.Sprintf("%d, %d", f.Base(), f.End())
+ if want := "100, 142"; got != want {
+ t.Errorf("Base, End = %s, want %s", got, want)
+ }
+}
diff --git a/src/go/token/tree.go b/src/go/token/tree.go
index 7ba927c606..b2ca09f2c0 100644
--- a/src/go/token/tree.go
+++ b/src/go/token/tree.go
@@ -313,8 +313,8 @@ func (t *tree) add(file *File) {
}
if prev := (*pos).file; prev != file {
panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)",
- prev.Name(), prev.Base(), prev.Base()+prev.Size(),
- file.Name(), file.Base(), file.Base()+file.Size()))
+ prev.Name(), prev.Base(), prev.End(),
+ file.Name(), file.Base(), file.End()))
}
}
diff --git a/src/go/types/resolver.go b/src/go/types/resolver.go
index a8d11c2aa5..2017b9c881 100644
--- a/src/go/types/resolver.go
+++ b/src/go/types/resolver.go
@@ -247,7 +247,7 @@ func (check *Checker) collectObjects() {
// Be conservative and use the *ast.File extent if we don't have a *token.File.
pos, end := file.Pos(), file.End()
if f := check.fset.File(file.Pos()); f != nil {
- pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size())
+ pos, end = token.Pos(f.Base()), f.End()
}
fileScope := NewScope(pkg.scope, pos, end, check.filename(fileNo))
fileScopes[fileNo] = fileScope
--
cgit v1.3
From bc159638135e751a291fe6753fc8c8c3d61be863 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Fri, 14 Nov 2025 14:57:47 -0800
Subject: cmd/compile: clean up prove pass
flowLimit no longer needs to return whether it updated any information,
after CL 714920 which got rid of the iterate-until-done paradigm.
Change-Id: I0c5f592578ff27c27586c1f8b8a8d9071d94846d
Reviewed-on: https://go-review.googlesource.com/c/go/+/720720
Reviewed-by: Youlin Feng
Reviewed-by: Mark Freeman
Reviewed-by: Jorropo
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/prove.go | 148 ++++++++++++++++------------------
1 file changed, 71 insertions(+), 77 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 4919d6ad37..bbcab3efa5 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -466,57 +466,56 @@ func (ft *factsTable) initLimitForNewValue(v *Value) {
// signedMin records the fact that we know v is at least
// min in the signed domain.
-func (ft *factsTable) signedMin(v *Value, min int64) bool {
- return ft.newLimit(v, limit{min: min, max: math.MaxInt64, umin: 0, umax: math.MaxUint64})
+func (ft *factsTable) signedMin(v *Value, min int64) {
+ ft.newLimit(v, limit{min: min, max: math.MaxInt64, umin: 0, umax: math.MaxUint64})
}
// signedMax records the fact that we know v is at most
// max in the signed domain.
-func (ft *factsTable) signedMax(v *Value, max int64) bool {
- return ft.newLimit(v, limit{min: math.MinInt64, max: max, umin: 0, umax: math.MaxUint64})
+func (ft *factsTable) signedMax(v *Value, max int64) {
+ ft.newLimit(v, limit{min: math.MinInt64, max: max, umin: 0, umax: math.MaxUint64})
}
-func (ft *factsTable) signedMinMax(v *Value, min, max int64) bool {
- return ft.newLimit(v, limit{min: min, max: max, umin: 0, umax: math.MaxUint64})
+func (ft *factsTable) signedMinMax(v *Value, min, max int64) {
+ ft.newLimit(v, limit{min: min, max: max, umin: 0, umax: math.MaxUint64})
}
// setNonNegative records the fact that v is known to be non-negative.
-func (ft *factsTable) setNonNegative(v *Value) bool {
- return ft.signedMin(v, 0)
+func (ft *factsTable) setNonNegative(v *Value) {
+ ft.signedMin(v, 0)
}
// unsignedMin records the fact that we know v is at least
// min in the unsigned domain.
-func (ft *factsTable) unsignedMin(v *Value, min uint64) bool {
- return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: math.MaxUint64})
+func (ft *factsTable) unsignedMin(v *Value, min uint64) {
+ ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: math.MaxUint64})
}
// unsignedMax records the fact that we know v is at most
// max in the unsigned domain.
-func (ft *factsTable) unsignedMax(v *Value, max uint64) bool {
- return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: max})
+func (ft *factsTable) unsignedMax(v *Value, max uint64) {
+ ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: 0, umax: max})
}
-func (ft *factsTable) unsignedMinMax(v *Value, min, max uint64) bool {
- return ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: max})
+func (ft *factsTable) unsignedMinMax(v *Value, min, max uint64) {
+ ft.newLimit(v, limit{min: math.MinInt64, max: math.MaxInt64, umin: min, umax: max})
}
-func (ft *factsTable) booleanFalse(v *Value) bool {
- return ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
+func (ft *factsTable) booleanFalse(v *Value) {
+ ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
}
-func (ft *factsTable) booleanTrue(v *Value) bool {
- return ft.newLimit(v, limit{min: 1, max: 1, umin: 1, umax: 1})
+func (ft *factsTable) booleanTrue(v *Value) {
+ ft.newLimit(v, limit{min: 1, max: 1, umin: 1, umax: 1})
}
-func (ft *factsTable) pointerNil(v *Value) bool {
- return ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
+func (ft *factsTable) pointerNil(v *Value) {
+ ft.newLimit(v, limit{min: 0, max: 0, umin: 0, umax: 0})
}
-func (ft *factsTable) pointerNonNil(v *Value) bool {
+func (ft *factsTable) pointerNonNil(v *Value) {
l := noLimit
l.umin = 1
- return ft.newLimit(v, l)
+ ft.newLimit(v, l)
}
// newLimit adds new limiting information for v.
-// Returns true if the new limit added any new information.
-func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
+func (ft *factsTable) newLimit(v *Value, newLim limit) {
oldLim := ft.limits[v.ID]
// Merge old and new information.
@@ -531,13 +530,12 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
}
if lim == oldLim {
- return false // nothing new to record
+ return // nothing new to record
}
if lim.unsat() {
- r := !ft.unsat
ft.unsat = true
- return r
+ return
}
// Check for recursion. This normally happens because in unsatisfiable
@@ -548,7 +546,7 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
// the posets will not notice.
if ft.recurseCheck[v.ID] {
// This should only happen for unsatisfiable cases. TODO: check
- return false
+ return
}
ft.recurseCheck[v.ID] = true
defer func() {
@@ -713,8 +711,6 @@ func (ft *factsTable) newLimit(v *Value, newLim limit) bool {
}
}
}
-
- return true
}
func (ft *factsTable) addOrdering(v, w *Value, d domain, r relation) {
@@ -1825,7 +1821,7 @@ func initLimit(v *Value) limit {
return lim
}
-// flowLimit updates the known limits of v in ft. Returns true if anything changed.
+// flowLimit updates the known limits of v in ft.
// flowLimit can use the ranges of input arguments.
//
// Note: this calculation only happens at the point the value is defined. We do not reevaluate
@@ -1838,10 +1834,10 @@ func initLimit(v *Value) limit {
// block. We could recompute the range of v once we enter the block so
// we know that it is 0 <= v <= 8, but we don't have a mechanism to do
// that right now.
-func (ft *factsTable) flowLimit(v *Value) bool {
+func (ft *factsTable) flowLimit(v *Value) {
if !v.Type.IsInteger() {
// TODO: boolean?
- return false
+ return
}
// Additional limits based on opcode and argument.
@@ -1851,36 +1847,36 @@ func (ft *factsTable) flowLimit(v *Value) bool {
// extensions
case OpZeroExt8to64, OpZeroExt8to32, OpZeroExt8to16, OpZeroExt16to64, OpZeroExt16to32, OpZeroExt32to64:
a := ft.limits[v.Args[0].ID]
- return ft.unsignedMinMax(v, a.umin, a.umax)
+ ft.unsignedMinMax(v, a.umin, a.umax)
case OpSignExt8to64, OpSignExt8to32, OpSignExt8to16, OpSignExt16to64, OpSignExt16to32, OpSignExt32to64:
a := ft.limits[v.Args[0].ID]
- return ft.signedMinMax(v, a.min, a.max)
+ ft.signedMinMax(v, a.min, a.max)
case OpTrunc64to8, OpTrunc64to16, OpTrunc64to32, OpTrunc32to8, OpTrunc32to16, OpTrunc16to8:
a := ft.limits[v.Args[0].ID]
if a.umax <= 1<<(uint64(v.Type.Size())*8)-1 {
- return ft.unsignedMinMax(v, a.umin, a.umax)
+ ft.unsignedMinMax(v, a.umin, a.umax)
}
// math/bits
case OpCtz64:
a := ft.limits[v.Args[0].ID]
if a.nonzero() {
- return ft.unsignedMax(v, uint64(bits.Len64(a.umax)-1))
+ ft.unsignedMax(v, uint64(bits.Len64(a.umax)-1))
}
case OpCtz32:
a := ft.limits[v.Args[0].ID]
if a.nonzero() {
- return ft.unsignedMax(v, uint64(bits.Len32(uint32(a.umax))-1))
+ ft.unsignedMax(v, uint64(bits.Len32(uint32(a.umax))-1))
}
case OpCtz16:
a := ft.limits[v.Args[0].ID]
if a.nonzero() {
- return ft.unsignedMax(v, uint64(bits.Len16(uint16(a.umax))-1))
+ ft.unsignedMax(v, uint64(bits.Len16(uint16(a.umax))-1))
}
case OpCtz8:
a := ft.limits[v.Args[0].ID]
if a.nonzero() {
- return ft.unsignedMax(v, uint64(bits.Len8(uint8(a.umax))-1))
+ ft.unsignedMax(v, uint64(bits.Len8(uint8(a.umax))-1))
}
case OpPopCount64, OpPopCount32, OpPopCount16, OpPopCount8:
@@ -1889,26 +1885,26 @@ func (ft *factsTable) flowLimit(v *Value) bool {
sharedLeadingMask := ^(uint64(1)<>b.min, a.min>>b.max)
vmax := max(a.max>>b.min, a.max>>b.max)
- return ft.signedMinMax(v, vmin, vmax)
+ ft.signedMinMax(v, vmin, vmax)
}
case OpRsh64Ux64, OpRsh64Ux32, OpRsh64Ux16, OpRsh64Ux8,
OpRsh32Ux64, OpRsh32Ux32, OpRsh32Ux16, OpRsh32Ux8,
@@ -1988,7 +1983,7 @@ func (ft *factsTable) flowLimit(v *Value) bool {
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
if b.min >= 0 {
- return ft.unsignedMinMax(v, a.umin>>b.max, a.umax>>b.min)
+ ft.unsignedMinMax(v, a.umin>>b.max, a.umax>>b.min)
}
case OpDiv64, OpDiv32, OpDiv16, OpDiv8:
a := ft.limits[v.Args[0].ID]
@@ -2008,11 +2003,11 @@ func (ft *factsTable) flowLimit(v *Value) bool {
if b.umin > 0 {
lim = lim.unsignedMax(a.umax / b.umin)
}
- return ft.newLimit(v, lim)
+ ft.newLimit(v, lim)
case OpMod64, OpMod32, OpMod16, OpMod8:
- return ft.modLimit(true, v, v.Args[0], v.Args[1])
+ ft.modLimit(true, v, v.Args[0], v.Args[1])
case OpMod64u, OpMod32u, OpMod16u, OpMod8u:
- return ft.modLimit(false, v, v.Args[0], v.Args[1])
+ ft.modLimit(false, v, v.Args[0], v.Args[1])
case OpPhi:
// Compute the union of all the input phis.
@@ -2032,9 +2027,8 @@ func (ft *factsTable) flowLimit(v *Value) bool {
l.umin = min(l.umin, l2.umin)
l.umax = max(l.umax, l2.umax)
}
- return ft.newLimit(v, l)
+ ft.newLimit(v, l)
}
- return false
}
// detectSliceLenRelation matches the pattern where
@@ -2047,13 +2041,13 @@ func (ft *factsTable) flowLimit(v *Value) bool {
//
// Note that "index" is not useed for indexing in this pattern, but
// in the motivating example (chunked slice iteration) it is.
-func (ft *factsTable) detectSliceLenRelation(v *Value) (inferred bool) {
+func (ft *factsTable) detectSliceLenRelation(v *Value) {
if v.Op != OpSub64 {
- return false
+ return
}
if !(v.Args[0].Op == OpSliceLen || v.Args[0].Op == OpSliceCap) {
- return false
+ return
}
slice := v.Args[0].Args[0]
@@ -2093,13 +2087,12 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) (inferred bool) {
if K < 0 { // We hate thinking about overflow
continue
}
- inferred = inferred || ft.signedMin(v, K)
+ ft.signedMin(v, K)
}
- return inferred
}
// x%d has been rewritten to x - (x/d)*d.
-func (ft *factsTable) detectMod(v *Value) bool {
+func (ft *factsTable) detectMod(v *Value) {
var opDiv, opDivU, opMul, opConst Op
switch v.Op {
case OpSub64:
@@ -2126,36 +2119,37 @@ func (ft *factsTable) detectMod(v *Value) bool {
mul := v.Args[1]
if mul.Op != opMul {
- return false
+ return
}
div, con := mul.Args[0], mul.Args[1]
if div.Op == opConst {
div, con = con, div
}
if con.Op != opConst || (div.Op != opDiv && div.Op != opDivU) || div.Args[0] != v.Args[0] || div.Args[1].Op != opConst || div.Args[1].AuxInt != con.AuxInt {
- return false
+ return
}
- return ft.modLimit(div.Op == opDiv, v, v.Args[0], con)
+ ft.modLimit(div.Op == opDiv, v, v.Args[0], con)
}
// modLimit sets v with facts derived from v = p % q.
-func (ft *factsTable) modLimit(signed bool, v, p, q *Value) bool {
+func (ft *factsTable) modLimit(signed bool, v, p, q *Value) {
a := ft.limits[p.ID]
b := ft.limits[q.ID]
if signed {
if a.min < 0 && b.min > 0 {
- return ft.signedMinMax(v, -(b.max - 1), b.max-1)
+ ft.signedMinMax(v, -(b.max - 1), b.max-1)
+ return
}
if !(a.nonnegative() && b.nonnegative()) {
// TODO: we could handle signed limits but I didn't bother.
- return false
+ return
}
if a.min >= 0 && b.min > 0 {
ft.setNonNegative(v)
}
}
// Underflow in the arithmetic below is ok, it gives to MaxUint64 which does nothing to the limit.
- return ft.unsignedMax(v, min(a.umax, b.umax-1))
+ ft.unsignedMax(v, min(a.umax, b.umax-1))
}
// getBranch returns the range restrictions added by p
--
cgit v1.3
From c12c33709923907348837e8131122ec4c45d2c83 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Fri, 14 Nov 2025 15:26:36 -0800
Subject: cmd/compile: teach prove about subtract idioms
For v = x-y:
if y >= 0 then v <= x
if y <= x then v >= 0
(With appropriate guards against overflow/underflow.)
Fixes #76304
Change-Id: I8f8f1254156c347fa97802bd057a8379676720ae
Reviewed-on: https://go-review.googlesource.com/c/go/+/720740
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Jorropo
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/prove.go | 43 +++++++++++++++++++++++++++++++++++
test/prove.go | 18 ++++++++++++---
2 files changed, 58 insertions(+), 3 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index bbcab3efa5..4b2cedc8be 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -1945,6 +1945,7 @@ func (ft *factsTable) flowLimit(v *Value) {
ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8))
ft.detectMod(v)
ft.detectSliceLenRelation(v)
+ ft.detectSubRelations(v)
case OpNeg64, OpNeg32, OpNeg16, OpNeg8:
a := ft.limits[v.Args[0].ID]
bitsize := uint(v.Type.Size()) * 8
@@ -2091,6 +2092,48 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) {
}
}
+// v must be Sub{64,32,16,8}.
+func (ft *factsTable) detectSubRelations(v *Value) {
+ // v = x-y
+ x := v.Args[0]
+ y := v.Args[1]
+ if x == y {
+ ft.signedMinMax(v, 0, 0)
+ return
+ }
+ xLim := ft.limits[x.ID]
+ yLim := ft.limits[y.ID]
+
+ // Check if we might wrap around. If so, give up.
+ width := uint(v.Type.Size()) * 8
+ if _, ok := safeSub(xLim.min, yLim.max, width); !ok {
+ return // x-y might underflow
+ }
+ if _, ok := safeSub(xLim.max, yLim.min, width); !ok {
+ return // x-y might overflow
+ }
+
+ // Subtracting a positive number only makes
+ // things smaller.
+ if yLim.min >= 0 {
+ ft.update(v.Block, v, x, signed, lt|eq)
+ // TODO: is this worth it?
+ //if yLim.min > 0 {
+ // ft.update(v.Block, v, x, signed, lt)
+ //}
+ }
+
+ // Subtracting a number from a bigger one
+ // can't go below 0.
+ if ft.orderS.OrderedOrEqual(y, x) {
+ ft.setNonNegative(v)
+ // TODO: is this worth it?
+ //if ft.orderS.Ordered(y, x) {
+ // ft.signedMin(v, 1)
+ //}
+ }
+}
+
// x%d has been rewritten to x - (x/d)*d.
func (ft *factsTable) detectMod(v *Value) {
var opDiv, opDivU, opMul, opConst Op
diff --git a/test/prove.go b/test/prove.go
index 365e8ba006..3f8990615e 100644
--- a/test/prove.go
+++ b/test/prove.go
@@ -679,12 +679,12 @@ func natcmp(x, y []uint) (r int) {
}
func suffix(s, suffix string) bool {
- // todo, we're still not able to drop the bound check here in the general case
- return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
+ // Note: issue 76304
+ return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix // ERROR "Proved IsSliceInBounds"
}
func constsuffix(s string) bool {
- return suffix(s, "abc") // ERROR "Proved IsSliceInBounds$"
+ return suffix(s, "abc") // ERROR "Proved IsSliceInBounds$" "Proved slicemask not needed$" "Proved Eq64$"
}
func atexit(foobar []func()) {
@@ -2639,6 +2639,18 @@ func unsignedRightShiftBounds(v uint, s int) {
}
}
+func subLengths1(b []byte, i int) {
+ if i >= 0 && i <= len(b) {
+ _ = b[len(b)-i:] // ERROR "Proved IsSliceInBounds"
+ }
+}
+
+func subLengths2(b []byte, i int) {
+ if i >= 0 && i <= len(b) {
+ _ = b[:len(b)-i] // ERROR "Proved IsSliceInBounds"
+ }
+}
+
//go:noinline
func prove(x int) {
}
--
cgit v1.3
From 590cf18dafcad9f39a3bf2ecf9f1b7578471eff8 Mon Sep 17 00:00:00 2001
From: Filippo Valsorda
Date: Fri, 5 Sep 2025 19:37:45 +0200
Subject: crypto/mlkem/mlkemtest: add derandomized Encapsulate768/1024
Fixes #73627
Change-Id: I6a6a69649927e9b1cdff910832084fdc04ff5bc2
Reviewed-on: https://go-review.googlesource.com/c/go/+/703795
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Filippo Valsorda
Reviewed-by: Mark Freeman
Reviewed-by: Junyang Shao
Reviewed-by: Daniel McCarney
---
api/next/73627.txt | 2 +
.../99-minor/crypto/mlkem/mlkemtest/73627.md | 3 ++
src/crypto/mlkem/mlkem.go | 6 +++
src/crypto/mlkem/mlkem_test.go | 17 ++++----
src/crypto/mlkem/mlkemtest/mlkemtest.go | 46 ++++++++++++++++++++++
src/go/build/deps_test.go | 3 ++
6 files changed, 70 insertions(+), 7 deletions(-)
create mode 100644 api/next/73627.txt
create mode 100644 doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md
create mode 100644 src/crypto/mlkem/mlkemtest/mlkemtest.go
(limited to 'src')
diff --git a/api/next/73627.txt b/api/next/73627.txt
new file mode 100644
index 0000000000..b13d705a61
--- /dev/null
+++ b/api/next/73627.txt
@@ -0,0 +1,2 @@
+pkg crypto/mlkem/mlkemtest, func Encapsulate1024(*mlkem.EncapsulationKey1024, []uint8) ([]uint8, []uint8, error) #73627
+pkg crypto/mlkem/mlkemtest, func Encapsulate768(*mlkem.EncapsulationKey768, []uint8) ([]uint8, []uint8, error) #73627
diff --git a/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md b/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md
new file mode 100644
index 0000000000..5a475c4ff6
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/mlkem/mlkemtest/73627.md
@@ -0,0 +1,3 @@
+The new [crypto/mlkem/mlkemtest] package exposes the [Encapsulate768] and
+[Encapsulate1024] functions which implement derandomized ML-KEM encapsulation,
+for use with known-answer tests.
diff --git a/src/crypto/mlkem/mlkem.go b/src/crypto/mlkem/mlkem.go
index 69c0bc571f..cb44bede20 100644
--- a/src/crypto/mlkem/mlkem.go
+++ b/src/crypto/mlkem/mlkem.go
@@ -108,6 +108,9 @@ func (ek *EncapsulationKey768) Bytes() []byte {
// encapsulation key, drawing random bytes from the default crypto/rand source.
//
// The shared key must be kept secret.
+//
+// For testing, derandomized encapsulation is provided by the
+// [crypto/mlkem/mlkemtest] package.
func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) {
return ek.key.Encapsulate()
}
@@ -187,6 +190,9 @@ func (ek *EncapsulationKey1024) Bytes() []byte {
// encapsulation key, drawing random bytes from the default crypto/rand source.
//
// The shared key must be kept secret.
+//
+// For testing, derandomized encapsulation is provided by the
+// [crypto/mlkem/mlkemtest] package.
func (ek *EncapsulationKey1024) Encapsulate() (sharedKey, ciphertext []byte) {
return ek.key.Encapsulate()
}
diff --git a/src/crypto/mlkem/mlkem_test.go b/src/crypto/mlkem/mlkem_test.go
index 207d6d48c3..922147ab15 100644
--- a/src/crypto/mlkem/mlkem_test.go
+++ b/src/crypto/mlkem/mlkem_test.go
@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package mlkem
+package mlkem_test
import (
"bytes"
"crypto/internal/fips140/mlkem"
"crypto/internal/fips140/sha3"
+ . "crypto/mlkem"
+ "crypto/mlkem/mlkemtest"
"crypto/rand"
"encoding/hex"
"flag"
@@ -176,7 +178,7 @@ func TestAccumulated(t *testing.T) {
s := sha3.NewShake128()
o := sha3.NewShake128()
seed := make([]byte, SeedSize)
- var msg [32]byte
+ msg := make([]byte, 32)
ct1 := make([]byte, CiphertextSize768)
for i := 0; i < n; i++ {
@@ -188,8 +190,11 @@ func TestAccumulated(t *testing.T) {
ek := dk.EncapsulationKey()
o.Write(ek.Bytes())
- s.Read(msg[:])
- k, ct := ek.key.EncapsulateInternal(&msg)
+ s.Read(msg)
+ k, ct, err := mlkemtest.Encapsulate768(ek, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
o.Write(ct)
o.Write(k)
@@ -231,8 +236,6 @@ func BenchmarkKeyGen(b *testing.B) {
func BenchmarkEncaps(b *testing.B) {
seed := make([]byte, SeedSize)
rand.Read(seed)
- var m [32]byte
- rand.Read(m[:])
dk, err := NewDecapsulationKey768(seed)
if err != nil {
b.Fatal(err)
@@ -244,7 +247,7 @@ func BenchmarkEncaps(b *testing.B) {
if err != nil {
b.Fatal(err)
}
- K, c := ek.key.EncapsulateInternal(&m)
+ K, c := ek.Encapsulate()
sink ^= c[0] ^ K[0]
}
}
diff --git a/src/crypto/mlkem/mlkemtest/mlkemtest.go b/src/crypto/mlkem/mlkemtest/mlkemtest.go
new file mode 100644
index 0000000000..39e3994ea9
--- /dev/null
+++ b/src/crypto/mlkem/mlkemtest/mlkemtest.go
@@ -0,0 +1,46 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package mlkemtest provides testing functions for the ML-KEM algorithm.
+package mlkemtest
+
+import (
+ fips140mlkem "crypto/internal/fips140/mlkem"
+ "crypto/mlkem"
+ "errors"
+)
+
+// Encapsulate768 implements derandomized ML-KEM-768 encapsulation
+// (ML-KEM.Encaps_internal from FIPS 203) using the provided encapsulation key
+// ek and 32 bytes of randomness.
+//
+// It must only be used for known-answer tests.
+func Encapsulate768(ek *mlkem.EncapsulationKey768, random []byte) (sharedKey, ciphertext []byte, err error) {
+ if len(random) != 32 {
+ return nil, nil, errors.New("mlkemtest: Encapsulate768: random must be 32 bytes")
+ }
+ k, err := fips140mlkem.NewEncapsulationKey768(ek.Bytes())
+ if err != nil {
+ return nil, nil, errors.New("mlkemtest: Encapsulate768: failed to reconstruct key: " + err.Error())
+ }
+ sharedKey, ciphertext = k.EncapsulateInternal((*[32]byte)(random))
+ return sharedKey, ciphertext, nil
+}
+
+// Encapsulate1024 implements derandomized ML-KEM-1024 encapsulation
+// (ML-KEM.Encaps_internal from FIPS 203) using the provided encapsulation key
+// ek and 32 bytes of randomness.
+//
+// It must only be used for known-answer tests.
+func Encapsulate1024(ek *mlkem.EncapsulationKey1024, random []byte) (sharedKey, ciphertext []byte, err error) {
+ if len(random) != 32 {
+ return nil, nil, errors.New("mlkemtest: Encapsulate1024: random must be 32 bytes")
+ }
+ k, err := fips140mlkem.NewEncapsulationKey1024(ek.Bytes())
+ if err != nil {
+ return nil, nil, errors.New("mlkemtest: Encapsulate1024: failed to reconstruct key: " + err.Error())
+ }
+ sharedKey, ciphertext = k.EncapsulateInternal((*[32]byte)(random))
+ return sharedKey, ciphertext, nil
+}
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 48a9f3e75b..868be194c3 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -730,6 +730,9 @@ var depsRules = `
testing
< internal/testhash;
+ CRYPTO-MATH
+ < crypto/mlkem/mlkemtest;
+
CRYPTO-MATH, testing, internal/testenv, internal/testhash, encoding/json
< crypto/internal/cryptotest;
--
cgit v1.3
From 592775ec7d8bbc99ee0a1ada56c4490c855f9385 Mon Sep 17 00:00:00 2001
From: Filippo Valsorda
Date: Mon, 27 Oct 2025 18:58:52 +0100
Subject: crypto/mlkem: avoid a few unnecessary inverse NTT calls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
We were mistakenly doing NTT⁻¹ inside the inner loop, on the components
of the inner product intead of the sum, leading to k² = 9 inverse NTT
calls instead of k = 3 inverse NTT.
Surprisingly large speedup as a result.
fips140: off
goos: darwin
goarch: arm64
pkg: crypto/mlkem
cpu: Apple M2
│ 4c285e0988 │ 4c285e0988-dirty │
│ sec/op │ sec/op vs base │
KeyGen-2 28.95µ ± 3% 28.64µ ± 4% ~ (p=0.699 n=6)
Encaps-2 43.13µ ± 3% 35.02µ ± 1% -18.81% (p=0.002 n=6)
Decaps-2 43.80µ ± 1% 35.49µ ± 1% -18.97% (p=0.002 n=6)
RoundTrip/Alice-2 77.27µ ± 7% 69.12µ ± 3% -10.55% (p=0.002 n=6)
RoundTrip/Bob-2 43.08µ ± 2% 35.14µ ± 3% -18.44% (p=0.002 n=6)
geomean 44.88µ 38.67µ -13.84%
Change-Id: I6a6a69649c1378411c9aca75d473fd5b9984a609
Reviewed-on: https://go-review.googlesource.com/c/go/+/715381
Reviewed-by: Junyang Shao
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Filippo Valsorda
Reviewed-by: Daniel McCarney
---
src/crypto/internal/fips140/mlkem/mlkem1024.go | 5 +++--
src/crypto/internal/fips140/mlkem/mlkem768.go | 5 +++--
2 files changed, 6 insertions(+), 4 deletions(-)
(limited to 'src')
diff --git a/src/crypto/internal/fips140/mlkem/mlkem1024.go b/src/crypto/internal/fips140/mlkem/mlkem1024.go
index edde161422..953eea9bc2 100644
--- a/src/crypto/internal/fips140/mlkem/mlkem1024.go
+++ b/src/crypto/internal/fips140/mlkem/mlkem1024.go
@@ -369,11 +369,12 @@ func pkeEncrypt1024(cc *[CiphertextSize1024]byte, ex *encryptionKey1024, m *[mes
u := make([]ringElement, k1024) // NTT⁻¹(AT ◦ r) + e1
for i := range u {
- u[i] = e1[i]
+ var uHat nttElement
for j := range r {
// Note that i and j are inverted, as we need the transposed of A.
- u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.a[j*k1024+i], r[j])))
+ uHat = polyAdd(uHat, nttMul(ex.a[j*k1024+i], r[j]))
}
+ u[i] = polyAdd(e1[i], inverseNTT(uHat))
}
μ := ringDecodeAndDecompress1(m)
diff --git a/src/crypto/internal/fips140/mlkem/mlkem768.go b/src/crypto/internal/fips140/mlkem/mlkem768.go
index 088c2954de..c4c3a9deaf 100644
--- a/src/crypto/internal/fips140/mlkem/mlkem768.go
+++ b/src/crypto/internal/fips140/mlkem/mlkem768.go
@@ -428,11 +428,12 @@ func pkeEncrypt(cc *[CiphertextSize768]byte, ex *encryptionKey, m *[messageSize]
u := make([]ringElement, k) // NTT⁻¹(AT ◦ r) + e1
for i := range u {
- u[i] = e1[i]
+ var uHat nttElement
for j := range r {
// Note that i and j are inverted, as we need the transposed of A.
- u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.a[j*k+i], r[j])))
+ uHat = polyAdd(uHat, nttMul(ex.a[j*k+i], r[j]))
}
+ u[i] = polyAdd(e1[i], inverseNTT(uHat))
}
μ := ringDecodeAndDecompress1(m)
--
cgit v1.3
From 8e734ec954ed25e4c41e7d5a6f59ed1c1072ea83 Mon Sep 17 00:00:00 2001
From: Alan Donovan
Date: Wed, 12 Nov 2025 17:13:40 -0500
Subject: go/ast: fix BasicLit.End position for raw strings containing \r
This CL causes the parser to record in a new field, BasicLit.EndPos,
the actual end position of each literal token, and to use it in
BasicLit.End. Previously, the End was computed heuristically as
Pos + len(Value). This heuristic is incorrect for a multiline
raw string literal on Windows, since the scanner normalizes
\r\n to \n.
Unfortunately the actual end position is not returned by the
Scanner.Scan method, so the scanner and parser conspire
using a global variable in the go/internal/scannerhook
package to communicate.
+ test, api change, relnote
Fixes #76031
Change-Id: I57c18a44e85f7403d470ba23d41dcdcc5a9432c2
Reviewed-on: https://go-review.googlesource.com/c/go/+/720060
Reviewed-by: Robert Griesemer
LUCI-TryBot-Result: Go LUCI
---
api/next/76031.txt | 1 +
doc/next/6-stdlib/99-minor/go/ast/76031.md | 5 +++
src/go/ast/ast.go | 15 ++++++---
src/go/ast/commentmap_test.go | 2 +-
src/go/ast/example_test.go | 51 +++++++++++++++---------------
src/go/build/deps_test.go | 1 +
src/go/internal/scannerhooks/hooks.go | 11 +++++++
src/go/parser/parser.go | 24 ++++++++++----
src/go/parser/parser_test.go | 50 +++++++++++++++++++++++++++++
src/go/scanner/scanner.go | 19 +++++++++--
10 files changed, 140 insertions(+), 39 deletions(-)
create mode 100644 api/next/76031.txt
create mode 100644 doc/next/6-stdlib/99-minor/go/ast/76031.md
create mode 100644 src/go/internal/scannerhooks/hooks.go
(limited to 'src')
diff --git a/api/next/76031.txt b/api/next/76031.txt
new file mode 100644
index 0000000000..049edc7a56
--- /dev/null
+++ b/api/next/76031.txt
@@ -0,0 +1 @@
+pkg go/ast, type BasicLit struct, ValueEnd token.Pos #76031
diff --git a/doc/next/6-stdlib/99-minor/go/ast/76031.md b/doc/next/6-stdlib/99-minor/go/ast/76031.md
new file mode 100644
index 0000000000..964872f416
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/go/ast/76031.md
@@ -0,0 +1,5 @@
+The new [BasicLit.ValueEnd] field records the precise end position of
+a literal so that the [BasicLit.End] method can now always return the
+correct answer. (Previously it was computed using a heuristic that was
+incorrect for multi-line raw string literals in Windows source files,
+due to removal of carriage returns.)
diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go
index a6dab5bb51..37fc3c9666 100644
--- a/src/go/ast/ast.go
+++ b/src/go/ast/ast.go
@@ -312,11 +312,10 @@ type (
//
// For raw string literals (Kind == token.STRING && Value[0] == '`'),
// the Value field contains the string text without carriage returns (\r) that
- // may have been present in the source. Because the end position is
- // computed using len(Value), the position reported by [BasicLit.End] does not match the
- // true source end position for raw string literals containing carriage returns.
+ // may have been present in the source.
BasicLit struct {
ValuePos token.Pos // literal position
+ ValueEnd token.Pos // position immediately after the literal
Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING
Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
}
@@ -535,7 +534,15 @@ func (x *Ellipsis) End() token.Pos {
}
return x.Ellipsis + 3 // len("...")
}
-func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) }
+func (x *BasicLit) End() token.Pos {
+ if !x.ValueEnd.IsValid() {
+ // Not from parser; use a heuristic.
+ // (Incorrect for `...` containing \r\n;
+ // see https://go.dev/issue/76031.)
+ return token.Pos(int(x.ValuePos) + len(x.Value))
+ }
+ return x.ValueEnd
+}
func (x *FuncLit) End() token.Pos { return x.Body.End() }
func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 }
func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 }
diff --git a/src/go/ast/commentmap_test.go b/src/go/ast/commentmap_test.go
index f0faeed610..0d5e8de013 100644
--- a/src/go/ast/commentmap_test.go
+++ b/src/go/ast/commentmap_test.go
@@ -109,7 +109,7 @@ func TestCommentMap(t *testing.T) {
}
cmap := NewCommentMap(fset, f, f.Comments)
- // very correct association of comments
+ // verify correct association of comments
for n, list := range cmap {
key := fmt.Sprintf("%2d: %T", fset.Position(n.Pos()).Line, n)
got := ctext(list)
diff --git a/src/go/ast/example_test.go b/src/go/ast/example_test.go
index 31b32efece..36daa7e7e1 100644
--- a/src/go/ast/example_test.go
+++ b/src/go/ast/example_test.go
@@ -113,31 +113,32 @@ func main() {
// 34 . . . . . . . Args: []ast.Expr (len = 1) {
// 35 . . . . . . . . 0: *ast.BasicLit {
// 36 . . . . . . . . . ValuePos: 4:10
- // 37 . . . . . . . . . Kind: STRING
- // 38 . . . . . . . . . Value: "\"Hello, World!\""
- // 39 . . . . . . . . }
- // 40 . . . . . . . }
- // 41 . . . . . . . Ellipsis: -
- // 42 . . . . . . . Rparen: 4:25
- // 43 . . . . . . }
- // 44 . . . . . }
- // 45 . . . . }
- // 46 . . . . Rbrace: 5:1
- // 47 . . . }
- // 48 . . }
- // 49 . }
- // 50 . FileStart: 1:1
- // 51 . FileEnd: 5:3
- // 52 . Scope: *ast.Scope {
- // 53 . . Objects: map[string]*ast.Object (len = 1) {
- // 54 . . . "main": *(obj @ 11)
- // 55 . . }
- // 56 . }
- // 57 . Unresolved: []*ast.Ident (len = 1) {
- // 58 . . 0: *(obj @ 29)
- // 59 . }
- // 60 . GoVersion: ""
- // 61 }
+ // 37 . . . . . . . . . ValueEnd: 4:25
+ // 38 . . . . . . . . . Kind: STRING
+ // 39 . . . . . . . . . Value: "\"Hello, World!\""
+ // 40 . . . . . . . . }
+ // 41 . . . . . . . }
+ // 42 . . . . . . . Ellipsis: -
+ // 43 . . . . . . . Rparen: 4:25
+ // 44 . . . . . . }
+ // 45 . . . . . }
+ // 46 . . . . }
+ // 47 . . . . Rbrace: 5:1
+ // 48 . . . }
+ // 49 . . }
+ // 50 . }
+ // 51 . FileStart: 1:1
+ // 52 . FileEnd: 5:3
+ // 53 . Scope: *ast.Scope {
+ // 54 . . Objects: map[string]*ast.Object (len = 1) {
+ // 55 . . . "main": *(obj @ 11)
+ // 56 . . }
+ // 57 . }
+ // 58 . Unresolved: []*ast.Ident (len = 1) {
+ // 59 . . 0: *(obj @ 29)
+ // 60 . }
+ // 61 . GoVersion: ""
+ // 62 }
}
func ExamplePreorder() {
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 868be194c3..5f95535ed9 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -335,6 +335,7 @@ var depsRules = `
< internal/gover
< go/version
< go/token
+ < go/internal/scannerhooks
< go/scanner
< go/ast
< go/internal/typeparams;
diff --git a/src/go/internal/scannerhooks/hooks.go b/src/go/internal/scannerhooks/hooks.go
new file mode 100644
index 0000000000..057261df06
--- /dev/null
+++ b/src/go/internal/scannerhooks/hooks.go
@@ -0,0 +1,11 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package scannerhooks defines nonexported channels between parser and scanner.
+// Ideally this package could be eliminated by adding API to scanner.
+package scannerhooks
+
+import "go/token"
+
+var StringEnd func(scanner any) token.Pos
diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go
index e725371e76..e01a221968 100644
--- a/src/go/parser/parser.go
+++ b/src/go/parser/parser.go
@@ -28,6 +28,7 @@ import (
"fmt"
"go/ast"
"go/build/constraint"
+ "go/internal/scannerhooks"
"go/scanner"
"go/token"
"strings"
@@ -52,9 +53,10 @@ type parser struct {
goVersion string // minimum Go version found in //go:build comment
// Next token
- pos token.Pos // token position
- tok token.Token // one token look-ahead
- lit string // token literal
+ pos token.Pos // token position
+ tok token.Token // one token look-ahead
+ lit string // token literal
+ stringEnd token.Pos // position immediately after token; STRING only
// Error recovery
// (used to limit the number of calls to parser.advance
@@ -163,6 +165,10 @@ func (p *parser) next0() {
continue
}
} else {
+ if p.tok == token.STRING {
+ p.stringEnd = scannerhooks.StringEnd(&p.scanner)
+ }
+
// Found a non-comment; top of file is over.
p.top = false
}
@@ -720,7 +726,7 @@ func (p *parser) parseFieldDecl() *ast.Field {
var tag *ast.BasicLit
if p.tok == token.STRING {
- tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
+ tag = &ast.BasicLit{ValuePos: p.pos, ValueEnd: p.stringEnd, Kind: p.tok, Value: p.lit}
p.next()
}
@@ -1474,7 +1480,11 @@ func (p *parser) parseOperand() ast.Expr {
return x
case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING:
- x := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
+ end := p.pos + token.Pos(len(p.lit))
+ if p.tok == token.STRING {
+ end = p.stringEnd
+ }
+ x := &ast.BasicLit{ValuePos: p.pos, ValueEnd: end, Kind: p.tok, Value: p.lit}
p.next()
return x
@@ -2511,9 +2521,11 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as
}
pos := p.pos
+ end := p.pos
var path string
if p.tok == token.STRING {
path = p.lit
+ end = p.stringEnd
p.next()
} else if p.tok.IsLiteral() {
p.error(pos, "import path must be a string")
@@ -2528,7 +2540,7 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as
spec := &ast.ImportSpec{
Doc: doc,
Name: ident,
- Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path},
+ Path: &ast.BasicLit{ValuePos: pos, ValueEnd: end, Kind: token.STRING, Value: path},
Comment: comment,
}
p.imports = append(p.imports, spec)
diff --git a/src/go/parser/parser_test.go b/src/go/parser/parser_test.go
index 87b7d7bbab..8118189230 100644
--- a/src/go/parser/parser_test.go
+++ b/src/go/parser/parser_test.go
@@ -946,3 +946,53 @@ func _() {}
t.Errorf("unexpected doc comment %v", docComment2)
}
}
+
+// Tests of BasicLit.End() method, which in go1.26 started precisely
+// recording the Value token's end position instead of heuristically
+// computing it, which is inaccurate for strings containing "\r".
+func TestBasicLit_End(t *testing.T) {
+ // lit is a raw string literal containing [a b c \r \n],
+ // denoting "abc\n", because the scanner normalizes \r\n to \n.
+ const stringlit = "`abc\r\n`"
+
+ // The semicolons exercise the case in which the next token
+ // (a SEMICOLON implied by a \n) isn't immediate but follows
+ // some horizontal space.
+ const src = `package p
+
+import ` + stringlit + ` ;
+
+type _ struct{ x int ` + stringlit + ` }
+
+const _ = ` + stringlit + ` ;
+`
+
+ fset := token.NewFileSet()
+ f, _ := ParseFile(fset, "", src, ParseComments|SkipObjectResolution)
+ tokFile := fset.File(f.Pos())
+
+ count := 0
+ ast.Inspect(f, func(n ast.Node) bool {
+ if lit, ok := n.(*ast.BasicLit); ok {
+ count++
+ var (
+ start = tokFile.Offset(lit.Pos())
+ end = tokFile.Offset(lit.End())
+ )
+
+ // Check BasicLit.Value.
+ if want := "`abc\n`"; lit.Value != want {
+ t.Errorf("%s: BasicLit.Value = %q, want %q", fset.Position(lit.Pos()), lit.Value, want)
+ }
+
+ // Check source extent.
+ if got := src[start:end]; got != stringlit {
+ t.Errorf("%s: src[BasicLit.Pos:End] = %q, want %q", fset.Position(lit.Pos()), got, stringlit)
+ }
+ }
+ return true
+ })
+ if count != 3 {
+ t.Errorf("found %d BasicLit, want 3", count)
+ }
+}
diff --git a/src/go/scanner/scanner.go b/src/go/scanner/scanner.go
index cdbeb6323c..07d987c88f 100644
--- a/src/go/scanner/scanner.go
+++ b/src/go/scanner/scanner.go
@@ -10,6 +10,7 @@ package scanner
import (
"bytes"
"fmt"
+ "go/internal/scannerhooks"
"go/token"
"path/filepath"
"strconv"
@@ -41,11 +42,19 @@ type Scanner struct {
lineOffset int // current line offset
insertSemi bool // insert a semicolon before next newline
nlPos token.Pos // position of newline in preceding comment
+ stringEnd token.Pos // end position; defined only for STRING tokens
// public state - ok to modify
ErrorCount int // number of errors encountered
}
+// Provide go/parser with backdoor access to the StringEnd information.
+func init() {
+ scannerhooks.StringEnd = func(scanner any) token.Pos {
+ return scanner.(*Scanner).stringEnd
+ }
+}
+
const (
bom = 0xFEFF // byte order mark, only permitted as very first character
eof = -1 // end of file
@@ -691,7 +700,7 @@ func stripCR(b []byte, comment bool) []byte {
return c[:i]
}
-func (s *Scanner) scanRawString() string {
+func (s *Scanner) scanRawString() (string, int) {
// '`' opening already consumed
offs := s.offset - 1
@@ -712,11 +721,12 @@ func (s *Scanner) scanRawString() string {
}
lit := s.src[offs:s.offset]
+ rawLen := len(lit)
if hasCR {
lit = stripCR(lit, false)
}
- return string(lit)
+ return string(lit), rawLen
}
func (s *Scanner) skipWhitespace() {
@@ -850,6 +860,7 @@ scanAgain:
insertSemi = true
tok = token.STRING
lit = s.scanString()
+ s.stringEnd = pos + token.Pos(len(lit))
case '\'':
insertSemi = true
tok = token.CHAR
@@ -857,7 +868,9 @@ scanAgain:
case '`':
insertSemi = true
tok = token.STRING
- lit = s.scanRawString()
+ var rawLen int
+ lit, rawLen = s.scanRawString()
+ s.stringEnd = pos + token.Pos(rawLen)
case ':':
tok = s.switch2(token.COLON, token.DEFINE)
case '.':
--
cgit v1.3
From 6919858338ae3c4f244f65ca87e9e71662a83413 Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Mon, 17 Nov 2025 13:34:51 -0500
Subject: runtime: rename findrunnable references to findRunnable
These cases were missed by CL 393880.
Change-Id: I6a6a636cf0d97a4efcf4b9df766002ecef48b4de
Reviewed-on: https://go-review.googlesource.com/c/go/+/721120
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Pratt
Reviewed-by: Michael Knyszek
---
src/runtime/proc.go | 18 +++++++++---------
src/runtime/runtime2.go | 4 ++--
2 files changed, 11 insertions(+), 11 deletions(-)
(limited to 'src')
diff --git a/src/runtime/proc.go b/src/runtime/proc.go
index 61364d91ff..a378b8c39d 100644
--- a/src/runtime/proc.go
+++ b/src/runtime/proc.go
@@ -3119,7 +3119,7 @@ func startm(pp *p, spinning, lockheld bool) {
//go:nowritebarrierrec
func handoffp(pp *p) {
// handoffp must start an M in any situation where
- // findrunnable would return a G to run on pp.
+ // findRunnable would return a G to run on pp.
// if it has local work, start it straight away
if !runqempty(pp) || !sched.runq.empty() {
@@ -3362,7 +3362,7 @@ func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
mp := getg().m
// The conditions here and in handoffp must agree: if
- // findrunnable would return a G to run, handoffp must start
+ // findRunnable would return a G to run, handoffp must start
// an M.
top:
@@ -3586,7 +3586,7 @@ top:
goto top
}
if releasep() != pp {
- throw("findrunnable: wrong p")
+ throw("findRunnable: wrong p")
}
now = pidleput(pp, now)
unlock(&sched.lock)
@@ -3631,7 +3631,7 @@ top:
if mp.spinning {
mp.spinning = false
if sched.nmspinning.Add(-1) < 0 {
- throw("findrunnable: negative nmspinning")
+ throw("findRunnable: negative nmspinning")
}
// Note the for correctness, only the last M transitioning from
@@ -3704,10 +3704,10 @@ top:
if netpollinited() && (netpollAnyWaiters() || pollUntil != 0) && sched.lastpoll.Swap(0) != 0 {
sched.pollUntil.Store(pollUntil)
if mp.p != 0 {
- throw("findrunnable: netpoll with p")
+ throw("findRunnable: netpoll with p")
}
if mp.spinning {
- throw("findrunnable: netpoll with spinning")
+ throw("findRunnable: netpoll with spinning")
}
delay := int64(-1)
if pollUntil != 0 {
@@ -3973,7 +3973,7 @@ func checkIdleGCNoP() (*p, *g) {
// timers and the network poller if there isn't one already.
func wakeNetPoller(when int64) {
if sched.lastpoll.Load() == 0 {
- // In findrunnable we ensure that when polling the pollUntil
+ // In findRunnable we ensure that when polling the pollUntil
// field is either zero or the time to which the current
// poll is expected to run. This can have a spurious wakeup
// but should never miss a wakeup.
@@ -3998,7 +3998,7 @@ func resetspinning() {
gp.m.spinning = false
nmspinning := sched.nmspinning.Add(-1)
if nmspinning < 0 {
- throw("findrunnable: negative nmspinning")
+ throw("findRunnable: negative nmspinning")
}
// M wakeup policy is deliberately somewhat conservative, so check if we
// need to wakeup another P here. See "Worker thread parking/unparking"
@@ -7269,7 +7269,7 @@ func pidlegetSpinning(now int64) (*p, int64) {
pp, now := pidleget(now)
if pp == nil {
- // See "Delicate dance" comment in findrunnable. We found work
+ // See "Delicate dance" comment in findRunnable. We found work
// that we cannot take, we must synchronize with non-spinning
// Ms that may be preparing to drop their P.
sched.needspinning.Store(1)
diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go
index 6c955460d4..e415df6ec1 100644
--- a/src/runtime/runtime2.go
+++ b/src/runtime/runtime2.go
@@ -1425,9 +1425,9 @@ var (
// must be set. An idle P (passed to pidleput) cannot add new timers while
// idle, so if it has no timers at that time, its mask may be cleared.
//
- // Thus, we get the following effects on timer-stealing in findrunnable:
+ // Thus, we get the following effects on timer-stealing in findRunnable:
//
- // - Idle Ps with no timers when they go idle are never checked in findrunnable
+ // - Idle Ps with no timers when they go idle are never checked in findRunnable
// (for work- or timer-stealing; this is the ideal case).
// - Running Ps must always be checked.
// - Idle Ps whose timers are stolen must continue to be checked until they run
--
cgit v1.3
From eda2e8c683798e435e725f60f0bb580eb4aa9686 Mon Sep 17 00:00:00 2001
From: Nick Ripley
Date: Mon, 17 Nov 2025 11:47:20 -0500
Subject: runtime: clear frame pointer at thread entry points
There are a few places in the runtime where new threads enter Go code
with a possibly invalid frame pointer. mstart is the entry point for new
Ms, and rt0_go is the entrypoint for the program. As we try to introduce
frame pointer unwinding in more places (e.g. for heap profiling in CL
540476 or for execution trace events on the system stack in CL 593835),
we see these functions on the stack. We need to ensure that they have
valid frame pointers. These functions are both considered the "top"
(first) frame frame of the call stack, so this CL sets the frame pointer
register to 0 in these functions.
Updates #63630
Change-Id: I6a6a6964a9ebc6f68ba23d2616e5fb6f19677f97
Reviewed-on: https://go-review.googlesource.com/c/go/+/721020
Reviewed-by: Michael Knyszek
Reviewed-by: Cherry Mui
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Michael Knyszek
---
src/runtime/asm_amd64.s | 15 +++++++++++++++
src/runtime/asm_arm64.s | 15 +++++++++++++++
2 files changed, 30 insertions(+)
(limited to 'src')
diff --git a/src/runtime/asm_amd64.s b/src/runtime/asm_amd64.s
index a4c6c53a90..f4244f6e06 100644
--- a/src/runtime/asm_amd64.s
+++ b/src/runtime/asm_amd64.s
@@ -181,6 +181,14 @@ TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
MOVQ AX, 24(SP)
MOVQ BX, 32(SP)
+ // This is typically the entry point for Go programs.
+ // Call stack unwinding must not proceed past this frame.
+ // Set the frame pointer register to 0 so that frame pointer-based unwinders
+ // (which don't use debug info for performance reasons)
+ // won't attempt to unwind past this function.
+ // See go.dev/issue/63630
+ MOVQ $0, BP
+
// create istack out of the given (operating system) stack.
// _cgo_init may update stackguard.
MOVQ $runtime·g0(SB), DI
@@ -408,6 +416,13 @@ TEXT runtime·asminit(SB),NOSPLIT,$0-0
RET
TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME|NOFRAME,$0
+ // This is the root frame of new Go-created OS threads.
+ // Call stack unwinding must not proceed past this frame.
+ // Set the frame pointer register to 0 so that frame pointer-based unwinders
+ // (which don't use debug info for performance reasons)
+ // won't attempt to unwind past this function.
+ // See go.dev/issue/63630
+ MOVD $0, BP
CALL runtime·mstart0(SB)
RET // not reached
diff --git a/src/runtime/asm_arm64.s b/src/runtime/asm_arm64.s
index 902a7066aa..01f2690f4e 100644
--- a/src/runtime/asm_arm64.s
+++ b/src/runtime/asm_arm64.s
@@ -109,6 +109,14 @@ TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0
MOVW R0, 8(RSP) // argc
MOVD R1, 16(RSP) // argv
+ // This is typically the entry point for Go programs.
+ // Call stack unwinding must not proceed past this frame.
+ // Set the frame pointer register to 0 so that frame pointer-based unwinders
+ // (which don't use debug info for performance reasons)
+ // won't attempt to unwind past this function.
+ // See go.dev/issue/63630
+ MOVD $0, R29
+
#ifdef TLS_darwin
// Initialize TLS.
MOVD ZR, g // clear g, make sure it's not junk.
@@ -248,6 +256,13 @@ TEXT runtime·asminit(SB),NOSPLIT|NOFRAME,$0-0
RET
TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME,$0
+ // This is the root frame of new Go-created OS threads.
+ // Call stack unwinding must not proceed past this frame.
+ // Set the frame pointer register to 0 so that frame pointer-based unwinders
+ // (which don't use debug info for performance reasons)
+ // won't attempt to unwind past this function.
+ // See go.dev/issue/63630
+ MOVD $0, R29
BL runtime·mstart0(SB)
RET // not reached
--
cgit v1.3
From 6caab99026a496107e903469d8c906be66a71896 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Thu, 2 Oct 2025 13:09:03 -0700
Subject: runtime: relax TestMemoryLimit on darwin a bit more
Add 8MB more. Covers most of the failures watchflakes has seen.
Fixes #73136
Change-Id: I593c599a9519b8b31ed0f401d4157d27ac692587
Reviewed-on: https://go-review.googlesource.com/c/go/+/708617
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
Reviewed-by: Keith Randall
---
src/runtime/testdata/testprog/gc.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/runtime/testdata/testprog/gc.go b/src/runtime/testdata/testprog/gc.go
index bbe1453401..32e2c5e1b4 100644
--- a/src/runtime/testdata/testprog/gc.go
+++ b/src/runtime/testdata/testprog/gc.go
@@ -396,7 +396,7 @@ func gcMemoryLimit(gcPercent int) {
// should do considerably better than this bound.
bound := int64(myLimit + 16<<20)
if runtime.GOOS == "darwin" {
- bound += 16 << 20 // Be more lax on Darwin, see issue 73136.
+ bound += 24 << 20 // Be more lax on Darwin, see issue 73136.
}
start := time.Now()
for time.Since(start) < 200*time.Millisecond {
--
cgit v1.3
From e1a12c781f55da85a30fd63471f8adcba908acd2 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 17 Nov 2025 12:47:04 -0800
Subject: cmd/compile: use 32x32->64 multiplies on arm64
Gets rid of some sign extensions.
Change-Id: Ie67ef36b4ca1cd1a2cd9fa5d84578db553578a22
Reviewed-on: https://go-review.googlesource.com/c/go/+/721241
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/ARM64.rules | 4 +++
src/cmd/compile/internal/ssa/rewriteARM64.go | 48 +++++++++++++++++++++++++++
test/codegen/arithmetic.go | 9 +++++
3 files changed, 61 insertions(+)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules
index f54a692725..04f43f3137 100644
--- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules
+++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules
@@ -1814,3 +1814,7 @@
(Select0 (Mul64uover x y)) => (MUL x y)
(Select1 (Mul64uover x y)) => (NotEqual (CMPconst (UMULH x y) [0]))
+
+// 32 mul 32 -> 64
+(MUL r:(MOVWUreg x) s:(MOVWUreg y)) && r.Uses == 1 && s.Uses == 1 => (UMULL x y)
+(MUL r:(MOVWreg x) s:(MOVWreg y)) && r.Uses == 1 && s.Uses == 1 => (MULL x y)
diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go
index 6af1558833..6137ec13a0 100644
--- a/src/cmd/compile/internal/ssa/rewriteARM64.go
+++ b/src/cmd/compile/internal/ssa/rewriteARM64.go
@@ -12556,6 +12556,54 @@ func rewriteValueARM64_OpARM64MUL(v *Value) bool {
}
break
}
+ // match: (MUL r:(MOVWUreg x) s:(MOVWUreg y))
+ // cond: r.Uses == 1 && s.Uses == 1
+ // result: (UMULL x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ r := v_0
+ if r.Op != OpARM64MOVWUreg {
+ continue
+ }
+ x := r.Args[0]
+ s := v_1
+ if s.Op != OpARM64MOVWUreg {
+ continue
+ }
+ y := s.Args[0]
+ if !(r.Uses == 1 && s.Uses == 1) {
+ continue
+ }
+ v.reset(OpARM64UMULL)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
+ // match: (MUL r:(MOVWreg x) s:(MOVWreg y))
+ // cond: r.Uses == 1 && s.Uses == 1
+ // result: (MULL x y)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ r := v_0
+ if r.Op != OpARM64MOVWreg {
+ continue
+ }
+ x := r.Args[0]
+ s := v_1
+ if s.Op != OpARM64MOVWreg {
+ continue
+ }
+ y := s.Args[0]
+ if !(r.Uses == 1 && s.Uses == 1) {
+ continue
+ }
+ v.reset(OpARM64MULL)
+ v.AddArg2(x, y)
+ return true
+ }
+ break
+ }
return false
}
func rewriteValueARM64_OpARM64MULW(v *Value) bool {
diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go
index 6b2c5529e1..9443d812dc 100644
--- a/test/codegen/arithmetic.go
+++ b/test/codegen/arithmetic.go
@@ -333,6 +333,15 @@ func Fold2NegMul(a, b int) int {
return -a * -b
}
+func Mul32(a, b int32) int64 {
+ // arm64:"SMULL" -"MOVW"
+ return int64(a) * int64(b)
+}
+func Mul32U(a, b uint32) uint64 {
+ // arm64:"UMULL" -"MOVWU"
+ return uint64(a) * uint64(b)
+}
+
// -------------- //
// Division //
// -------------- //
--
cgit v1.3
From a087dea8692eee879e8226b70eb691dea7758b0b Mon Sep 17 00:00:00 2001
From: WANG Xuerui
Date: Sat, 13 Sep 2025 15:57:12 +0800
Subject: debug/elf: sync new loong64 relocation types up to LoongArch ELF
psABI v20250521
Add several new relocation types defined in newer versions of LoongArch
ELF psABI v20250521, part of the v2.40 spec bundle. The new relocations
are seeing increased adoption because distributions are moving to newer
GNU/LLVM toolchain versions, so Go's internal linker must be prepared to
handle some of them, especially R_LARCH_CALL36 because the ecosystem is
slowly migrating to the "medium" code model by default.
The constants R_LARCH_DELETE and R_LARCH_CFA were removed in LoongArch
ELF psABI v20231102 (spec bundle v2.20), but they are already part of
the public API, so they are retained for now for upholding the go1
compatibility guarantee.
Corresponding binutils implementation:
* R_LARCH_CALL36: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=dc5f359ed6a36d2c895d89c3e4886f3a2b6d9232
* TLSDESC: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=26265e7fdf19d461563388495b6799eb3719f80a
* TLS {LD,GD,DESC} relaxation: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=ae296cc45258b95223210263d1b91115e84beb56
* TLS LE relaxation: https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=775dead218e12e3fb94481c7a99aa0238d6a9138
Updates #75562
See: https://github.com/loongson/la-abi-specs/blob/v2.40/laelf.adoc
See: https://github.com/loongson-community/discussions/issues/43
Change-Id: Ib023e0b6becc0862d27afc419d3eb84c737359db
Reviewed-on: https://go-review.googlesource.com/c/go/+/709716
Reviewed-by: Mark Freeman
Reviewed-by: Meidan Li
Reviewed-by: abner chenc
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: sophie zhao
---
api/next/75562.txt | 38 +++++++++++++++++++++++++++
doc/next/6-stdlib/99-minor/debug/elf/75562.md | 4 +++
src/debug/elf/elf.go | 38 +++++++++++++++++++++++++++
src/debug/elf/elf_test.go | 1 +
4 files changed, 81 insertions(+)
create mode 100644 api/next/75562.txt
create mode 100644 doc/next/6-stdlib/99-minor/debug/elf/75562.md
(limited to 'src')
diff --git a/api/next/75562.txt b/api/next/75562.txt
new file mode 100644
index 0000000000..5e3fe6c6a5
--- /dev/null
+++ b/api/next/75562.txt
@@ -0,0 +1,38 @@
+pkg debug/elf, const R_LARCH_TLS_DESC32 = 13 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC32 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64 = 14 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64 R_LARCH #75562
+pkg debug/elf, const R_LARCH_CALL36 = 110 #75562
+pkg debug/elf, const R_LARCH_CALL36 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 = 111 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PC_HI20 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 = 112 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PC_LO12 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 = 113 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_PC_LO20 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 = 114 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_PC_HI12 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_HI20 = 115 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_HI20 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_LO12 = 116 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_LO12 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 = 117 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_LO20 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 = 118 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC64_HI12 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_LD = 119 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_LD R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_CALL = 120 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_CALL R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_LE_HI20_R = 121 #75562
+pkg debug/elf, const R_LARCH_TLS_LE_HI20_R R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_LE_ADD_R = 122 #75562
+pkg debug/elf, const R_LARCH_TLS_LE_ADD_R R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_LE_LO12_R = 123 #75562
+pkg debug/elf, const R_LARCH_TLS_LE_LO12_R R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 = 124 #75562
+pkg debug/elf, const R_LARCH_TLS_LD_PCREL20_S2 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 = 125 #75562
+pkg debug/elf, const R_LARCH_TLS_GD_PCREL20_S2 R_LARCH #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 = 126 #75562
+pkg debug/elf, const R_LARCH_TLS_DESC_PCREL20_S2 R_LARCH #75562
diff --git a/doc/next/6-stdlib/99-minor/debug/elf/75562.md b/doc/next/6-stdlib/99-minor/debug/elf/75562.md
new file mode 100644
index 0000000000..306111ddd8
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/debug/elf/75562.md
@@ -0,0 +1,4 @@
+Additional `R_LARCH_*` constants from [LoongArch ELF psABI v20250521][laelf-20250521]
+(global version v2.40) are defined for use with LoongArch systems.
+
+[laelf-20250521]: https://github.com/loongson/la-abi-specs/blob/v2.40/laelf.adoc
diff --git a/src/debug/elf/elf.go b/src/debug/elf/elf.go
index 58e37daed2..557648ece9 100644
--- a/src/debug/elf/elf.go
+++ b/src/debug/elf/elf.go
@@ -2305,6 +2305,8 @@ const (
R_LARCH_TLS_TPREL32 R_LARCH = 10
R_LARCH_TLS_TPREL64 R_LARCH = 11
R_LARCH_IRELATIVE R_LARCH = 12
+ R_LARCH_TLS_DESC32 R_LARCH = 13
+ R_LARCH_TLS_DESC64 R_LARCH = 14
R_LARCH_MARK_LA R_LARCH = 20
R_LARCH_MARK_PCREL R_LARCH = 21
R_LARCH_SOP_PUSH_PCREL R_LARCH = 22
@@ -2390,6 +2392,23 @@ const (
R_LARCH_ADD_ULEB128 R_LARCH = 107
R_LARCH_SUB_ULEB128 R_LARCH = 108
R_LARCH_64_PCREL R_LARCH = 109
+ R_LARCH_CALL36 R_LARCH = 110
+ R_LARCH_TLS_DESC_PC_HI20 R_LARCH = 111
+ R_LARCH_TLS_DESC_PC_LO12 R_LARCH = 112
+ R_LARCH_TLS_DESC64_PC_LO20 R_LARCH = 113
+ R_LARCH_TLS_DESC64_PC_HI12 R_LARCH = 114
+ R_LARCH_TLS_DESC_HI20 R_LARCH = 115
+ R_LARCH_TLS_DESC_LO12 R_LARCH = 116
+ R_LARCH_TLS_DESC64_LO20 R_LARCH = 117
+ R_LARCH_TLS_DESC64_HI12 R_LARCH = 118
+ R_LARCH_TLS_DESC_LD R_LARCH = 119
+ R_LARCH_TLS_DESC_CALL R_LARCH = 120
+ R_LARCH_TLS_LE_HI20_R R_LARCH = 121
+ R_LARCH_TLS_LE_ADD_R R_LARCH = 122
+ R_LARCH_TLS_LE_LO12_R R_LARCH = 123
+ R_LARCH_TLS_LD_PCREL20_S2 R_LARCH = 124
+ R_LARCH_TLS_GD_PCREL20_S2 R_LARCH = 125
+ R_LARCH_TLS_DESC_PCREL20_S2 R_LARCH = 126
)
var rlarchStrings = []intName{
@@ -2406,6 +2425,8 @@ var rlarchStrings = []intName{
{10, "R_LARCH_TLS_TPREL32"},
{11, "R_LARCH_TLS_TPREL64"},
{12, "R_LARCH_IRELATIVE"},
+ {13, "R_LARCH_TLS_DESC32"},
+ {14, "R_LARCH_TLS_DESC64"},
{20, "R_LARCH_MARK_LA"},
{21, "R_LARCH_MARK_PCREL"},
{22, "R_LARCH_SOP_PUSH_PCREL"},
@@ -2491,6 +2512,23 @@ var rlarchStrings = []intName{
{107, "R_LARCH_ADD_ULEB128"},
{108, "R_LARCH_SUB_ULEB128"},
{109, "R_LARCH_64_PCREL"},
+ {110, "R_LARCH_CALL36"},
+ {111, "R_LARCH_TLS_DESC_PC_HI20"},
+ {112, "R_LARCH_TLS_DESC_PC_LO12"},
+ {113, "R_LARCH_TLS_DESC64_PC_LO20"},
+ {114, "R_LARCH_TLS_DESC64_PC_HI12"},
+ {115, "R_LARCH_TLS_DESC_HI20"},
+ {116, "R_LARCH_TLS_DESC_LO12"},
+ {117, "R_LARCH_TLS_DESC64_LO20"},
+ {118, "R_LARCH_TLS_DESC64_HI12"},
+ {119, "R_LARCH_TLS_DESC_LD"},
+ {120, "R_LARCH_TLS_DESC_CALL"},
+ {121, "R_LARCH_TLS_LE_HI20_R"},
+ {122, "R_LARCH_TLS_LE_ADD_R"},
+ {123, "R_LARCH_TLS_LE_LO12_R"},
+ {124, "R_LARCH_TLS_LD_PCREL20_S2"},
+ {125, "R_LARCH_TLS_GD_PCREL20_S2"},
+ {126, "R_LARCH_TLS_DESC_PCREL20_S2"},
}
func (i R_LARCH) String() string { return stringName(uint32(i), rlarchStrings, false) }
diff --git a/src/debug/elf/elf_test.go b/src/debug/elf/elf_test.go
index 0350d53050..256f850f96 100644
--- a/src/debug/elf/elf_test.go
+++ b/src/debug/elf/elf_test.go
@@ -34,6 +34,7 @@ var nameTests = []nameTest{
{R_ALPHA_OP_PUSH, "R_ALPHA_OP_PUSH"},
{R_ARM_THM_ABS5, "R_ARM_THM_ABS5"},
{R_386_GOT32, "R_386_GOT32"},
+ {R_LARCH_CALL36, "R_LARCH_CALL36"},
{R_PPC_GOT16_HI, "R_PPC_GOT16_HI"},
{R_SPARC_GOT22, "R_SPARC_GOT22"},
{ET_LOOS + 5, "ET_LOOS+5"},
--
cgit v1.3
From b9ef0633f6117c74fabcd7247a76b4feb86df086 Mon Sep 17 00:00:00 2001
From: Joel Sing
Date: Sun, 27 Aug 2023 19:35:33 +1000
Subject: cmd/internal/sys,internal/goarch,runtime: enable the use of
compressed instructions on riscv64
Enable the use of compressed instructions on riscv64 by reducing
the PC quantum to two bytes and reducing the minimum instruction
length to two bytes. Change gostartcall on riscv64 to land at
two times the PC quantum into goexit, so that we retain four byte
alignment and revise the NOP instructions in goexit to ensure that
they are never compressed. Additionally, adjust PCALIGN so that it
correctly handles two byte offsets.
Fixes #47560
Updates #71105
Cq-Include-Trybots: luci.golang.try:gotip-linux-riscv64
Change-Id: I4329a8fbfcb4de636aadaeadabb826bc22698640
Reviewed-on: https://go-review.googlesource.com/c/go/+/523477
Reviewed-by: Junyang Shao
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Mark Ryan
---
src/cmd/internal/obj/riscv/obj.go | 11 +++++++++--
src/cmd/internal/sys/arch.go | 2 +-
src/internal/goarch/goarch_riscv64.go | 2 +-
src/runtime/asm_riscv64.s | 10 +++++-----
src/runtime/sys_riscv64.go | 11 +++++++++--
5 files changed, 25 insertions(+), 11 deletions(-)
(limited to 'src')
diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go
index 3deab34d31..8b9be5d78b 100644
--- a/src/cmd/internal/obj/riscv/obj.go
+++ b/src/cmd/internal/obj/riscv/obj.go
@@ -4799,10 +4799,17 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
v := pcAlignPadLength(p.Pc, alignedValue)
offset := p.Pc
for ; v >= 4; v -= 4 {
- // NOP
- cursym.WriteBytes(ctxt, offset, []byte{0x13, 0, 0, 0})
+ // NOP (ADDI $0, X0, X0)
+ cursym.WriteBytes(ctxt, offset, []byte{0x13, 0x00, 0x00, 0x00})
offset += 4
}
+ if v == 2 {
+ // CNOP
+ cursym.WriteBytes(ctxt, offset, []byte{0x01, 0x00})
+ offset += 2
+ } else if v != 0 {
+ ctxt.Diag("bad PCALIGN pad length")
+ }
continue
}
diff --git a/src/cmd/internal/sys/arch.go b/src/cmd/internal/sys/arch.go
index 3c92a6bbf2..14b1cde22b 100644
--- a/src/cmd/internal/sys/arch.go
+++ b/src/cmd/internal/sys/arch.go
@@ -236,7 +236,7 @@ var ArchRISCV64 = &Arch{
ByteOrder: binary.LittleEndian,
PtrSize: 8,
RegSize: 8,
- MinLC: 4,
+ MinLC: 2,
Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS)
CanMergeLoads: false,
HasLR: true,
diff --git a/src/internal/goarch/goarch_riscv64.go b/src/internal/goarch/goarch_riscv64.go
index 3b6da1e02f..468f9a6374 100644
--- a/src/internal/goarch/goarch_riscv64.go
+++ b/src/internal/goarch/goarch_riscv64.go
@@ -7,7 +7,7 @@ package goarch
const (
_ArchFamily = RISCV64
_DefaultPhysPageSize = 4096
- _PCQuantum = 4
+ _PCQuantum = 2
_MinFrameSize = 8
_StackAlign = PtrSize
)
diff --git a/src/runtime/asm_riscv64.s b/src/runtime/asm_riscv64.s
index 5bd16181ee..428701a503 100644
--- a/src/runtime/asm_riscv64.s
+++ b/src/runtime/asm_riscv64.s
@@ -623,14 +623,14 @@ TEXT _cgo_topofstack(SB),NOSPLIT,$8
RET
// func goexit(neverCallThisFunction)
-// The top-most function running on a goroutine
-// returns to goexit+PCQuantum.
+// The top-most function running on a goroutine, returns to goexit+PCQuantum*2.
+// Note that the NOPs are written in a manner that will not be compressed,
+// since the offset must be known by the runtime.
TEXT runtime·goexit(SB),NOSPLIT|NOFRAME|TOPFRAME,$0-0
- MOV ZERO, ZERO // NOP
+ WORD $0x00000013 // NOP
JMP runtime·goexit1(SB) // does not return
// traceback from goexit1 must hit code range of goexit
- MOV ZERO, ZERO // NOP
-
+ WORD $0x00000013 // NOP
// This is called from .init_array and follows the platform, not the Go ABI.
TEXT runtime·addmoduledata(SB),NOSPLIT,$0-0
diff --git a/src/runtime/sys_riscv64.go b/src/runtime/sys_riscv64.go
index e710840819..65dc684c33 100644
--- a/src/runtime/sys_riscv64.go
+++ b/src/runtime/sys_riscv64.go
@@ -4,7 +4,12 @@
package runtime
-import "unsafe"
+import (
+ "unsafe"
+
+ "internal/abi"
+ "internal/runtime/sys"
+)
// adjust Gobuf as if it executed a call to fn with context ctxt
// and then did an immediate Gosave.
@@ -12,7 +17,9 @@ func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {
if buf.lr != 0 {
throw("invalid use of gostartcall")
}
- buf.lr = buf.pc
+ // Use double the PC quantum on riscv64, so that we retain
+ // four byte alignment and use non-compressed instructions.
+ buf.lr = abi.FuncPCABI0(goexit) + sys.PCQuantum*2
buf.pc = uintptr(fn)
buf.ctxt = ctxt
}
--
cgit v1.3
From 9859b436430aac382b337964a1b380bc4bfcda70 Mon Sep 17 00:00:00 2001
From: Joel Sing
Date: Fri, 26 Sep 2025 05:05:49 +1000
Subject: cmd/asm,cmd/compile,cmd/internal/obj/riscv: use compressed
instructions on riscv64
Make use of compressed instructions on riscv64 - add a compress
pass to the end of the assembler, which replaces non-compressed
instructions with compressed alternatives if possible.
Provide a `compressinstructions` compiler and assembler debug
flag, such that the compression pass can be disabled via
`-asmflags=all=-d=compressinstructions=0` and
`-gcflags=all=-d=compressinstructions=0`. Note that this does
not prevent the explicit use of compressed instructions via
assembly.
Note that this does not make use of compressed control transfer
instructions - this will be implemented in later changes.
Reduces the text size of a hello world binary by ~121KB
and reduces the text size of the go binary on riscv64 by ~1.21MB
(between 8-10% in both cases).
Updates #71105
Cq-Include-Trybots: luci.golang.try:gotip-linux-riscv64
Change-Id: I24258353688554042c2a836deed4830cc673e985
Reviewed-on: https://go-review.googlesource.com/c/go/+/523478
Reviewed-by: Mark Ryan
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Cherry Mui
---
src/cmd/asm/internal/flags/flags.go | 7 +-
src/cmd/asm/main.go | 1 +
src/cmd/compile/internal/base/debug.go | 1 +
src/cmd/compile/internal/base/flag.go | 2 +
src/cmd/internal/obj/link.go | 61 +++++------
src/cmd/internal/obj/riscv/asm_test.go | 16 +--
src/cmd/internal/obj/riscv/cpu.go | 3 +
src/cmd/internal/obj/riscv/obj.go | 178 +++++++++++++++++++++++++++++++--
src/cmd/link/internal/ld/ld_test.go | 4 +-
9 files changed, 225 insertions(+), 48 deletions(-)
(limited to 'src')
diff --git a/src/cmd/asm/internal/flags/flags.go b/src/cmd/asm/internal/flags/flags.go
index e15a062749..19aa65630f 100644
--- a/src/cmd/asm/internal/flags/flags.go
+++ b/src/cmd/asm/internal/flags/flags.go
@@ -29,8 +29,9 @@ var (
)
var DebugFlags struct {
- MayMoreStack string `help:"call named function before all stack growth checks"`
- PCTab string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"`
+ CompressInstructions int `help:"use compressed instructions when possible (if supported by architecture)"`
+ MayMoreStack string `help:"call named function before all stack growth checks"`
+ PCTab string `help:"print named pc-value table\nOne of: pctospadj, pctofile, pctoline, pctoinline, pctopcdata"`
}
var (
@@ -47,6 +48,8 @@ func init() {
flag.Var(objabi.NewDebugFlag(&DebugFlags, nil), "d", "enable debugging settings; try -d help")
objabi.AddVersionFlag() // -V
objabi.Flagcount("S", "print assembly and machine code", &PrintOut)
+
+ DebugFlags.CompressInstructions = 1
}
// MultiFlag allows setting a value multiple times to collect a list, as in -I=dir1 -I=dir2.
diff --git a/src/cmd/asm/main.go b/src/cmd/asm/main.go
index f2697db516..25cf307140 100644
--- a/src/cmd/asm/main.go
+++ b/src/cmd/asm/main.go
@@ -40,6 +40,7 @@ func main() {
log.Fatalf("unrecognized architecture %s", GOARCH)
}
ctxt := obj.Linknew(architecture.LinkArch)
+ ctxt.CompressInstructions = flags.DebugFlags.CompressInstructions != 0
ctxt.Debugasm = flags.PrintOut
ctxt.Debugvlog = flags.DebugV
ctxt.Flag_dynlink = *flags.Dynlink
diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go
index 9e8ab2f488..b532bf435e 100644
--- a/src/cmd/compile/internal/base/debug.go
+++ b/src/cmd/compile/internal/base/debug.go
@@ -20,6 +20,7 @@ type DebugFlags struct {
Append int `help:"print information about append compilation"`
Checkptr int `help:"instrument unsafe pointer conversions\n0: instrumentation disabled\n1: conversions involving unsafe.Pointer are instrumented\n2: conversions to unsafe.Pointer force heap allocation" concurrent:"ok"`
Closure int `help:"print information about closure compilation"`
+ CompressInstructions int `help:"use compressed instructions when possible (if supported by architecture)"`
Converthash string `help:"hash value for use in debugging changes to platform-dependent float-to-[u]int conversion" concurrent:"ok"`
Defer int `help:"print information about defer compilation"`
DisableNil int `help:"disable nil checks" concurrent:"ok"`
diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go
index 1d211e0a2d..63cae41524 100644
--- a/src/cmd/compile/internal/base/flag.go
+++ b/src/cmd/compile/internal/base/flag.go
@@ -177,6 +177,7 @@ func ParseFlags() {
Flag.WB = true
Debug.ConcurrentOk = true
+ Debug.CompressInstructions = 1
Debug.MaxShapeLen = 500
Debug.AlignHot = 1
Debug.InlFuncsWithClosures = 1
@@ -299,6 +300,7 @@ func ParseFlags() {
}
parseSpectre(Flag.Spectre) // left as string for RecordFlags
+ Ctxt.CompressInstructions = Debug.CompressInstructions != 0
Ctxt.Flag_shared = Ctxt.Flag_dynlink || Ctxt.Flag_shared
Ctxt.Flag_optimize = Flag.N == 0
Ctxt.Debugasm = int(Flag.S)
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 85dca33d27..c70c1d9438 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -1153,36 +1153,37 @@ type Func interface {
// Link holds the context for writing object code from a compiler
// to be linker input or for reading that input into the linker.
type Link struct {
- Headtype objabi.HeadType
- Arch *LinkArch
- Debugasm int
- Debugvlog bool
- Debugpcln string
- Flag_shared bool
- Flag_dynlink bool
- Flag_linkshared bool
- Flag_optimize bool
- Flag_locationlists bool
- Flag_noRefName bool // do not include referenced symbol names in object file
- Retpoline bool // emit use of retpoline stubs for indirect jmp/call
- Flag_maymorestack string // If not "", call this function before stack checks
- Bso *bufio.Writer
- Pathname string
- Pkgpath string // the current package's import path
- hashmu sync.Mutex // protects hash, funchash
- hash map[string]*LSym // name -> sym mapping
- funchash map[string]*LSym // name -> sym mapping for ABIInternal syms
- statichash map[string]*LSym // name -> sym mapping for static syms
- PosTable src.PosTable
- InlTree InlTree // global inlining tree used by gc/inl.go
- DwFixups *DwarfFixupTable
- DwTextCount int
- Imports []goobj.ImportedPkg
- DiagFunc func(string, ...any)
- DiagFlush func()
- DebugInfo func(ctxt *Link, fn *LSym, info *LSym, curfn Func) ([]dwarf.Scope, dwarf.InlCalls)
- GenAbstractFunc func(fn *LSym)
- Errors int
+ Headtype objabi.HeadType
+ Arch *LinkArch
+ CompressInstructions bool // use compressed instructions where possible (if supported by architecture)
+ Debugasm int
+ Debugvlog bool
+ Debugpcln string
+ Flag_shared bool
+ Flag_dynlink bool
+ Flag_linkshared bool
+ Flag_optimize bool
+ Flag_locationlists bool
+ Flag_noRefName bool // do not include referenced symbol names in object file
+ Retpoline bool // emit use of retpoline stubs for indirect jmp/call
+ Flag_maymorestack string // If not "", call this function before stack checks
+ Bso *bufio.Writer
+ Pathname string
+ Pkgpath string // the current package's import path
+ hashmu sync.Mutex // protects hash, funchash
+ hash map[string]*LSym // name -> sym mapping
+ funchash map[string]*LSym // name -> sym mapping for ABIInternal syms
+ statichash map[string]*LSym // name -> sym mapping for static syms
+ PosTable src.PosTable
+ InlTree InlTree // global inlining tree used by gc/inl.go
+ DwFixups *DwarfFixupTable
+ DwTextCount int
+ Imports []goobj.ImportedPkg
+ DiagFunc func(string, ...any)
+ DiagFlush func()
+ DebugInfo func(ctxt *Link, fn *LSym, info *LSym, curfn Func) ([]dwarf.Scope, dwarf.InlCalls)
+ GenAbstractFunc func(fn *LSym)
+ Errors int
InParallel bool // parallel backend phase in effect
UseBASEntries bool // use Base Address Selection Entries in location lists and PC ranges
diff --git a/src/cmd/internal/obj/riscv/asm_test.go b/src/cmd/internal/obj/riscv/asm_test.go
index f40e57fa64..5b50d1533a 100644
--- a/src/cmd/internal/obj/riscv/asm_test.go
+++ b/src/cmd/internal/obj/riscv/asm_test.go
@@ -11,8 +11,8 @@ import (
"os"
"os/exec"
"path/filepath"
+ "regexp"
"runtime"
- "strings"
"testing"
)
@@ -48,10 +48,10 @@ func genLargeBranch(buf *bytes.Buffer) {
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
fmt.Fprintln(buf, "BEQ X0, X0, label")
for i := 0; i < 1<<19; i++ {
- fmt.Fprintln(buf, "ADD $0, X0, X0")
+ fmt.Fprintln(buf, "ADD $0, X5, X0")
}
fmt.Fprintln(buf, "label:")
- fmt.Fprintln(buf, "ADD $0, X0, X0")
+ fmt.Fprintln(buf, "ADD $0, X5, X0")
}
// TestLargeCall generates a large function (>1MB of text) with a call to
@@ -112,11 +112,11 @@ func genLargeCall(buf *bytes.Buffer) {
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
fmt.Fprintln(buf, "CALL ·y(SB)")
for i := 0; i < 1<<19; i++ {
- fmt.Fprintln(buf, "ADD $0, X0, X0")
+ fmt.Fprintln(buf, "ADD $0, X5, X0")
}
fmt.Fprintln(buf, "RET")
fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
- fmt.Fprintln(buf, "ADD $0, X0, X0")
+ fmt.Fprintln(buf, "ADD $0, X5, X0")
fmt.Fprintln(buf, "RET")
}
@@ -301,9 +301,9 @@ TEXT _stub(SB),$0-0
// FENCE
// NOP
// FENCE
- // RET
- want := "0f 00 f0 0f 13 00 00 00 0f 00 f0 0f 67 80 00 00"
- if !strings.Contains(string(out), want) {
+ // RET (CJALR or JALR)
+ want := regexp.MustCompile("0x0000 0f 00 f0 0f 13 00 00 00 0f 00 f0 0f (82 80|67 80 00 00) ")
+ if !want.Match(out) {
t.Errorf("PCALIGN test failed - got %s\nwant %s", out, want)
}
}
diff --git a/src/cmd/internal/obj/riscv/cpu.go b/src/cmd/internal/obj/riscv/cpu.go
index 60174a0b3a..a91395dd38 100644
--- a/src/cmd/internal/obj/riscv/cpu.go
+++ b/src/cmd/internal/obj/riscv/cpu.go
@@ -326,6 +326,9 @@ const (
NEED_GOT_PCREL_ITYPE_RELOC
)
+const NEED_RELOC = NEED_JAL_RELOC | NEED_CALL_RELOC | NEED_PCREL_ITYPE_RELOC |
+ NEED_PCREL_STYPE_RELOC | NEED_GOT_PCREL_ITYPE_RELOC
+
// RISC-V mnemonics, as defined in the "opcodes" and "opcodes-pseudo" files
// at https://github.com/riscv/riscv-opcodes.
//
diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go
index 8b9be5d78b..043be17c07 100644
--- a/src/cmd/internal/obj/riscv/obj.go
+++ b/src/cmd/internal/obj/riscv/obj.go
@@ -414,10 +414,10 @@ func containsCall(sym *obj.LSym) bool {
// setPCs sets the Pc field in all instructions reachable from p.
// It uses pc as the initial value and returns the next available pc.
-func setPCs(p *obj.Prog, pc int64) int64 {
+func setPCs(p *obj.Prog, pc int64, compress bool) int64 {
for ; p != nil; p = p.Link {
p.Pc = pc
- for _, ins := range instructionsForProg(p) {
+ for _, ins := range instructionsForProg(p, compress) {
pc += int64(ins.length())
}
@@ -671,7 +671,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
// a fixed point will be reached). No attempt to handle functions > 2GiB.
for {
big, rescan := false, false
- maxPC := setPCs(cursym.Func().Text, 0)
+ maxPC := setPCs(cursym.Func().Text, 0, ctxt.CompressInstructions)
if maxPC+maxTrampSize > (1 << 20) {
big = true
}
@@ -801,7 +801,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
// Validate all instructions - this provides nice error messages.
for p := cursym.Func().Text; p != nil; p = p.Link {
- for _, ins := range instructionsForProg(p) {
+ for _, ins := range instructionsForProg(p, ctxt.CompressInstructions) {
ins.validate(ctxt)
}
}
@@ -1141,6 +1141,14 @@ func wantImmU(ctxt *obj.Link, ins *instruction, imm int64, nbits uint) {
}
}
+func isScaledImmI(imm int64, nbits uint, scale int64) bool {
+ return immFits(imm, nbits, true) == nil && imm%scale == 0
+}
+
+func isScaledImmU(imm int64, nbits uint, scale int64) bool {
+ return immFits(imm, nbits, false) == nil && imm%scale == 0
+}
+
func wantScaledImm(ctxt *obj.Link, ins *instruction, imm int64, nbits uint, scale int64, signed bool) {
if err := immFits(imm, nbits, signed); err != nil {
ctxt.Diag("%v: %v", ins, err)
@@ -1180,6 +1188,10 @@ func wantIntReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
wantReg(ctxt, ins, pos, "integer", r, REG_X0, REG_X31)
}
+func isIntPrimeReg(r uint32) bool {
+ return r >= REG_X8 && r <= REG_X15
+}
+
// wantIntPrimeReg checks that r is an integer register that can be used
// in a prime register field of a compressed instruction.
func wantIntPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
@@ -1191,6 +1203,10 @@ func wantFloatReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
wantReg(ctxt, ins, pos, "float", r, REG_F0, REG_F31)
}
+func isFloatPrimeReg(r uint32) bool {
+ return r >= REG_F8 && r <= REG_F15
+}
+
// wantFloatPrimeReg checks that r is an floating-point register that can
// be used in a prime register field of a compressed instruction.
func wantFloatPrimeReg(ctxt *obj.Link, ins *instruction, pos string, r uint32) {
@@ -3515,6 +3531,147 @@ func (ins *instruction) usesRegTmp() bool {
return ins.rd == REG_TMP || ins.rs1 == REG_TMP || ins.rs2 == REG_TMP
}
+func (ins *instruction) compress() {
+ switch ins.as {
+ case ALW:
+ if ins.rd != REG_X0 && ins.rs1 == REG_SP && isScaledImmU(ins.imm, 8, 4) {
+ ins.as, ins.rs1, ins.rs2 = ACLWSP, obj.REG_NONE, ins.rs1
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 7, 4) {
+ ins.as = ACLW
+ }
+
+ case ALD:
+ if ins.rs1 == REG_SP && ins.rd != REG_X0 && isScaledImmU(ins.imm, 9, 8) {
+ ins.as, ins.rs1, ins.rs2 = ACLDSP, obj.REG_NONE, ins.rs1
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
+ ins.as = ACLD
+ }
+
+ case AFLD:
+ if ins.rs1 == REG_SP && isScaledImmU(ins.imm, 9, 8) {
+ ins.as, ins.rs1, ins.rs2 = ACFLDSP, obj.REG_NONE, ins.rs1
+ } else if isFloatPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
+ ins.as = ACFLD
+ }
+
+ case ASW:
+ if ins.rd == REG_SP && isScaledImmU(ins.imm, 8, 4) {
+ ins.as, ins.rs1, ins.rs2 = ACSWSP, obj.REG_NONE, ins.rs1
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 7, 4) {
+ ins.as, ins.rd, ins.rs1, ins.rs2 = ACSW, obj.REG_NONE, ins.rd, ins.rs1
+ }
+
+ case ASD:
+ if ins.rd == REG_SP && isScaledImmU(ins.imm, 9, 8) {
+ ins.as, ins.rs1, ins.rs2 = ACSDSP, obj.REG_NONE, ins.rs1
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
+ ins.as, ins.rd, ins.rs1, ins.rs2 = ACSD, obj.REG_NONE, ins.rd, ins.rs1
+ }
+
+ case AFSD:
+ if ins.rd == REG_SP && isScaledImmU(ins.imm, 9, 8) {
+ ins.as, ins.rs1, ins.rs2 = ACFSDSP, obj.REG_NONE, ins.rs1
+ } else if isIntPrimeReg(ins.rd) && isFloatPrimeReg(ins.rs1) && isScaledImmU(ins.imm, 8, 8) {
+ ins.as, ins.rd, ins.rs1, ins.rs2 = ACFSD, obj.REG_NONE, ins.rd, ins.rs1
+ }
+
+ case AADDI:
+ if ins.rd == REG_SP && ins.rs1 == REG_SP && ins.imm != 0 && isScaledImmI(ins.imm, 10, 16) {
+ ins.as = ACADDI16SP
+ } else if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.imm != 0 && immIFits(ins.imm, 6) == nil {
+ ins.as = ACADDI
+ } else if isIntPrimeReg(ins.rd) && ins.rs1 == REG_SP && ins.imm != 0 && isScaledImmU(ins.imm, 10, 4) {
+ ins.as = ACADDI4SPN
+ } else if ins.rd != REG_X0 && ins.rs1 == REG_X0 && immIFits(ins.imm, 6) == nil {
+ ins.as, ins.rs1 = ACLI, obj.REG_NONE
+ } else if ins.rd != REG_X0 && ins.rs1 != REG_X0 && ins.imm == 0 {
+ ins.as, ins.rs1, ins.rs2 = ACMV, obj.REG_NONE, ins.rs1
+ } else if ins.rd == REG_X0 && ins.rs1 == REG_X0 && ins.imm == 0 {
+ ins.as, ins.rs1 = ACNOP, ins.rd
+ }
+
+ case AADDIW:
+ if ins.rd == ins.rs1 && immIFits(ins.imm, 6) == nil {
+ ins.as = ACADDIW
+ }
+
+ case ALUI:
+ if ins.rd != REG_X0 && ins.rd != REG_SP && ins.imm != 0 && immIFits(ins.imm, 6) == nil {
+ ins.as = ACLUI
+ }
+
+ case ASLLI:
+ if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.imm != 0 {
+ ins.as = ACSLLI
+ }
+
+ case ASRLI:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && ins.imm != 0 {
+ ins.as = ACSRLI
+ }
+
+ case ASRAI:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && ins.imm != 0 {
+ ins.as = ACSRAI
+ }
+
+ case AANDI:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && immIFits(ins.imm, 6) == nil {
+ ins.as = ACANDI
+ }
+
+ case AADD:
+ if ins.rd != REG_X0 && ins.rd == ins.rs1 && ins.rs2 != REG_X0 {
+ ins.as = ACADD
+ } else if ins.rd != REG_X0 && ins.rd == ins.rs2 && ins.rs1 != REG_X0 {
+ ins.as, ins.rs1, ins.rs2 = ACADD, ins.rs2, ins.rs1
+ } else if ins.rd != REG_X0 && ins.rs1 == REG_X0 && ins.rs2 != REG_X0 {
+ ins.as = ACMV
+ }
+
+ case AADDW:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACADDW
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
+ ins.as, ins.rs1, ins.rs2 = ACADDW, ins.rs2, ins.rs1
+ }
+
+ case ASUB:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACSUB
+ }
+
+ case ASUBW:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACSUBW
+ }
+
+ case AAND:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACAND
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
+ ins.as, ins.rs1, ins.rs2 = ACAND, ins.rs2, ins.rs1
+ }
+
+ case AOR:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACOR
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
+ ins.as, ins.rs1, ins.rs2 = ACOR, ins.rs2, ins.rs1
+ }
+
+ case AXOR:
+ if isIntPrimeReg(ins.rd) && ins.rd == ins.rs1 && isIntPrimeReg(ins.rs2) {
+ ins.as = ACXOR
+ } else if isIntPrimeReg(ins.rd) && isIntPrimeReg(ins.rs1) && ins.rd == ins.rs2 {
+ ins.as, ins.rs1, ins.rs2 = ACXOR, ins.rs2, ins.rs1
+ }
+
+ case AEBREAK:
+ ins.as, ins.rd, ins.rs1 = ACEBREAK, obj.REG_NONE, obj.REG_NONE
+ }
+}
+
// instructionForProg returns the default *obj.Prog to instruction mapping.
func instructionForProg(p *obj.Prog) *instruction {
ins := &instruction{
@@ -4057,7 +4214,7 @@ func instructionsForMinMax(p *obj.Prog, ins *instruction) []*instruction {
}
// instructionsForProg returns the machine instructions for an *obj.Prog.
-func instructionsForProg(p *obj.Prog) []*instruction {
+func instructionsForProg(p *obj.Prog, compress bool) []*instruction {
ins := instructionForProg(p)
inss := []*instruction{ins}
@@ -4710,6 +4867,15 @@ func instructionsForProg(p *obj.Prog) []*instruction {
ins.rs1, ins.rs2 = obj.REG_NONE, REG_V0
}
+ // Only compress instructions when there is no relocation, since
+ // relocation relies on knowledge about the exact instructions that
+ // are in use.
+ if compress && p.Mark&NEED_RELOC == 0 {
+ for _, ins := range inss {
+ ins.compress()
+ }
+ }
+
for _, ins := range inss {
ins.p = p
}
@@ -4814,7 +4980,7 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
}
offset := p.Pc
- for _, ins := range instructionsForProg(p) {
+ for _, ins := range instructionsForProg(p, ctxt.CompressInstructions) {
if ic, err := ins.encode(); err == nil {
cursym.WriteInt(ctxt, offset, ins.length(), int64(ic))
offset += int64(ins.length())
diff --git a/src/cmd/link/internal/ld/ld_test.go b/src/cmd/link/internal/ld/ld_test.go
index 9a27ac8c76..64b86f3a0b 100644
--- a/src/cmd/link/internal/ld/ld_test.go
+++ b/src/cmd/link/internal/ld/ld_test.go
@@ -387,7 +387,7 @@ func TestRISCVTrampolines(t *testing.T) {
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
for i := 0; i < 1<<17; i++ {
- fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
+ fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
}
fmt.Fprintf(buf, "\tCALL b(SB)\n")
fmt.Fprintf(buf, "\tRET\n")
@@ -398,7 +398,7 @@ func TestRISCVTrampolines(t *testing.T) {
fmt.Fprintf(buf, "\tRET\n")
fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
for i := 0; i < 1<<17; i++ {
- fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
+ fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
}
fmt.Fprintf(buf, "\tCALL a(SB)\n")
fmt.Fprintf(buf, "\tCALL c(SB)\n")
--
cgit v1.3
From c93766007dbb9c975d2f18b7c741f4804ce911c0 Mon Sep 17 00:00:00 2001
From: Youlin Feng
Date: Wed, 29 Oct 2025 13:11:48 +0800
Subject: runtime: do not print recovered when double panic with the same value
Show the "[recovered, repanicked]" message only when it is repanicked
after recovered. For the duplicated panics that not recovered, do not
show this message.
Fixes #76099
Change-Id: I87282022ebe44c6f6efbe3239218be4a2a7b1104
Reviewed-on: https://go-review.googlesource.com/c/go/+/716020
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Michael Knyszek
Reviewed-by: Junyang Shao
Auto-Submit: Michael Pratt
---
src/runtime/crash_test.go | 9 +++++++++
src/runtime/panic.go | 2 +-
src/runtime/testdata/testprog/crash.go | 11 +++++++++++
3 files changed, 21 insertions(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go
index 2b8ca549ad..00e67aeca0 100644
--- a/src/runtime/crash_test.go
+++ b/src/runtime/crash_test.go
@@ -413,6 +413,15 @@ func TestRepanickedPanicSandwich(t *testing.T) {
}
}
+func TestDoublePanicWithSameValue(t *testing.T) {
+ output := runTestProg(t, "testprog", "DoublePanicWithSameValue")
+ want := `panic: message
+`
+ if !strings.HasPrefix(output, want) {
+ t.Fatalf("output does not start with %q:\n%s", want, output)
+ }
+}
+
func TestGoexitCrash(t *testing.T) {
// External linking brings in cgo, causing deadlock detection not working.
testenv.MustInternalLink(t, deadlockBuildTypes)
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index 3c967a2999..62affac5f9 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -739,7 +739,7 @@ func printpanics(p *_panic) {
}
print("panic: ")
printpanicval(p.arg)
- if p.repanicked {
+ if p.recovered && p.repanicked {
print(" [recovered, repanicked]")
} else if p.recovered {
print(" [recovered]")
diff --git a/src/runtime/testdata/testprog/crash.go b/src/runtime/testdata/testprog/crash.go
index 556215a71e..fcce388871 100644
--- a/src/runtime/testdata/testprog/crash.go
+++ b/src/runtime/testdata/testprog/crash.go
@@ -22,6 +22,7 @@ func init() {
register("RepanickedPanic", RepanickedPanic)
register("RepanickedMiddlePanic", RepanickedMiddlePanic)
register("RepanickedPanicSandwich", RepanickedPanicSandwich)
+ register("DoublePanicWithSameValue", DoublePanicWithSameValue)
}
func test(name string) {
@@ -189,3 +190,13 @@ func RepanickedPanicSandwich() {
panic("outer")
}()
}
+
+// Double panic with same value and not recovered.
+// See issue 76099.
+func DoublePanicWithSameValue() {
+ var e any = "message"
+ defer func() {
+ panic(e)
+ }()
+ panic(e)
+}
--
cgit v1.3
From 8806d53c106ba9d797a4383b2a49418c509a42c2 Mon Sep 17 00:00:00 2001
From: Cherry Mui
Date: Mon, 17 Nov 2025 17:27:21 -0500
Subject: cmd/link: align sections, not symbols after DWARF compress
After DWARF compression, we recompute the symbol and section
addresses. On Windows, we need to align the sections to
PEFILEALIGN. But the code actually apply the alignment to every
symbol. This works mostly fine as after compression a section
usually contains a single symbol (the compressed data). But if the
compression is not beneficial, it leaves with the original set of
symbols, which could be more than one. Applying alignment to every
symbol causing the section size too big, no longer matching the
size we computed before compression.
Fixes #76022.
Change-Id: I2246045955405997c77e54001bbb83f9ccd1ee7c
Reviewed-on: https://go-review.googlesource.com/c/go/+/721340
LUCI-TryBot-Result: Go LUCI
Reviewed-by: David Chase
---
src/cmd/link/internal/ld/dwarf.go | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
(limited to 'src')
diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go
index 31de34aff4..9bab73e7b7 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -2507,19 +2507,19 @@ func dwarfcompress(ctxt *Link) {
var prevSect *sym.Section
for _, si := range dwarfp {
for _, s := range si.syms {
- ldr.SetSymValue(s, int64(pos))
sect := ldr.SymSect(s)
if sect != prevSect {
+ if ctxt.IsWindows() {
+ pos = uint64(Rnd(int64(pos), PEFILEALIGN))
+ }
sect.Vaddr = pos
prevSect = sect
}
+ ldr.SetSymValue(s, int64(pos))
if ldr.SubSym(s) != 0 {
log.Fatalf("%s: unexpected sub-symbols", ldr.SymName(s))
}
pos += uint64(ldr.SymSize(s))
- if ctxt.IsWindows() {
- pos = uint64(Rnd(int64(pos), PEFILEALIGN))
- }
}
}
Segdwarf.Length = pos - Segdwarf.Vaddr
--
cgit v1.3
From ba634ca5c7f19105c853db5752cc0f6d3ca76e45 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 17 Nov 2025 22:40:26 -0800
Subject: cmd/compile: fold boolean NOT into branches
Gets rid of an EOR $1 instruction.
Change-Id: Ib032b0cee9ac484329c978af9b1305446f8d5dac
Reviewed-on: https://go-review.googlesource.com/c/go/+/721501
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Junyang Shao
Reviewed-by: Keith Randall
---
src/cmd/compile/internal/ssa/_gen/ARM64.rules | 2 ++
src/cmd/compile/internal/ssa/rewriteARM64.go | 31 +++++++++++++++++++++++++++
test/codegen/bool.go | 15 +++++++++++++
3 files changed, 48 insertions(+)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/_gen/ARM64.rules b/src/cmd/compile/internal/ssa/_gen/ARM64.rules
index 04f43f3137..53bb35d289 100644
--- a/src/cmd/compile/internal/ssa/_gen/ARM64.rules
+++ b/src/cmd/compile/internal/ssa/_gen/ARM64.rules
@@ -573,6 +573,8 @@
(TBNZ [0] (GreaterThanF cc) yes no) => (FGT cc yes no)
(TBNZ [0] (GreaterEqualF cc) yes no) => (FGE cc yes no)
+(TB(Z|NZ) [0] (XORconst [1] x) yes no) => (TB(NZ|Z) [0] x yes no)
+
((EQ|NE|LT|LE|GT|GE) (CMPconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TST x y) yes no)
((EQ|NE|LT|LE|GT|GE) (CMPconst [0] x:(ANDconst [c] y)) yes no) && x.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTconst [c] y) yes no)
((EQ|NE|LT|LE|GT|GE) (CMPWconst [0] z:(AND x y)) yes no) && z.Uses == 1 => ((EQ|NE|LT|LE|GT|GE) (TSTW x y) yes no)
diff --git a/src/cmd/compile/internal/ssa/rewriteARM64.go b/src/cmd/compile/internal/ssa/rewriteARM64.go
index 6137ec13a0..b3f790dbda 100644
--- a/src/cmd/compile/internal/ssa/rewriteARM64.go
+++ b/src/cmd/compile/internal/ssa/rewriteARM64.go
@@ -25321,6 +25321,37 @@ func rewriteBlockARM64(b *Block) bool {
b.resetWithControl(BlockARM64FGE, cc)
return true
}
+ // match: (TBNZ [0] (XORconst [1] x) yes no)
+ // result: (TBZ [0] x yes no)
+ for b.Controls[0].Op == OpARM64XORconst {
+ v_0 := b.Controls[0]
+ if auxIntToInt64(v_0.AuxInt) != 1 {
+ break
+ }
+ x := v_0.Args[0]
+ if auxIntToInt64(b.AuxInt) != 0 {
+ break
+ }
+ b.resetWithControl(BlockARM64TBZ, x)
+ b.AuxInt = int64ToAuxInt(0)
+ return true
+ }
+ case BlockARM64TBZ:
+ // match: (TBZ [0] (XORconst [1] x) yes no)
+ // result: (TBNZ [0] x yes no)
+ for b.Controls[0].Op == OpARM64XORconst {
+ v_0 := b.Controls[0]
+ if auxIntToInt64(v_0.AuxInt) != 1 {
+ break
+ }
+ x := v_0.Args[0]
+ if auxIntToInt64(b.AuxInt) != 0 {
+ break
+ }
+ b.resetWithControl(BlockARM64TBNZ, x)
+ b.AuxInt = int64ToAuxInt(0)
+ return true
+ }
case BlockARM64UGE:
// match: (UGE (FlagConstant [fc]) yes no)
// cond: fc.uge()
diff --git a/test/codegen/bool.go b/test/codegen/bool.go
index 37f85a45b7..8fe7a94687 100644
--- a/test/codegen/bool.go
+++ b/test/codegen/bool.go
@@ -313,3 +313,18 @@ func constantWrite(b bool, p *bool) {
*p = b
}
}
+
+func boolCompare1(p, q *bool) int {
+ // arm64:-"EOR [$]1"
+ if *p == *q {
+ return 5
+ }
+ return 7
+}
+func boolCompare2(p, q *bool) int {
+ // arm64:-"EOR [$]1"
+ if *p != *q {
+ return 5
+ }
+ return 7
+}
--
cgit v1.3
From 4d0658bb0871806a8c5551063d1ef1d205916ceb Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 17 Nov 2025 15:33:01 -0800
Subject: cmd/compile: prefer fixed registers for values
For this code:
func f() (int, int) {
return 0, 0
}
We currently generate on arm64:
MOVD ZR, R0
MOVD R0, R1
This CL changes that to
MOVD ZR, R0
MOVD ZR, R1
Probably not a big performance difference, but it makes the generated
code clearer.
A followup-ish CL from 633075 when the zero fixed register was exposed
to the register allocator.
Change-Id: I869a92817dcbbca46c900999fab538e76e10ed05
Reviewed-on: https://go-review.googlesource.com/c/go/+/721440
Reviewed-by: Keith Randall
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
---
src/cmd/compile/internal/ssa/regalloc.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/regalloc.go b/src/cmd/compile/internal/ssa/regalloc.go
index b5174acbc9..9ed8a0e86c 100644
--- a/src/cmd/compile/internal/ssa/regalloc.go
+++ b/src/cmd/compile/internal/ssa/regalloc.go
@@ -596,17 +596,18 @@ func (s *regAllocState) allocValToReg(v *Value, mask regMask, nospill bool, pos
var c *Value
if vi.regs != 0 {
// Copy from a register that v is already in.
- r2 := pickReg(vi.regs)
var current *Value
- if !s.allocatable.contains(r2) {
- current = v // v is in a fixed register
+ if vi.regs&^s.allocatable != 0 {
+ // v is in a fixed register, prefer that
+ current = v
} else {
+ r2 := pickReg(vi.regs)
if s.regs[r2].v != v {
panic("bad register state")
}
current = s.regs[r2].c
+ s.usedSinceBlockStart |= regMask(1) << r2
}
- s.usedSinceBlockStart |= regMask(1) << r2
c = s.curBlock.NewValue1(pos, OpCopy, v.Type, current)
} else if v.rematerializeable() {
// Rematerialize instead of loading from the spill location.
--
cgit v1.3
From 2cf9d4b62f167cbef01469d625dabefdd783c0e8 Mon Sep 17 00:00:00 2001
From: "Nicholas S. Husin"
Date: Tue, 18 Nov 2025 12:32:44 -0500
Subject: Revert "net/http: do not discard body content when closing it within
request handlers"
This reverts commit cb0d9980f5721715ebb73dd2e580eaa11c2ddee2.
Reason for revert: the old behavior seems to be relied on by current
users, e.g.
https://github.com/connectrpc/connect-go/blob/cb2e11fb88c9a61804043355a619c12d4a30a1a5/protocol_connect.go#L837.
For #75933
Change-Id: I996280238e5c70a8d760a0b31e3a13c6a44b8616
Reviewed-on: https://go-review.googlesource.com/c/go/+/721761
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Damien Neil
Auto-Submit: Nicholas Husin
Reviewed-by: Nicholas Husin
---
src/net/http/serve_test.go | 188 +++++++++++++++++++++------------------------
src/net/http/server.go | 31 +++-----
src/net/http/transfer.go | 109 ++++++++++++--------------
3 files changed, 143 insertions(+), 185 deletions(-)
(limited to 'src')
diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go
index af15593c35..4a16ba02af 100644
--- a/src/net/http/serve_test.go
+++ b/src/net/http/serve_test.go
@@ -2150,137 +2150,118 @@ func TestServerUnreadRequestBodyLarge(t *testing.T) {
}
}
-type bodyDiscardTest struct {
+type handlerBodyCloseTest struct {
bodySize int
bodyChunked bool
reqConnClose bool
- shouldDiscardBody bool // should the handler discard body after it exits?
+ wantEOFSearch bool // should Handler's Body.Close do Reads, looking for EOF?
+ wantNextReq bool // should it find the next request on the same conn?
}
-func (t bodyDiscardTest) connectionHeader() string {
+func (t handlerBodyCloseTest) connectionHeader() string {
if t.reqConnClose {
return "Connection: close\r\n"
}
return ""
}
-var bodyDiscardTests = [...]bodyDiscardTest{
- // Have:
- // - Small body.
- // - Content-Length defined.
- // Should:
- // - Discard remaining body.
+var handlerBodyCloseTests = [...]handlerBodyCloseTest{
+ // Small enough to slurp past to the next request +
+ // has Content-Length.
0: {
- bodySize: 20 << 10,
- bodyChunked: false,
- reqConnClose: false,
- shouldDiscardBody: true,
+ bodySize: 20 << 10,
+ bodyChunked: false,
+ reqConnClose: false,
+ wantEOFSearch: true,
+ wantNextReq: true,
},
- // Have:
- // - Small body.
- // - Chunked (no Content-Length defined).
- // Should:
- // - Discard remaining body.
+ // Small enough to slurp past to the next request +
+ // is chunked.
1: {
- bodySize: 20 << 10,
- bodyChunked: true,
- reqConnClose: false,
- shouldDiscardBody: true,
+ bodySize: 20 << 10,
+ bodyChunked: true,
+ reqConnClose: false,
+ wantEOFSearch: true,
+ wantNextReq: true,
},
- // Have:
- // - Small body.
- // - Content-Length defined.
- // - Connection: close.
- // Should:
- // - Not discard remaining body (no point as Connection: close).
+ // Small enough to slurp past to the next request +
+ // has Content-Length +
+ // declares Connection: close (so pointless to read more).
2: {
- bodySize: 20 << 10,
- bodyChunked: false,
- reqConnClose: true,
- shouldDiscardBody: false,
+ bodySize: 20 << 10,
+ bodyChunked: false,
+ reqConnClose: true,
+ wantEOFSearch: false,
+ wantNextReq: false,
},
- // Have:
- // - Small body.
- // - Chunked (no Content-Length defined).
- // - Connection: close.
- // Should:
- // - Discard remaining body (chunked, so it might have trailers).
- //
- // TODO: maybe skip this if no trailers were declared in the headers.
+ // Small enough to slurp past to the next request +
+ // declares Connection: close,
+ // but chunked, so it might have trailers.
+ // TODO: maybe skip this search if no trailers were declared
+ // in the headers.
3: {
- bodySize: 20 << 10,
- bodyChunked: true,
- reqConnClose: true,
- shouldDiscardBody: true,
+ bodySize: 20 << 10,
+ bodyChunked: true,
+ reqConnClose: true,
+ wantEOFSearch: true,
+ wantNextReq: false,
},
- // Have:
- // - Large body.
- // - Content-Length defined.
- // Should:
- // - Not discard remaining body (we know it is too large from Content-Length).
+ // Big with Content-Length, so give up immediately if we know it's too big.
4: {
- bodySize: 1 << 20,
- bodyChunked: false,
- reqConnClose: false,
- shouldDiscardBody: false,
+ bodySize: 1 << 20,
+ bodyChunked: false, // has a Content-Length
+ reqConnClose: false,
+ wantEOFSearch: false,
+ wantNextReq: false,
},
- // Have:
- // - Large body.
- // - Chunked (no Content-Length defined).
- // Should:
- // - Discard remaining body (chunked, so we try up to a limit before giving up).
+ // Big chunked, so read a bit before giving up.
5: {
- bodySize: 1 << 20,
- bodyChunked: true,
- reqConnClose: false,
- shouldDiscardBody: true,
+ bodySize: 1 << 20,
+ bodyChunked: true,
+ reqConnClose: false,
+ wantEOFSearch: true,
+ wantNextReq: false,
},
- // Have:
- // - Large body.
- // - Content-Length defined.
- // - Connection: close.
- // Should:
- // - Not discard remaining body (Connection: Close, and Content-Length is too large).
+ // Big with Connection: close, but chunked, so search for trailers.
+ // TODO: maybe skip this search if no trailers were declared
+ // in the headers.
6: {
- bodySize: 1 << 20,
- bodyChunked: false,
- reqConnClose: true,
- shouldDiscardBody: false,
+ bodySize: 1 << 20,
+ bodyChunked: true,
+ reqConnClose: true,
+ wantEOFSearch: true,
+ wantNextReq: false,
},
- // Have:
- // - Large body.
- // - Chunked (no Content-Length defined).
- // - Connection: close.
- // Should:
- // - Discard remaining body (chunked, so it might have trailers).
- //
- // TODO: maybe skip this if no trailers were declared in the headers.
+
+ // Big with Connection: close, so don't do any reads on Close.
+ // With Content-Length.
7: {
- bodySize: 1 << 20,
- bodyChunked: true,
- reqConnClose: true,
- shouldDiscardBody: true,
+ bodySize: 1 << 20,
+ bodyChunked: false,
+ reqConnClose: true,
+ wantEOFSearch: false,
+ wantNextReq: false,
},
}
-func TestBodyDiscard(t *testing.T) {
+func TestHandlerBodyClose(t *testing.T) {
setParallel(t)
if testing.Short() && testenv.Builder() == "" {
t.Skip("skipping in -short mode")
}
- for i, tt := range bodyDiscardTests {
- testBodyDiscard(t, i, tt)
+ for i, tt := range handlerBodyCloseTests {
+ testHandlerBodyClose(t, i, tt)
}
}
-func testBodyDiscard(t *testing.T, i int, tt bodyDiscardTest) {
+func testHandlerBodyClose(t *testing.T, i int, tt handlerBodyCloseTest) {
conn := new(testConn)
body := strings.Repeat("x", tt.bodySize)
if tt.bodyChunked {
@@ -2294,12 +2275,12 @@ func testBodyDiscard(t *testing.T, i int, tt bodyDiscardTest) {
cw.Close()
conn.readBuf.WriteString("\r\n")
} else {
- conn.readBuf.Write(fmt.Appendf(nil,
+ conn.readBuf.Write([]byte(fmt.Sprintf(
"POST / HTTP/1.1\r\n"+
"Host: test\r\n"+
tt.connectionHeader()+
"Content-Length: %d\r\n"+
- "\r\n", len(body)))
+ "\r\n", len(body))))
conn.readBuf.Write([]byte(body))
}
if !tt.reqConnClose {
@@ -2314,23 +2295,26 @@ func testBodyDiscard(t *testing.T, i int, tt bodyDiscardTest) {
}
ls := &oneConnListener{conn}
- var initialSize, closedSize, exitSize int
+ var numReqs int
+ var size0, size1 int
go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) {
- initialSize = readBufLen()
- req.Body.Close()
- closedSize = readBufLen()
+ numReqs++
+ if numReqs == 1 {
+ size0 = readBufLen()
+ req.Body.Close()
+ size1 = readBufLen()
+ }
}))
<-conn.closec
- exitSize = readBufLen()
-
- if initialSize != closedSize {
- t.Errorf("%d. Close() within request handler should be a no-op, but body size went from %d to %d", i, initialSize, closedSize)
+ if numReqs < 1 || numReqs > 2 {
+ t.Fatalf("%d. bug in test. unexpected number of requests = %d", i, numReqs)
}
- if tt.shouldDiscardBody && closedSize <= exitSize {
- t.Errorf("%d. want body content to be discarded upon request handler exit, but size went from %d to %d", i, closedSize, exitSize)
+ didSearch := size0 != size1
+ if didSearch != tt.wantEOFSearch {
+ t.Errorf("%d. did EOF search = %v; want %v (size went from %d to %d)", i, didSearch, !didSearch, size0, size1)
}
- if !tt.shouldDiscardBody && closedSize != exitSize {
- t.Errorf("%d. want body content to not be discarded upon request handler exit, but size went from %d to %d", i, closedSize, exitSize)
+ if tt.wantNextReq && numReqs != 2 {
+ t.Errorf("%d. numReq = %d; want 2", i, numReqs)
}
}
diff --git a/src/net/http/server.go b/src/net/http/server.go
index 5d8e576f71..02554d1a20 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -1077,6 +1077,9 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
req.ctx = ctx
req.RemoteAddr = c.remoteAddr
req.TLS = c.tlsState
+ if body, ok := req.Body.(*body); ok {
+ body.doEarlyClose = true
+ }
// Adjust the read deadline if necessary.
if !hdrDeadline.Equal(wholeReqDeadline) {
@@ -1708,11 +1711,9 @@ func (w *response) finishRequest() {
w.conn.r.abortPendingRead()
- // Try to discard the body (regardless of w.closeAfterReply), so we can
- // potentially reuse it in the same connection.
- if b, ok := w.reqBody.(*body); ok {
- b.tryDiscardBody()
- }
+ // Close the body (regardless of w.closeAfterReply) so we can
+ // re-use its bufio.Reader later safely.
+ w.reqBody.Close()
if w.req.MultipartForm != nil {
w.req.MultipartForm.RemoveAll()
@@ -1740,16 +1741,16 @@ func (w *response) shouldReuseConnection() bool {
return false
}
- if w.didIncompleteDiscard() {
+ if w.closedRequestBodyEarly() {
return false
}
return true
}
-func (w *response) didIncompleteDiscard() bool {
+func (w *response) closedRequestBodyEarly() bool {
body, ok := w.req.Body.(*body)
- return ok && body.didIncompleteDiscard()
+ return ok && body.didEarlyClose()
}
func (w *response) Flush() {
@@ -2105,18 +2106,6 @@ func (c *conn) serve(ctx context.Context) {
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
inFlightResponse = w
- // Ensure that Close() invocations within request handlers do not
- // discard the body.
- if b, ok := w.reqBody.(*body); ok {
- b.mu.Lock()
- b.inRequestHandler = true
- b.mu.Unlock()
- defer func() {
- b.mu.Lock()
- b.inRequestHandler = false
- b.mu.Unlock()
- }()
- }
serverHandler{c.server}.ServeHTTP(w, w.req)
inFlightResponse = nil
w.cancelCtx()
@@ -2127,7 +2116,7 @@ func (c *conn) serve(ctx context.Context) {
w.finishRequest()
c.rwc.SetWriteDeadline(time.Time{})
if !w.shouldReuseConnection() {
- if w.requestBodyLimitHit || w.didIncompleteDiscard() {
+ if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go
index 9b972aaa44..675551287f 100644
--- a/src/net/http/transfer.go
+++ b/src/net/http/transfer.go
@@ -809,17 +809,17 @@ func fixTrailer(header Header, chunked bool) (Header, error) {
// Close ensures that the body has been fully read
// and then reads the trailer if necessary.
type body struct {
- src io.Reader
- hdr any // non-nil (Response or Request) value means read trailer
- r *bufio.Reader // underlying wire-format reader for the trailer
- closing bool // is the connection to be closed after reading body?
-
- mu sync.Mutex // guards following, and calls to Read and Close
- sawEOF bool
- closed bool
- incompleteDiscard bool // if true, we failed to discard the body content completely
- inRequestHandler bool // used so Close() calls from within request handlers do not discard body
- onHitEOF func() // if non-nil, func to call when EOF is Read
+ src io.Reader
+ hdr any // non-nil (Response or Request) value means read trailer
+ r *bufio.Reader // underlying wire-format reader for the trailer
+ closing bool // is the connection to be closed after reading body?
+ doEarlyClose bool // whether Close should stop early
+
+ mu sync.Mutex // guards following, and calls to Read and Close
+ sawEOF bool
+ closed bool
+ earlyClose bool // Close called and we didn't read to the end of src
+ onHitEOF func() // if non-nil, func to call when EOF is Read
}
// ErrBodyReadAfterClose is returned when reading a [Request] or [Response]
@@ -968,69 +968,51 @@ func (b *body) unreadDataSizeLocked() int64 {
return -1
}
-// shouldDiscardBodyLocked determines whether a body needs to discard its
-// content or not.
-// b.mu must be held.
-func (b *body) shouldDiscardBodyLocked() bool {
- // Already saw EOF. No point in discarding the body.
- if b.sawEOF {
- return false
- }
- // No trailer and closing the connection next. No point in discarding the
- // body since it will not be reusable.
- if b.hdr == nil && b.closing {
- return false
- }
- return true
-}
-
-// tryDiscardBody attempts to discard the body content. If the body cannot be
-// discarded completely, b.incompleteDiscard will be set to true.
-func (b *body) tryDiscardBody() {
- b.mu.Lock()
- defer b.mu.Unlock()
- if !b.shouldDiscardBodyLocked() {
- return
- }
- // Read up to maxPostHandlerReadBytes bytes of the body, looking for EOF
- // (and trailers), so we can re-use this connection.
- if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes {
- // There was a declared Content-Length, and we have more bytes
- // remaining than our maxPostHandlerReadBytes tolerance. So, give up.
- b.incompleteDiscard = true
- return
- }
- // Consume the body, which will also lead to us reading the trailer headers
- // after the body, if present.
- n, err := io.CopyN(io.Discard, bodyLocked{b}, maxPostHandlerReadBytes)
- if err == io.EOF {
- err = nil
- }
- if n == maxPostHandlerReadBytes || err != nil {
- b.incompleteDiscard = true
- }
-}
-
func (b *body) Close() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.closed {
return nil
}
- b.closed = true
- if !b.shouldDiscardBodyLocked() || b.inRequestHandler {
- return nil
+ var err error
+ switch {
+ case b.sawEOF:
+ // Already saw EOF, so no need going to look for it.
+ case b.hdr == nil && b.closing:
+ // no trailer and closing the connection next.
+ // no point in reading to EOF.
+ case b.doEarlyClose:
+ // Read up to maxPostHandlerReadBytes bytes of the body, looking
+ // for EOF (and trailers), so we can re-use this connection.
+ if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > maxPostHandlerReadBytes {
+ // There was a declared Content-Length, and we have more bytes remaining
+ // than our maxPostHandlerReadBytes tolerance. So, give up.
+ b.earlyClose = true
+ } else {
+ var n int64
+ // Consume the body, or, which will also lead to us reading
+ // the trailer headers after the body, if present.
+ n, err = io.CopyN(io.Discard, bodyLocked{b}, maxPostHandlerReadBytes)
+ if err == io.EOF {
+ err = nil
+ }
+ if n == maxPostHandlerReadBytes {
+ b.earlyClose = true
+ }
+ }
+ default:
+ // Fully consume the body, which will also lead to us reading
+ // the trailer headers after the body, if present.
+ _, err = io.Copy(io.Discard, bodyLocked{b})
}
- // Fully consume the body, which will also lead to us reading
- // the trailer headers after the body, if present.
- _, err := io.Copy(io.Discard, bodyLocked{b})
+ b.closed = true
return err
}
-func (b *body) didIncompleteDiscard() bool {
+func (b *body) didEarlyClose() bool {
b.mu.Lock()
defer b.mu.Unlock()
- return b.incompleteDiscard
+ return b.earlyClose
}
// bodyRemains reports whether future Read calls might
@@ -1054,6 +1036,9 @@ type bodyLocked struct {
}
func (bl bodyLocked) Read(p []byte) (n int, err error) {
+ if bl.b.closed {
+ return 0, ErrBodyReadAfterClose
+ }
return bl.b.readLocked(p)
}
--
cgit v1.3
From e912618bd2de2121d6c9fed3473b5e0a47da138c Mon Sep 17 00:00:00 2001
From: Austin Clements
Date: Tue, 10 Jun 2025 19:19:08 -0400
Subject: runtime: add hexdumper
Currently, we have a simple hexdumpWords facility for debugging. It's
useful but pretty limited.
This CL adds a much more configurable and capable "hexdumper". It can
be configured for any word size (including bytes), handles unaligned
data, includes an ASCII dump, and accepts data in multiple slices. It
also has a much nicer "mark" facility for annotating the hexdump that
isn't limited to a single character per word.
We use this to improve our existing hexdumps, particularly the new
mark facility. The next CL will integrate hexdumps into debuglog,
which will make use of several other new capabilities.
Also this adds an actual test.
The output looks like:
7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef
000000c00006ef70: 03000000 00000000 ........
000000c00006ef80: 00000000 0053da80 000000c0 000bc380 ..S.............
^
000000c00006ef90: 00000000 0053dac0 000000c0 000bc380 ..S.............
^
000000c00006efa0: 000000c0 0006ef90 000000c0 0006ef80 ................
000000c00006efb0: 000000c0 0006efd0 00000000 0053eb65 ........e.S.....
^
000000c00006efc0: 000000c0 000bc380 00000000 009aaae8 ................
000000c00006efd0: 00000000 00000000 00000000 00496b01 .........kI.....
^
000000c00006efe0: 00000000 00000000 00000000 00000000 ................
000000c00006eff0: 00000000 00000000 ........
The header gives column labels, indicating the order of bytes within
the following words. The addresses on the left are always 16-byte
aligned so it's easy to combine that address with the column header to
determine the full address of a byte. Annotations are no longer
interleaved with the data, so the data stays in nicely aligned
columns. The annotations are also now much more flexible, including
support for multiple annotations on the same word (not shown).
Change-Id: I27e83800a1f6a7bdd3cc2c59614661a810a57d4d
Reviewed-on: https://go-review.googlesource.com/c/go/+/681375
Reviewed-by: Michael Pratt
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Austin Clements
---
src/runtime/export_test.go | 33 ++++++
src/runtime/hexdump.go | 269 ++++++++++++++++++++++++++++++++++++++++++++
src/runtime/hexdump_test.go | 151 +++++++++++++++++++++++++
src/runtime/mgcmark.go | 15 ++-
src/runtime/mgcsweep.go | 2 +-
src/runtime/print.go | 41 -------
src/runtime/traceback.go | 21 ++--
7 files changed, 475 insertions(+), 57 deletions(-)
create mode 100644 src/runtime/hexdump.go
create mode 100644 src/runtime/hexdump_test.go
(limited to 'src')
diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go
index 8438603b9e..2db8add7e4 100644
--- a/src/runtime/export_test.go
+++ b/src/runtime/export_test.go
@@ -2029,3 +2029,36 @@ func (head *ListHeadManual) Pop() unsafe.Pointer {
func (head *ListHeadManual) Remove(p unsafe.Pointer) {
head.l.remove(p)
}
+
+func Hexdumper(base uintptr, wordBytes int, mark func(addr uintptr, start func()), data ...[]byte) string {
+ buf := make([]byte, 0, 2048)
+ getg().writebuf = buf
+ h := hexdumper{addr: base, addrBytes: 4, wordBytes: uint8(wordBytes)}
+ if mark != nil {
+ h.mark = func(addr uintptr, m hexdumpMarker) {
+ mark(addr, m.start)
+ }
+ }
+ for _, d := range data {
+ h.write(d)
+ }
+ h.close()
+ n := len(getg().writebuf)
+ getg().writebuf = nil
+ if n == cap(buf) {
+ panic("Hexdumper buf too small")
+ }
+ return string(buf[:n])
+}
+
+func HexdumpWords(p, bytes uintptr) string {
+ buf := make([]byte, 0, 2048)
+ getg().writebuf = buf
+ hexdumpWords(p, bytes, nil)
+ n := len(getg().writebuf)
+ getg().writebuf = nil
+ if n == cap(buf) {
+ panic("HexdumpWords buf too small")
+ }
+ return string(buf[:n])
+}
diff --git a/src/runtime/hexdump.go b/src/runtime/hexdump.go
new file mode 100644
index 0000000000..0d7dbb540b
--- /dev/null
+++ b/src/runtime/hexdump.go
@@ -0,0 +1,269 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime
+
+import (
+ "internal/goarch"
+ "unsafe"
+)
+
+// hexdumpWords prints a word-oriented hex dump of [p, p+len).
+//
+// If mark != nil, it will be passed to hexdumper.mark.
+func hexdumpWords(p, len uintptr, mark func(uintptr, hexdumpMarker)) {
+ printlock()
+
+ // Provide a default annotation
+ symMark := func(u uintptr, hm hexdumpMarker) {
+ if mark != nil {
+ mark(u, hm)
+ }
+
+ // Can we symbolize this value?
+ val := *(*uintptr)(unsafe.Pointer(u))
+ fn := findfunc(val)
+ if fn.valid() {
+ hm.start()
+ print("<", funcname(fn), "+", hex(val-fn.entry()), ">\n")
+ }
+ }
+
+ h := hexdumper{addr: p, mark: symMark}
+ h.write(unsafe.Slice((*byte)(unsafe.Pointer(p)), len))
+ h.close()
+ printunlock()
+}
+
+// hexdumper is a Swiss-army knife hex dumper.
+//
+// To use, optionally set addr and wordBytes, then call write repeatedly,
+// followed by close.
+type hexdumper struct {
+ // addr is the address to print for the first byte of data.
+ addr uintptr
+
+ // addrBytes is the number of bytes of addr to print. If this is 0, it
+ // defaults to goarch.PtrSize.
+ addrBytes uint8
+
+ // wordBytes is the number of bytes in a word. If wordBytes is 1, this
+ // prints a byte-oriented dump. If it's > 1, this interprets the data as a
+ // sequence of words of the given size. If it's 0, it's treated as
+ // goarch.PtrSize.
+ wordBytes uint8
+
+ // mark is an optional function that can annotate values in the hex dump.
+ //
+ // If non-nil, it is called with the address of every complete, aligned word
+ // in the hex dump.
+ //
+ // If it decides to print an annotation, it must first call m.start(), then
+ // print the annotation, followed by a new line.
+ mark func(addr uintptr, m hexdumpMarker)
+
+ // Below here is state
+
+ ready int8 // 0=need to init state; 1=need to print header; 2=ready
+
+ // dataBuf accumulates a line at a time of data, in case it's split across
+ // buffers.
+ dataBuf [16]byte
+ dataPos uint8
+ dataSkip uint8 // Skip first n bytes of buf on first line
+
+ // toPos maps from byte offset in data to a visual offset in the printed line.
+ toPos [16]byte
+}
+
+type hexdumpMarker struct {
+ chars int
+}
+
+func (h *hexdumper) write(data []byte) {
+ if h.ready == 0 {
+ h.init()
+ }
+
+ // Handle leading data
+ if h.dataPos > 0 {
+ n := copy(h.dataBuf[h.dataPos:], data)
+ h.dataPos += uint8(n)
+ data = data[n:]
+ if h.dataPos < uint8(len(h.dataBuf)) {
+ return
+ }
+ h.flushLine(h.dataBuf[:])
+ h.dataPos = 0
+ }
+
+ // Handle full lines in data
+ for len(data) >= len(h.dataBuf) {
+ h.flushLine(data[:len(h.dataBuf)])
+ data = data[len(h.dataBuf):]
+ }
+
+ // Handle trailing data
+ h.dataPos = uint8(copy(h.dataBuf[:], data))
+}
+
+func (h *hexdumper) close() {
+ if h.dataPos > 0 {
+ h.flushLine(h.dataBuf[:h.dataPos])
+ }
+}
+
+func (h *hexdumper) init() {
+ const bytesPerLine = len(h.dataBuf)
+
+ if h.addrBytes == 0 {
+ h.addrBytes = goarch.PtrSize
+ } else if h.addrBytes < 0 || h.addrBytes > goarch.PtrSize {
+ throw("invalid addrBytes")
+ }
+
+ if h.wordBytes == 0 {
+ h.wordBytes = goarch.PtrSize
+ }
+ wb := int(h.wordBytes)
+ if wb < 0 || wb >= bytesPerLine || wb&(wb-1) != 0 {
+ throw("invalid wordBytes")
+ }
+
+ // Construct position mapping.
+ for i := range h.toPos {
+ // First, calculate the "field" within the line, applying byte swizzling.
+ field := 0
+ if goarch.BigEndian {
+ field = i
+ } else {
+ field = i ^ int(wb-1)
+ }
+ // Translate this field into a visual offset.
+ // "00112233 44556677 8899AABB CCDDEEFF"
+ h.toPos[i] = byte(field*2 + field/4 + field/8)
+ }
+
+ // The first line may need to skip some fields to get to alignment.
+ // Round down the starting address.
+ nAddr := h.addr &^ uintptr(bytesPerLine-1)
+ // Skip bytes to get to alignment.
+ h.dataPos = uint8(h.addr - nAddr)
+ h.dataSkip = uint8(h.addr - nAddr)
+ h.addr = nAddr
+
+ // We're ready to print the header.
+ h.ready = 1
+}
+
+func (h *hexdumper) flushLine(data []byte) {
+ const bytesPerLine = len(h.dataBuf)
+
+ const maxAddrChars = 2 * goarch.PtrSize
+ const addrSep = ": "
+ dataStart := int(2*h.addrBytes) + len(addrSep)
+ // dataChars uses the same formula to toPos above. We calculate it with the
+ // "last field", then add the size of the last field.
+ const dataChars = (bytesPerLine-1)*2 + (bytesPerLine-1)/4 + (bytesPerLine-1)/8 + 2
+ const asciiSep = " "
+ asciiStart := dataStart + dataChars + len(asciiSep)
+ const asciiChars = bytesPerLine
+ nlPos := asciiStart + asciiChars
+
+ var lineBuf [maxAddrChars + len(addrSep) + dataChars + len(asciiSep) + asciiChars + 1]byte
+ clear := func() {
+ for i := range lineBuf {
+ lineBuf[i] = ' '
+ }
+ }
+ clear()
+
+ if h.ready == 1 {
+ // Print column offsets header.
+ for offset, pos := range h.toPos {
+ h.fmtHex(lineBuf[dataStart+int(pos+1):][:1], uint64(offset))
+ }
+ // Print ASCII offsets.
+ for offset := range asciiChars {
+ h.fmtHex(lineBuf[asciiStart+offset:][:1], uint64(offset))
+ }
+ lineBuf[nlPos] = '\n'
+ gwrite(lineBuf[:nlPos+1])
+ clear()
+ h.ready = 2
+ }
+
+ // Format address.
+ h.fmtHex(lineBuf[:2*h.addrBytes], uint64(h.addr))
+ copy(lineBuf[2*h.addrBytes:], addrSep)
+ // Format data in hex and ASCII.
+ for offset, b := range data {
+ if offset < int(h.dataSkip) {
+ continue
+ }
+
+ pos := h.toPos[offset]
+ h.fmtHex(lineBuf[dataStart+int(pos):][:2], uint64(b))
+
+ copy(lineBuf[dataStart+dataChars:], asciiSep)
+ ascii := uint8('.')
+ if b >= ' ' && b <= '~' {
+ ascii = b
+ }
+ lineBuf[asciiStart+offset] = ascii
+ }
+ // Trim buffer.
+ end := asciiStart + len(data)
+ lineBuf[end] = '\n'
+ buf := lineBuf[:end+1]
+
+ // Print.
+ gwrite(buf)
+
+ // Print marks.
+ if h.mark != nil {
+ clear()
+ for offset := 0; offset+int(h.wordBytes) <= len(data); offset += int(h.wordBytes) {
+ if offset < int(h.dataSkip) {
+ continue
+ }
+ addr := h.addr + uintptr(offset)
+ // Find the position of the left edge of this word
+ caret := dataStart + int(min(h.toPos[offset], h.toPos[offset+int(h.wordBytes)-1]))
+ h.mark(addr, hexdumpMarker{caret})
+ }
+ }
+
+ h.addr += uintptr(bytesPerLine)
+ h.dataPos = 0
+ h.dataSkip = 0
+}
+
+// fmtHex formats v in base 16 into buf. It fills all of buf. If buf is too
+// small to represent v, it the output will start with '*'.
+func (h *hexdumper) fmtHex(buf []byte, v uint64) {
+ const dig = "0123456789abcdef"
+ i := len(buf) - 1
+ for ; i >= 0; i-- {
+ buf[i] = dig[v%16]
+ v /= 16
+ }
+ if v != 0 {
+ // Indicate that we couldn't fit the whole number.
+ buf[0] = '*'
+ }
+}
+
+func (m hexdumpMarker) start() {
+ var spaces [64]byte
+ for i := range spaces {
+ spaces[i] = ' '
+ }
+ for m.chars > len(spaces) {
+ gwrite(spaces[:])
+ m.chars -= len(spaces)
+ }
+ gwrite(spaces[:m.chars])
+ print("^ ")
+}
diff --git a/src/runtime/hexdump_test.go b/src/runtime/hexdump_test.go
new file mode 100644
index 0000000000..cc44e48e4b
--- /dev/null
+++ b/src/runtime/hexdump_test.go
@@ -0,0 +1,151 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package runtime_test
+
+import (
+ "fmt"
+ "internal/abi"
+ "internal/goarch"
+ "runtime"
+ "slices"
+ "strings"
+ "testing"
+ "unsafe"
+)
+
+func TestHexdumper(t *testing.T) {
+ check := func(label, got, want string) {
+ got = strings.TrimRight(got, "\n")
+ want = strings.TrimPrefix(want, "\n")
+ want = strings.TrimRight(want, "\n")
+ if got != want {
+ t.Errorf("%s: got\n%s\nwant\n%s", label, got, want)
+ }
+ }
+
+ data := make([]byte, 32)
+ for i := range data {
+ data[i] = 0x10 + byte(i)
+ }
+
+ check("basic", runtime.Hexdumper(0, 1, nil, data), `
+ 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
+00000000: 10111213 14151617 18191a1b 1c1d1e1f ................
+00000010: 20212223 24252627 28292a2b 2c2d2e2f !"#$%&'()*+,-./`)
+
+ if !goarch.BigEndian {
+ // Different word sizes
+ check("word=4", runtime.Hexdumper(0, 4, nil, data), `
+ 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef
+00000000: 13121110 17161514 1b1a1918 1f1e1d1c ................
+00000010: 23222120 27262524 2b2a2928 2f2e2d2c !"#$%&'()*+,-./`)
+ check("word=8", runtime.Hexdumper(0, 8, nil, data), `
+ 7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef
+00000000: 17161514 13121110 1f1e1d1c 1b1a1918 ................
+00000010: 27262524 23222120 2f2e2d2c 2b2a2928 !"#$%&'()*+,-./`)
+ }
+
+ // Starting offset
+ check("offset=1", runtime.Hexdumper(1, 1, nil, data), `
+ 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
+00000000: 101112 13141516 1718191a 1b1c1d1e ...............
+00000010: 1f202122 23242526 2728292a 2b2c2d2e . !"#$%&'()*+,-.
+00000020: 2f /`)
+ if !goarch.BigEndian {
+ // ... combined with a word size
+ check("offset=1 and word=4", runtime.Hexdumper(1, 4, nil, data), `
+ 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef
+00000000: 121110 16151413 1a191817 1e1d1c1b ...............
+00000010: 2221201f 26252423 2a292827 2e2d2c2b . !"#$%&'()*+,-.
+00000020: 2f /`)
+ }
+
+ // Partial data full of annoying boundaries.
+ partials := make([][]byte, 0)
+ for i := 0; i < len(data); i += 2 {
+ partials = append(partials, data[i:i+2])
+ }
+ check("partials", runtime.Hexdumper(1, 1, nil, partials...), `
+ 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
+00000000: 101112 13141516 1718191a 1b1c1d1e ...............
+00000010: 1f202122 23242526 2728292a 2b2c2d2e . !"#$%&'()*+,-.
+00000020: 2f /`)
+
+ // Marks.
+ check("marks", runtime.Hexdumper(0, 1, func(addr uintptr, start func()) {
+ if addr%7 == 0 {
+ start()
+ println("mark")
+ }
+ }, data), `
+ 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
+00000000: 10111213 14151617 18191a1b 1c1d1e1f ................
+ ^ mark
+ ^ mark
+ ^ mark
+00000010: 20212223 24252627 28292a2b 2c2d2e2f !"#$%&'()*+,-./
+ ^ mark
+ ^ mark`)
+ if !goarch.BigEndian {
+ check("marks and word=4", runtime.Hexdumper(0, 4, func(addr uintptr, start func()) {
+ if addr%7 == 0 {
+ start()
+ println("mark")
+ }
+ }, data), `
+ 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef
+00000000: 13121110 17161514 1b1a1918 1f1e1d1c ................
+ ^ mark
+00000010: 23222120 27262524 2b2a2928 2f2e2d2c !"#$%&'()*+,-./
+ ^ mark`)
+ }
+}
+
+func TestHexdumpWords(t *testing.T) {
+ if goarch.BigEndian || goarch.PtrSize != 8 {
+ // We could support these, but it's kind of a pain.
+ t.Skip("requires 64-bit little endian")
+ }
+
+ // Most of this is in hexdumper. Here we just test the symbolizer.
+
+ pc := abi.FuncPCABIInternal(TestHexdumpWords)
+ pcs := slices.Repeat([]uintptr{pc}, 3)
+
+ // Make sure pcs doesn't move around on us.
+ var p runtime.Pinner
+ defer p.Unpin()
+ p.Pin(&pcs[0])
+ // Get a 16 byte, 16-byte-aligned chunk of pcs so the hexdump is simple.
+ start := uintptr(unsafe.Pointer(&pcs[0]))
+ start = (start + 15) &^ uintptr(15)
+
+ // Do the hex dump.
+ got := runtime.HexdumpWords(start, 16)
+
+ // Construct the expected output.
+ pcStr := fmt.Sprintf("%016x", pc)
+ pcStr = pcStr[:8] + " " + pcStr[8:] // Add middle space
+ ascii := make([]byte, 8)
+ for i := range ascii {
+ b := byte(pc >> (8 * i))
+ if b >= ' ' && b <= '~' {
+ ascii[i] = b
+ } else {
+ ascii[i] = '.'
+ }
+ }
+ want := fmt.Sprintf(`
+ 7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef
+%016x: %s %s %s%s
+ ^
+ ^
+`, start, pcStr, pcStr, ascii, ascii)
+ want = strings.TrimPrefix(want, "\n")
+
+ if got != want {
+ t.Errorf("got\n%s\nwant\n%s", got, want)
+ }
+}
diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go
index c9234c5084..714b9a51df 100644
--- a/src/runtime/mgcmark.go
+++ b/src/runtime/mgcmark.go
@@ -1524,29 +1524,32 @@ func scanConservative(b, n uintptr, ptrmask *uint8, gcw *gcWork, state *stackSca
if debugScanConservative {
printlock()
print("conservatively scanning [", hex(b), ",", hex(b+n), ")\n")
- hexdumpWords(b, b+n, func(p uintptr) byte {
+ hexdumpWords(b, n, func(p uintptr, m hexdumpMarker) {
if ptrmask != nil {
word := (p - b) / goarch.PtrSize
bits := *addb(ptrmask, word/8)
if (bits>>(word%8))&1 == 0 {
- return '$'
+ return
}
}
val := *(*uintptr)(unsafe.Pointer(p))
if state != nil && state.stack.lo <= val && val < state.stack.hi {
- return '@'
+ m.start()
+ println("ptr to stack")
+ return
}
span := spanOfHeap(val)
if span == nil {
- return ' '
+ return
}
idx := span.objIndex(val)
if span.isFreeOrNewlyAllocated(idx) {
- return ' '
+ return
}
- return '*'
+ m.start()
+ println("ptr to heap")
})
printunlock()
}
diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go
index c3d6afb90a..4eecb1cfd9 100644
--- a/src/runtime/mgcsweep.go
+++ b/src/runtime/mgcsweep.go
@@ -885,7 +885,7 @@ func (s *mspan) reportZombies() {
if length > 1024 {
length = 1024
}
- hexdumpWords(addr, addr+length, nil)
+ hexdumpWords(addr, length, nil)
}
mbits.advance()
abits.advance()
diff --git a/src/runtime/print.go b/src/runtime/print.go
index c01db9d7f9..d2733fb266 100644
--- a/src/runtime/print.go
+++ b/src/runtime/print.go
@@ -5,7 +5,6 @@
package runtime
import (
- "internal/goarch"
"internal/strconv"
"unsafe"
)
@@ -212,43 +211,3 @@ func printeface(e eface) {
func printiface(i iface) {
print("(", i.tab, ",", i.data, ")")
}
-
-// hexdumpWords prints a word-oriented hex dump of [p, end).
-//
-// If mark != nil, it will be called with each printed word's address
-// and should return a character mark to appear just before that
-// word's value. It can return 0 to indicate no mark.
-func hexdumpWords(p, end uintptr, mark func(uintptr) byte) {
- printlock()
- var markbuf [1]byte
- markbuf[0] = ' '
- minhexdigits = int(unsafe.Sizeof(uintptr(0)) * 2)
- for i := uintptr(0); p+i < end; i += goarch.PtrSize {
- if i%16 == 0 {
- if i != 0 {
- println()
- }
- print(hex(p+i), ": ")
- }
-
- if mark != nil {
- markbuf[0] = mark(p + i)
- if markbuf[0] == 0 {
- markbuf[0] = ' '
- }
- }
- gwrite(markbuf[:])
- val := *(*uintptr)(unsafe.Pointer(p + i))
- print(hex(val))
- print(" ")
-
- // Can we symbolize val?
- fn := findfunc(val)
- if fn.valid() {
- print("<", funcname(fn), "+", hex(val-fn.entry()), "> ")
- }
- }
- minhexdigits = 0
- println()
- printunlock()
-}
diff --git a/src/runtime/traceback.go b/src/runtime/traceback.go
index 6649f72471..74aaeba876 100644
--- a/src/runtime/traceback.go
+++ b/src/runtime/traceback.go
@@ -1366,16 +1366,19 @@ func tracebackHexdump(stk stack, frame *stkframe, bad uintptr) {
// Print the hex dump.
print("stack: frame={sp:", hex(frame.sp), ", fp:", hex(frame.fp), "} stack=[", hex(stk.lo), ",", hex(stk.hi), ")\n")
- hexdumpWords(lo, hi, func(p uintptr) byte {
- switch p {
- case frame.fp:
- return '>'
- case frame.sp:
- return '<'
- case bad:
- return '!'
+ hexdumpWords(lo, hi-lo, func(p uintptr, m hexdumpMarker) {
+ if p == frame.fp {
+ m.start()
+ println("FP")
+ }
+ if p == frame.sp {
+ m.start()
+ println("SP")
+ }
+ if p == bad {
+ m.start()
+ println("bad")
}
- return 0
})
}
--
cgit v1.3
From 8c41a482f9b7a101404cd0b417ac45abd441e598 Mon Sep 17 00:00:00 2001
From: Austin Clements
Date: Tue, 10 Jun 2025 19:19:34 -0400
Subject: runtime: add dlog.hexdump
Change-Id: I8149ab9314216bb8f9bf58da55633e2587d75851
Reviewed-on: https://go-review.googlesource.com/c/go/+/681376
Auto-Submit: Austin Clements
Reviewed-by: Michael Pratt
LUCI-TryBot-Result: Go LUCI
---
src/runtime/debuglog.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 51 insertions(+), 3 deletions(-)
(limited to 'src')
diff --git a/src/runtime/debuglog.go b/src/runtime/debuglog.go
index e993e396c1..405f2455c6 100644
--- a/src/runtime/debuglog.go
+++ b/src/runtime/debuglog.go
@@ -196,7 +196,8 @@ const (
debugLogPtr
debugLogString
debugLogConstString
- debugLogStringOverflow
+ debugLogHexdump
+ debugLogOverflow
debugLogPC
debugLogTraceback
@@ -365,13 +366,39 @@ func (l *dloggerImpl) s(x string) *dloggerImpl {
l.w.uvarint(uint64(len(b)))
l.w.bytes(b)
if len(b) != len(x) {
- l.w.byte(debugLogStringOverflow)
+ l.w.byte(debugLogOverflow)
l.w.uvarint(uint64(len(x) - len(b)))
}
}
return l
}
+//go:nosplit
+func (l dloggerFake) hexdump(p unsafe.Pointer, bytes uintptr) dloggerFake { return l }
+
+//go:nosplit
+func (l *dloggerImpl) hexdump(p unsafe.Pointer, bytes uintptr) *dloggerImpl {
+ var b []byte
+ bb := (*slice)(unsafe.Pointer(&b))
+ bb.array = unsafe.Pointer(p)
+ bb.len, bb.cap = int(bytes), int(bytes)
+ if len(b) > debugLogStringLimit {
+ b = b[:debugLogStringLimit]
+ }
+
+ l.w.byte(debugLogHexdump)
+ l.w.uvarint(uint64(uintptr(p)))
+ l.w.uvarint(uint64(len(b)))
+ l.w.bytes(b)
+
+ if uintptr(len(b)) != bytes {
+ l.w.byte(debugLogOverflow)
+ l.w.uvarint(uint64(bytes) - uint64(len(b)))
+ }
+
+ return l
+}
+
//go:nosplit
func (l dloggerFake) pc(x uintptr) dloggerFake { return l }
@@ -708,9 +735,30 @@ func (r *debugLogReader) printVal() bool {
s := *(*string)(unsafe.Pointer(&str))
print(s)
- case debugLogStringOverflow:
+ case debugLogOverflow:
print("..(", r.uvarint(), " more bytes)..")
+ case debugLogHexdump:
+ p := uintptr(r.uvarint())
+ bl := r.uvarint()
+ if r.begin+bl > r.end {
+ r.begin = r.end
+ print("")
+ break
+ }
+ println() // Start on a new line
+ hd := hexdumper{addr: p}
+ for bl > 0 {
+ b := r.data.b[r.begin%uint64(len(r.data.b)):]
+ if uint64(len(b)) > bl {
+ b = b[:bl]
+ }
+ r.begin += uint64(len(b))
+ bl -= uint64(len(b))
+ hd.write(b)
+ }
+ hd.close()
+
case debugLogPC:
printDebugLogPC(uintptr(r.uvarint()), false)
--
cgit v1.3
From 489d3dafb791df8297f71ecf4e2c3c84ea11f6f2 Mon Sep 17 00:00:00 2001
From: Srinivas Pokala
Date: Fri, 14 Nov 2025 14:21:37 +0100
Subject: math: switch s390x math.Pow to generic implementation
The s390x assembly implementation of math.Pow incorrectly handles
certain subnormal cases. This change switches the function to use the
generic implementation instead.
Updates #76247
Cq-Include-Trybots: luci.golang.try:gotip-linux-s390x
Change-Id: I794339080d5a7acf79bbffaeb0214809006fd30c
Reviewed-on: https://go-review.googlesource.com/c/go/+/720540
Reviewed-by: Vishwanatha HD
Reviewed-by: Michael Pratt
Auto-Submit: Michael Pratt
Reviewed-by: Kiran M Vijay IBM
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Mark Freeman
---
src/math/arith_s390x.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
(limited to 'src')
diff --git a/src/math/arith_s390x.go b/src/math/arith_s390x.go
index 129156a9f6..2fda82fff4 100644
--- a/src/math/arith_s390x.go
+++ b/src/math/arith_s390x.go
@@ -129,7 +129,7 @@ func archExpm1(x float64) float64
func expm1TrampolineSetup(x float64) float64
func expm1Asm(x float64) float64
-const haveArchPow = true
+const haveArchPow = false
func archPow(x, y float64) float64
func powTrampolineSetup(x, y float64) float64
--
cgit v1.3
From e64023dcbf40af59a637a982cba58ee8272d61c4 Mon Sep 17 00:00:00 2001
From: Jorropo
Date: Tue, 18 Nov 2025 01:18:30 +0100
Subject: cmd/compile: cleanup useless if statement in prove
Change-Id: Icf5db366b311b5f88809dd07f22cf4bfdead516c
Reviewed-on: https://go-review.googlesource.com/c/go/+/721203
Reviewed-by: Keith Randall
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Jorropo
Reviewed-by: Keith Randall
Reviewed-by: Mark Freeman
---
src/cmd/compile/internal/ssa/prove.go | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 4b2cedc8be..789d7721d4 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -3030,16 +3030,14 @@ func (ft *factsTable) topoSortValuesInBlock(b *Block) {
want := f.NumValues()
scores := ft.reusedTopoSortScoresTable
- if len(scores) < want {
- if want <= cap(scores) {
- scores = scores[:want]
- } else {
- if cap(scores) > 0 {
- f.Cache.freeUintSlice(scores)
- }
- scores = f.Cache.allocUintSlice(want)
- ft.reusedTopoSortScoresTable = scores
+ if want <= cap(scores) {
+ scores = scores[:want]
+ } else {
+ if cap(scores) > 0 {
+ f.Cache.freeUintSlice(scores)
}
+ scores = f.Cache.allocUintSlice(want)
+ ft.reusedTopoSortScoresTable = scores
}
for _, v := range b.Values {
--
cgit v1.3
From dc42565a202694731945421c1fd58815f12423b5 Mon Sep 17 00:00:00 2001
From: Jorropo
Date: Tue, 18 Nov 2025 01:26:01 +0100
Subject: cmd/compile: fix control flow for unsigned divisions proof relations
The continue used to make sense since I first wrote this patch with a loop,
for testing the commutativity of the add.
This was refactored to just try both but I forgot to fix the continue.
Change-Id: I91466a052d5d8ee7193084a71faf69bd27e36d2a
Reviewed-on: https://go-review.googlesource.com/c/go/+/721204
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Auto-Submit: Jorropo
---
src/cmd/compile/internal/ssa/prove.go | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
(limited to 'src')
diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go
index 789d7721d4..d4e7ed14b1 100644
--- a/src/cmd/compile/internal/ssa/prove.go
+++ b/src/cmd/compile/internal/ssa/prove.go
@@ -2503,15 +2503,13 @@ func addLocalFacts(ft *factsTable, b *Block) {
xl := ft.limits[x.ID]
y := add.Args[1]
yl := ft.limits[y.ID]
- if unsignedAddOverflows(xl.umax, yl.umax, add.Type) {
- continue
- }
-
- if xl.umax < uminDivisor {
- ft.update(b, v, y, unsigned, lt|eq)
- }
- if yl.umax < uminDivisor {
- ft.update(b, v, x, unsigned, lt|eq)
+ if !unsignedAddOverflows(xl.umax, yl.umax, add.Type) {
+ if xl.umax < uminDivisor {
+ ft.update(b, v, y, unsigned, lt|eq)
+ }
+ if yl.umax < uminDivisor {
+ ft.update(b, v, x, unsigned, lt|eq)
+ }
}
}
ft.update(b, v, v.Args[0], unsigned, lt|eq)
--
cgit v1.3
From 4fef9f8b5596d42a2997fd8b74185d53fb7d0e43 Mon Sep 17 00:00:00 2001
From: Mark Freeman
Date: Wed, 19 Nov 2025 15:10:24 -0500
Subject: go/types, types2: fix object path for grouped declaration statements
CL 715840 deferred popping from the object path during handling of
grouped declaration statements, which leaves extra objects on the
path since this executes in a loop.
Surprisingly, no test exercised this. This change fixes this small
bug and adds a supporting test.
Fixes #76366
Change-Id: I7fc038b39d3871eea3e60855c46614b463bcfa4f
Reviewed-on: https://go-review.googlesource.com/c/go/+/722060
Reviewed-by: Robert Griesemer
Auto-Submit: Mark Freeman
LUCI-TryBot-Result: Go LUCI
---
src/cmd/compile/internal/types2/decl.go | 2 +-
src/go/types/decl.go | 2 +-
src/internal/types/testdata/fixedbugs/issue76366.go | 12 ++++++++++++
3 files changed, 14 insertions(+), 2 deletions(-)
create mode 100644 src/internal/types/testdata/fixedbugs/issue76366.go
(limited to 'src')
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index 2df34f3b94..60371651ab 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -876,8 +876,8 @@ func (check *Checker) declStmt(list []syntax.Decl) {
scopePos := s.Name.Pos()
check.declare(check.scope, s.Name, obj, scopePos)
check.push(obj) // mark as grey
- defer check.pop()
check.typeDecl(obj, s, nil)
+ check.pop()
default:
check.errorf(s, InvalidSyntaxTree, "unknown syntax.Decl node %T", s)
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index 05cc63e01c..4b374fb66d 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -935,8 +935,8 @@ func (check *Checker) declStmt(d ast.Decl) {
scopePos := d.spec.Name.Pos()
check.declare(check.scope, d.spec.Name, obj, scopePos)
check.push(obj) // mark as grey
- defer check.pop()
check.typeDecl(obj, d.spec, nil)
+ check.pop()
default:
check.errorf(d.node(), InvalidSyntaxTree, "unknown ast.Decl node %T", d.node())
}
diff --git a/src/internal/types/testdata/fixedbugs/issue76366.go b/src/internal/types/testdata/fixedbugs/issue76366.go
new file mode 100644
index 0000000000..b78aa4463f
--- /dev/null
+++ b/src/internal/types/testdata/fixedbugs/issue76366.go
@@ -0,0 +1,12 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+func _() {
+ type (
+ A = int
+ B = []A
+ )
+}
--
cgit v1.3
From 6b83bd7146259316655a5852392b1fa090ba5024 Mon Sep 17 00:00:00 2001
From: Filippo Valsorda
Date: Mon, 22 Sep 2025 14:12:15 +0200
Subject: crypto/ecdh: add KeyExchanger interface
Updates #75300
Change-Id: I6a6a6964bbfa1f099c74d0a3fb3f7894d7b1b832
Reviewed-on: https://go-review.googlesource.com/c/go/+/705795
LUCI-TryBot-Result: Go LUCI
Reviewed-by: Carlos Amedee
Reviewed-by: Roland Shoemaker
Reviewed-by: Junyang Shao
Reviewed-by: Daniel McCarney
---
api/next/75300.txt | 4 ++++
doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md | 2 ++
src/crypto/ecdh/ecdh.go | 12 ++++++++++++
3 files changed, 18 insertions(+)
create mode 100644 api/next/75300.txt
create mode 100644 doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md
(limited to 'src')
diff --git a/api/next/75300.txt b/api/next/75300.txt
new file mode 100644
index 0000000000..9bc1e7f5db
--- /dev/null
+++ b/api/next/75300.txt
@@ -0,0 +1,4 @@
+pkg crypto/ecdh, type KeyExchanger interface { Curve, ECDH, PublicKey } #75300
+pkg crypto/ecdh, type KeyExchanger interface, Curve() Curve #75300
+pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) #75300
+pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300
diff --git a/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md b/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md
new file mode 100644
index 0000000000..5ca55b3215
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/ecdh/75300.md
@@ -0,0 +1,2 @@
+The new [KeyExchanger] interface, implemented by [PrivateKey], makes it possible
+to accept abstract ECDH private keys, e.g. those implemented in hardware.
diff --git a/src/crypto/ecdh/ecdh.go b/src/crypto/ecdh/ecdh.go
index 231f1ea04c..82daacf473 100644
--- a/src/crypto/ecdh/ecdh.go
+++ b/src/crypto/ecdh/ecdh.go
@@ -92,6 +92,18 @@ func (k *PublicKey) Curve() Curve {
return k.curve
}
+// KeyExchanger is an interface for an opaque private key that can be used for
+// key exchange operations. For example, an ECDH key kept in a hardware module.
+//
+// It is implemented by [PrivateKey].
+type KeyExchanger interface {
+ PublicKey() *PublicKey
+ Curve() Curve
+ ECDH(*PublicKey) ([]byte, error)
+}
+
+var _ KeyExchanger = (*PrivateKey)(nil)
+
// PrivateKey is an ECDH private key, usually kept secret.
//
// These keys can be parsed with [crypto/x509.ParsePKCS8PrivateKey] and encoded
--
cgit v1.3
From a2946f23854b0815d4d5883ec48432358f4052c5 Mon Sep 17 00:00:00 2001
From: Filippo Valsorda
Date: Mon, 22 Sep 2025 14:12:53 +0200
Subject: crypto: add Encapsulator and Decapsulator interfaces
Updates #75300
Change-Id: I6a6a6964a0ab36ee3132d8481515c34c86011c13
Reviewed-on: https://go-review.googlesource.com/c/go/+/705796
Reviewed-by: Mark Freeman
Reviewed-by: Daniel McCarney
Reviewed-by: Junyang Shao
LUCI-TryBot-Result: Go LUCI
---
api/next/75300.txt | 8 ++++++++
doc/next/6-stdlib/99-minor/crypto/75300.md | 2 ++
doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md | 3 +++
src/crypto/crypto.go | 18 +++++++++++++++++
src/crypto/mlkem/mlkem.go | 25 +++++++++++++++++++++++-
5 files changed, 55 insertions(+), 1 deletion(-)
create mode 100644 doc/next/6-stdlib/99-minor/crypto/75300.md
create mode 100644 doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md
(limited to 'src')
diff --git a/api/next/75300.txt b/api/next/75300.txt
index 9bc1e7f5db..da24eb4aa3 100644
--- a/api/next/75300.txt
+++ b/api/next/75300.txt
@@ -1,4 +1,12 @@
+pkg crypto, type Decapsulator interface { Decapsulate, Encapsulator } #75300
+pkg crypto, type Decapsulator interface, Decapsulate([]uint8) ([]uint8, error) #75300
+pkg crypto, type Decapsulator interface, Encapsulator() Encapsulator #75300
+pkg crypto, type Encapsulator interface { Bytes, Encapsulate } #75300
+pkg crypto, type Encapsulator interface, Bytes() []uint8 #75300
+pkg crypto, type Encapsulator interface, Encapsulate() ([]uint8, []uint8) #75300
pkg crypto/ecdh, type KeyExchanger interface { Curve, ECDH, PublicKey } #75300
pkg crypto/ecdh, type KeyExchanger interface, Curve() Curve #75300
pkg crypto/ecdh, type KeyExchanger interface, ECDH(*PublicKey) ([]uint8, error) #75300
pkg crypto/ecdh, type KeyExchanger interface, PublicKey() *PublicKey #75300
+pkg crypto/mlkem, method (*DecapsulationKey1024) Encapsulator() crypto.Encapsulator #75300
+pkg crypto/mlkem, method (*DecapsulationKey768) Encapsulator() crypto.Encapsulator #75300
diff --git a/doc/next/6-stdlib/99-minor/crypto/75300.md b/doc/next/6-stdlib/99-minor/crypto/75300.md
new file mode 100644
index 0000000000..02418ea371
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/75300.md
@@ -0,0 +1,2 @@
+The new [Encapsulator] and [Decapsulator] interfaces allow accepting abstract
+KEM encapsulation or decapsulation keys.
diff --git a/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md b/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md
new file mode 100644
index 0000000000..c9cf95f01b
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/crypto/mlkem/75300.md
@@ -0,0 +1,3 @@
+The new [DecapsulationKey768.Encapsulator] and
+[DecapsulationKey1024.Encapsulator] methods implement the new
+[crypto.Decapsulator] interface.
diff --git a/src/crypto/crypto.go b/src/crypto/crypto.go
index 6b3db5a1a3..0bf9ec834b 100644
--- a/src/crypto/crypto.go
+++ b/src/crypto/crypto.go
@@ -253,3 +253,21 @@ func SignMessage(signer Signer, rand io.Reader, msg []byte, opts SignerOpts) (si
}
return signer.Sign(rand, msg, opts)
}
+
+// Decapsulator is an interface for an opaque private KEM key that can be used for
+// decapsulation operations. For example, an ML-KEM key kept in a hardware module.
+//
+// It is implemented, for example, by [crypto/mlkem.DecapsulationKey768].
+type Decapsulator interface {
+ Encapsulator() Encapsulator
+ Decapsulate(ciphertext []byte) (sharedKey []byte, err error)
+}
+
+// Encapsulator is an interface for a public KEM key that can be used for
+// encapsulation operations.
+//
+// It is implemented, for example, by [crypto/mlkem.EncapsulationKey768].
+type Encapsulator interface {
+ Bytes() []byte
+ Encapsulate() (sharedKey, ciphertext []byte)
+}
diff --git a/src/crypto/mlkem/mlkem.go b/src/crypto/mlkem/mlkem.go
index cb44bede20..176b79673b 100644
--- a/src/crypto/mlkem/mlkem.go
+++ b/src/crypto/mlkem/mlkem.go
@@ -11,7 +11,10 @@
// [NIST FIPS 203]: https://doi.org/10.6028/NIST.FIPS.203
package mlkem
-import "crypto/internal/fips140/mlkem"
+import (
+ "crypto"
+ "crypto/internal/fips140/mlkem"
+)
const (
// SharedKeySize is the size of a shared key produced by ML-KEM.
@@ -82,6 +85,16 @@ func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 {
return &EncapsulationKey768{dk.key.EncapsulationKey()}
}
+// Encapsulator returns the encapsulation key, like
+// [DecapsulationKey768.EncapsulationKey].
+//
+// It implements [crypto.Decapsulator].
+func (dk *DecapsulationKey768) Encapsulator() crypto.Encapsulator {
+ return dk.EncapsulationKey()
+}
+
+var _ crypto.Decapsulator = (*DecapsulationKey768)(nil)
+
// An EncapsulationKey768 is the public key used to produce ciphertexts to be
// decapsulated by the corresponding DecapsulationKey768.
type EncapsulationKey768 struct {
@@ -164,6 +177,16 @@ func (dk *DecapsulationKey1024) EncapsulationKey() *EncapsulationKey1024 {
return &EncapsulationKey1024{dk.key.EncapsulationKey()}
}
+// Encapsulator returns the encapsulation key, like
+// [DecapsulationKey1024.EncapsulationKey].
+//
+// It implements [crypto.Decapsulator].
+func (dk *DecapsulationKey1024) Encapsulator() crypto.Encapsulator {
+ return dk.EncapsulationKey()
+}
+
+var _ crypto.Decapsulator = (*DecapsulationKey1024)(nil)
+
// An EncapsulationKey1024 is the public key used to produce ciphertexts to be
// decapsulated by the corresponding DecapsulationKey1024.
type EncapsulationKey1024 struct {
--
cgit v1.3
From 7f2ae21fb481e527086aafee6da3dafdca444f7a Mon Sep 17 00:00:00 2001
From: Xiaolin Zhao
Date: Tue, 18 Nov 2025 10:55:19 +0800
Subject: cmd/internal/obj/loong64: add MULW.D.W[U] instructions
Go asm syntax:
MULWVW RK, RJ, RD
MULWVWU RK, RJ, RD
Equivalent platform assembler syntax:
mulw.d.w rd, rj, rk
mulw.d.wu rd, rj, rk
Change-Id: Ie46a21904a4c25d04200b0663f83072c38a76c6f
Reviewed-on: https://go-review.googlesource.com/c/go/+/721521
LUCI-TryBot-Result: Go LUCI
Reviewed-by: abner chenc
Reviewed-by: Meidan Li
Reviewed-by: Mark Freeman
Reviewed-by: Keith Randall
---
src/cmd/asm/internal/asm/testdata/loong64enc1.s | 6 ++++++
src/cmd/internal/obj/loong64/a.out.go | 4 ++++
src/cmd/internal/obj/loong64/anames.go | 2 ++
src/cmd/internal/obj/loong64/asm.go | 6 ++++++
4 files changed, 18 insertions(+)
(limited to 'src')
diff --git a/src/cmd/asm/internal/asm/testdata/loong64enc1.s b/src/cmd/asm/internal/asm/testdata/loong64enc1.s
index c820a0a5a1..277396bf27 100644
--- a/src/cmd/asm/internal/asm/testdata/loong64enc1.s
+++ b/src/cmd/asm/internal/asm/testdata/loong64enc1.s
@@ -212,6 +212,12 @@ lable2:
SRLV $32, R4, R5 // 85804500
SRLV $32, R4 // 84804500
+ // MULW.D.W[U] instructions
+ MULWVW R4, R5 // a5101f00
+ MULWVW R4, R5, R6 // a6101f00
+ MULWVWU R4, R5 // a5901f00
+ MULWVWU R4, R5, R6 // a6901f00
+
MASKEQZ R4, R5, R6 // a6101300
MASKNEZ R4, R5, R6 // a6901300
diff --git a/src/cmd/internal/obj/loong64/a.out.go b/src/cmd/internal/obj/loong64/a.out.go
index 73f145df14..5b8bffc9f1 100644
--- a/src/cmd/internal/obj/loong64/a.out.go
+++ b/src/cmd/internal/obj/loong64/a.out.go
@@ -589,6 +589,10 @@ const (
AORN
AANDN
+ // 2.2.1.12
+ AMULWVW
+ AMULWVWU
+
// 2.2.7. Atomic Memory Access Instructions
AAMSWAPB
AAMSWAPH
diff --git a/src/cmd/internal/obj/loong64/anames.go b/src/cmd/internal/obj/loong64/anames.go
index ab85c52a21..1749b43bf6 100644
--- a/src/cmd/internal/obj/loong64/anames.go
+++ b/src/cmd/internal/obj/loong64/anames.go
@@ -131,6 +131,8 @@ var Anames = []string{
"ALSLV",
"ORN",
"ANDN",
+ "MULWVW",
+ "MULWVWU",
"AMSWAPB",
"AMSWAPH",
"AMSWAPW",
diff --git a/src/cmd/internal/obj/loong64/asm.go b/src/cmd/internal/obj/loong64/asm.go
index 38b075d77e..b35e49a1b6 100644
--- a/src/cmd/internal/obj/loong64/asm.go
+++ b/src/cmd/internal/obj/loong64/asm.go
@@ -1503,6 +1503,8 @@ func buildop(ctxt *obj.Link) {
opset(AREMU, r0)
opset(ADIV, r0)
opset(ADIVU, r0)
+ opset(AMULWVW, r0)
+ opset(AMULWVWU, r0)
case AMULV:
opset(AMULVU, r0)
@@ -3230,6 +3232,10 @@ func (c *ctxt0) oprrr(a obj.As) uint32 {
return 0x3c << 15 // mulh.d
case AMULHVU:
return 0x3d << 15 // mulhu.d
+ case AMULWVW:
+ return 0x3e << 15 // mulw.d.w
+ case AMULWVWU:
+ return 0x3f << 15 // mulw.d.wu
case ADIV:
return 0x40 << 15 // div.w
case ADIVU:
--
cgit v1.3
From c4bb9653ba28cba4bcd3a3cbb64285c495a03ba2 Mon Sep 17 00:00:00 2001
From: Guoqi Chen
Date: Mon, 17 Nov 2025 11:33:04 +0800
Subject: cmd/compile: Implement LoweredZeroLoop with LSX Instruction on
loong64
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
goos: linux
goarch: loong64
pkg: runtime
cpu: Loongson-3A6000 @ 2500.00MHz
| old.txt | new.txt |
| sec/op | sec/op vs base |
ClearFat256 6.406n ± 0% 3.329n ± 1% -48.03% (p=0.000 n=10)
ClearFat512 12.810n ± 0% 7.607n ± 0% -40.62% (p=0.000 n=10)
ClearFat1024 25.62n ± 0% 14.01n ± 0% -45.32% (p=0.000 n=10)
ClearFat1032 26.02n ± 0% 14.28n ± 0% -45.14% (p=0.000 n=10)
ClearFat1040 26.02n ± 0% 14.41n ± 0% -44.62% (p=0.000 n=10)
MemclrKnownSize192 4.804n ± 0% 2.827n ± 0% -41.15% (p=0.000 n=10)
MemclrKnownSize248 6.561n ± 0% 4.371n ± 0% -33.38% (p=0.000 n=10)
MemclrKnownSize256 6.406n ± 0% 3.335n ± 0% -47.94% (p=0.000 n=10)
geomean 11.41n 6.453n -43.45%
goos: linux
goarch: loong64
pkg: runtime
cpu: Loongson-3C5000 @ 2200.00MHz
| old.txt | new.txt |
| sec/op | sec/op vs base |
ClearFat256 14.570n ± 0% 7.284n ± 0% -50.01% (p=0.000 n=10)
ClearFat512 29.13n ± 0% 14.57n ± 0% -49.98% (p=0.000 n=10)
ClearFat1024 58.26n ± 0% 29.15n ± 0% -49.97% (p=0.000 n=10)
ClearFat1032 58.73n ± 0% 29.15n ± 0% -50.36% (p=0.000 n=10)
ClearFat1040 59.18n ± 0% 29.26n ± 0% -50.56% (p=0.000 n=10)
MemclrKnownSize192 10.930n ± 0% 5.466n ± 0% -49.99% (p=0.000 n=10)
MemclrKnownSize248 14.110n ± 0% 6.772n ± 0% -52.01% (p=0.000 n=10)
MemclrKnownSize256 14.570n ± 0% 7.285n ± 0% -50.00% (p=0.000 n=10)
geomean 25.75n 12.78n -50.36%
Change-Id: I88d7b6ae2f6fc3f095979f24fb83ff42a9d2d42e
Reviewed-on: https://go-review.googlesource.com/c/go/+/720940
Reviewed-by: Meidan Li
Reviewed-by: Mark Freeman
LUCI-TryBot-Result: Go LUCI
Reviewed-by: sophie zhao
Reviewed-by: Keith Randall
Reviewed-by: Keith Randall