mirror of
https://github.com/golang/go.git
synced 2025-12-28 06:34:04 +00:00
[dev.simd] all: merge master (a33bbf1) into dev.simd
Merge List: + 2025-12-08a33bbf1988weak: fix weak pointer test to correctly iterate over weak pointers after GC + 2025-12-08a88a96330fcmd/cgo: use doc link for cgo.Handle + 2025-12-08276cc4d3dbcmd/link: fix AIX builds after recent linker changes + 2025-12-08f2d96272cbruntime/trace: update TestSubscribers to dump traces + 2025-12-084837bcc92cinternal/trace: skip tests for alloc/free experiment by default + 2025-12-08b5f6816ceacmd/link: generate DWARF for moduledata + 2025-12-0844a39c9dacruntime: only run TestNotInGoMetricCallback when building with cgo + 2025-12-083a6a034cd6runtime: disable TestNotInGoMetricCallback on FreeBSD + race + 2025-12-084122d3e9earuntime: use atomic C types with atomic C functions + 2025-12-0834397865b1runtime: deflake TestProfBufWakeup + 2025-12-08d4972f6295runtime: mark getfp as nosplit + 2025-12-050d0d5c9a82test/codegen: test negation with add/sub on riscv64 + 2025-12-052e509e61efcmd/go: convert some more tests to script tests + 2025-12-05c270e71835cmd/go/internal/vet: skip -fix on pkgs from vendor or non-main mod + 2025-12-05745349712eruntime: don't count nGsyscallNoP for extra Ms in C + 2025-12-05f3d572d96acmd/go: fix race applying fixes in fix and vet -fix modes + 2025-12-0576345533f7runtime: expand Pinner documentation + 2025-12-05b133524c0fcmd/go/testdata/script: skip vet_cache in short mode + 2025-12-0596e142ba2bruntime: skip TestArenaCollision if we run out of hints + 2025-12-05fe4952f116runtime: relax threadsSlack in TestReadMetricsSched + 2025-12-058947f092a8runtime: skip mayMoreStackMove in goroutine leak tests + 2025-12-0544cb82449eruntime/race: set missing argument frame for ppc64x atomic And/Or wrappers + 2025-12-05435e61c801runtime: reject any goroutine leak test failure that failed to execute + 2025-12-0554e5540014runtime: print output in case of segfault in goroutine leak tests + 2025-12-059616c33295runtime: don't specify GOEXPERIMENT=greenteagc in goroutine leak tests + 2025-12-052244bd7eebcrypto/subtle: add speculation barrier after DIT + 2025-12-05f84f8d86becmd/compile: fix mis-infer bounds in slice len/cap calculations + 2025-12-05a70addd3b3all: fix some comment issues + 2025-12-0593b49f773dinternal/runtime/maps: clarify probeSeq doc comment + 2025-12-0491267f0a70all: update vendored x/tools + 2025-12-04a753a9ed54cmd/internal/fuzztest: move fuzz tests out of cmd/go test suite + 2025-12-041681c3b67fcrypto: use rand.IsDefaultReader instead of comparing to boring.RandReader + 2025-12-047b67b68a0dcmd/compile: use isUnsignedPowerOfTwo rather than isPowerOfTwo for unsigneds + 2025-12-032b62144069all: REVERSE MERGE dev.simd (9ac524a) into master Change-Id: Ia0cdf06cdde89b6a4db30ed15ed8e0bcbac6ae30
This commit is contained in:
commit
c456ab7a30
@ -425,8 +425,8 @@ and of course there is nothing stopping the C code from doing anything
|
||||
it likes. However, programs that break these rules are likely to fail
|
||||
in unexpected and unpredictable ways.
|
||||
|
||||
The runtime/cgo.Handle type can be used to safely pass Go values
|
||||
between Go and C. See the runtime/cgo package documentation for details.
|
||||
The type [runtime/cgo.Handle] can be used to safely pass Go values
|
||||
between Go and C.
|
||||
|
||||
Note: the current implementation has a bug. While Go code is permitted
|
||||
to write nil or a C pointer (but not a Go pointer) to C memory, the
|
||||
|
||||
@ -2119,7 +2119,10 @@ func (ft *factsTable) detectSliceLenRelation(v *Value) {
|
||||
if bound := ow.Args[0]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
|
||||
lenOffset = ow.Args[1]
|
||||
} else if bound := ow.Args[1]; (bound.Op == OpSliceLen || bound.Op == OpStringLen) && bound.Args[0] == slice {
|
||||
lenOffset = ow.Args[0]
|
||||
// Do not infer K - slicelen, see issue #76709.
|
||||
if ow.Op == OpAdd64 {
|
||||
lenOffset = ow.Args[0]
|
||||
}
|
||||
}
|
||||
if lenOffset == nil || lenOffset.Op != OpConst64 {
|
||||
continue
|
||||
@ -2885,9 +2888,9 @@ func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) {
|
||||
xl := ft.limits[x.ID]
|
||||
y := v.Args[1]
|
||||
yl := ft.limits[y.ID]
|
||||
if xl.umin == xl.umax && isPowerOfTwo(int64(xl.umin)) ||
|
||||
if xl.umin == xl.umax && isUnsignedPowerOfTwo(xl.umin) ||
|
||||
xl.min == xl.max && isPowerOfTwo(xl.min) ||
|
||||
yl.umin == yl.umax && isPowerOfTwo(int64(yl.umin)) ||
|
||||
yl.umin == yl.umax && isUnsignedPowerOfTwo(yl.umin) ||
|
||||
yl.min == yl.max && isPowerOfTwo(yl.min) {
|
||||
// 0,1 * a power of two is better done as a shift
|
||||
break
|
||||
|
||||
@ -11,7 +11,7 @@ require (
|
||||
golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670
|
||||
golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec
|
||||
golang.org/x/term v0.37.0
|
||||
golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713
|
||||
golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@ -22,7 +22,7 @@ golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 h1:IjQf87/qLz2y0SiCc0uY3DwajALXkAgP1Pxal0mmdrM=
|
||||
golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 h1:i4GzAuZW4RuKXltwKyLYAfk7E1TSKQBxRAI7XKfLjSk=
|
||||
golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2 h1:2Qqv605Nus9iUp3ErvEU/q92Q3HAzeROztzl9pzAno8=
|
||||
golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2/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=
|
||||
|
||||
@ -972,19 +972,6 @@ func TestNewReleaseRebuildsStalePackagesInGOPATH(t *testing.T) {
|
||||
tg.wantNotStale("p1", "", "./testgo list claims p1 is stale after building with old release")
|
||||
}
|
||||
|
||||
func TestPackageMainTestCompilerFlags(t *testing.T) {
|
||||
tg := testgo(t)
|
||||
defer tg.cleanup()
|
||||
tg.parallel()
|
||||
tg.makeTempdir()
|
||||
tg.setenv("GOPATH", tg.path("."))
|
||||
tg.tempFile("src/p1/p1.go", "package main\n")
|
||||
tg.tempFile("src/p1/p1_test.go", "package main\nimport \"testing\"\nfunc Test(t *testing.T){}\n")
|
||||
tg.run("test", "-c", "-n", "p1")
|
||||
tg.grepBothNot(`([\\/]compile|gccgo).* (-p main|-fgo-pkgpath=main).*p1\.go`, "should not have run compile -p main p1.go")
|
||||
tg.grepStderr(`([\\/]compile|gccgo).* (-p p1|-fgo-pkgpath=p1).*p1\.go`, "should have run compile -p p1 p1.go")
|
||||
}
|
||||
|
||||
// Issue 4104.
|
||||
func TestGoTestWithPackageListedMultipleTimes(t *testing.T) {
|
||||
tooSlow(t, "links and runs a test")
|
||||
@ -1070,43 +1057,6 @@ func TestGoListDeps(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoListTest(t *testing.T) {
|
||||
skipIfGccgo(t, "gccgo does not have standard packages")
|
||||
tg := testgo(t)
|
||||
defer tg.cleanup()
|
||||
tg.parallel()
|
||||
tg.makeTempdir()
|
||||
tg.setenv("GOCACHE", tg.tempdir)
|
||||
|
||||
tg.run("list", "-test", "-deps", "bytes")
|
||||
tg.grepStdout(`^bytes.test$`, "missing test main")
|
||||
tg.grepStdout(`^bytes$`, "missing real bytes")
|
||||
tg.grepStdout(`^bytes \[bytes.test\]$`, "missing test copy of bytes")
|
||||
tg.grepStdout(`^testing \[bytes.test\]$`, "missing test copy of testing")
|
||||
tg.grepStdoutNot(`^testing$`, "unexpected real copy of testing")
|
||||
|
||||
tg.run("list", "-test", "bytes")
|
||||
tg.grepStdout(`^bytes.test$`, "missing test main")
|
||||
tg.grepStdout(`^bytes$`, "missing real bytes")
|
||||
tg.grepStdout(`^bytes \[bytes.test\]$`, "unexpected test copy of bytes")
|
||||
tg.grepStdoutNot(`^testing \[bytes.test\]$`, "unexpected test copy of testing")
|
||||
tg.grepStdoutNot(`^testing$`, "unexpected real copy of testing")
|
||||
|
||||
tg.run("list", "-test", "cmd/buildid", "cmd/gofmt")
|
||||
tg.grepStdout(`^cmd/buildid$`, "missing cmd/buildid")
|
||||
tg.grepStdout(`^cmd/gofmt$`, "missing cmd/gofmt")
|
||||
tg.grepStdout(`^cmd/gofmt\.test$`, "missing cmd/gofmt test")
|
||||
tg.grepStdoutNot(`^cmd/buildid\.test$`, "unexpected cmd/buildid test")
|
||||
tg.grepStdoutNot(`^testing`, "unexpected testing")
|
||||
|
||||
tg.run("list", "-test", "runtime/cgo")
|
||||
tg.grepStdout(`^runtime/cgo$`, "missing runtime/cgo")
|
||||
|
||||
tg.run("list", "-deps", "-f", "{{if .DepOnly}}{{.ImportPath}}{{end}}", "sort")
|
||||
tg.grepStdout(`^internal/reflectlite$`, "missing internal/reflectlite")
|
||||
tg.grepStdoutNot(`^sort`, "unexpected sort")
|
||||
}
|
||||
|
||||
func TestGoListCompiledCgo(t *testing.T) {
|
||||
tooSlow(t, "compiles cgo files")
|
||||
|
||||
@ -1528,40 +1478,6 @@ func main() {}
|
||||
tg.run("run", tg.path("bar.go"))
|
||||
}
|
||||
|
||||
func TestListTemplateContextFunction(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, tt := range []struct {
|
||||
v string
|
||||
want string
|
||||
}{
|
||||
{"GOARCH", runtime.GOARCH},
|
||||
{"GOOS", runtime.GOOS},
|
||||
{"GOROOT", testGOROOT},
|
||||
{"GOPATH", os.Getenv("GOPATH")},
|
||||
{"CgoEnabled", ""},
|
||||
{"UseAllFiles", ""},
|
||||
{"Compiler", ""},
|
||||
{"BuildTags", ""},
|
||||
{"ReleaseTags", ""},
|
||||
{"InstallSuffix", ""},
|
||||
} {
|
||||
tt := tt
|
||||
t.Run(tt.v, func(t *testing.T) {
|
||||
tg := testgo(t)
|
||||
tg.parallel()
|
||||
defer tg.cleanup()
|
||||
tmpl := "{{context." + tt.v + "}}"
|
||||
tg.run("list", "-f", tmpl)
|
||||
if tt.want == "" {
|
||||
return
|
||||
}
|
||||
if got := strings.TrimSpace(tg.getStdout()); got != tt.want {
|
||||
t.Errorf("go list -f %q: got %q; want %q", tmpl, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test that you cannot use a local import in a package
|
||||
// accessed by a non-local import (found in a GOPATH/GOROOT).
|
||||
// See golang.org/issue/17475.
|
||||
@ -2247,23 +2163,6 @@ func TestCacheCoverage(t *testing.T) {
|
||||
tg.run("test", "-cover", "-short", "math", "strings")
|
||||
}
|
||||
|
||||
func TestIssue22588(t *testing.T) {
|
||||
// Don't get confused by stderr coming from tools.
|
||||
tg := testgo(t)
|
||||
defer tg.cleanup()
|
||||
tg.parallel()
|
||||
|
||||
tg.wantNotStale("runtime", "", "must be non-stale to compare staleness under -toolexec")
|
||||
|
||||
if _, err := os.Stat("/usr/bin/time"); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
|
||||
tg.run("list", "-f={{.Stale}}", "runtime")
|
||||
tg.run("list", "-toolexec=/usr/bin/time", "-f={{.Stale}}", "runtime")
|
||||
tg.grepStdout("false", "incorrectly reported runtime as stale")
|
||||
}
|
||||
|
||||
func TestIssue22531(t *testing.T) {
|
||||
tooSlow(t, "links binaries")
|
||||
if gocacheverify.Value() == "1" {
|
||||
|
||||
@ -736,7 +736,7 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
|
||||
// Otherwise, if fuzzing identifies a failure it could corrupt checksums in
|
||||
// the module cache (or permanently alter the behavior of std tests for all
|
||||
// users) by writing the failing input to the package's testdata directory.
|
||||
// (See https://golang.org/issue/48495 and test_fuzz_modcache.txt.)
|
||||
// (See https://golang.org/issue/48495 and cmd/internal/fuzztest/test_fuzz_modcache.txt.)
|
||||
mainMods := moduleLoaderState.MainModules
|
||||
if m := pkgs[0].Module; m != nil && m.Path != "" {
|
||||
if !mainMods.Contains(m.Path) {
|
||||
@ -1371,7 +1371,7 @@ func addTestVet(loaderstate *modload.State, b *work.Builder, p *load.Package, ru
|
||||
return
|
||||
}
|
||||
|
||||
vet := b.VetAction(loaderstate, work.ModeBuild, work.ModeBuild, p)
|
||||
vet := b.VetAction(loaderstate, work.ModeBuild, work.ModeBuild, false, p)
|
||||
runAction.Deps = append(runAction.Deps, vet)
|
||||
// Install will clean the build directory.
|
||||
// Make sure vet runs first.
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
package vet
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@ -176,6 +178,7 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
|
||||
work.VetExplicit = len(toolFlags) > 0
|
||||
|
||||
applyFixes := false
|
||||
if cmd.Name() == "fix" || *vetFixFlag {
|
||||
// fix mode: 'go fix' or 'go vet -fix'
|
||||
if jsonFlag {
|
||||
@ -186,6 +189,8 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
toolFlags = append(toolFlags, "-fix")
|
||||
if diffFlag {
|
||||
toolFlags = append(toolFlags, "-diff")
|
||||
} else {
|
||||
applyFixes = true
|
||||
}
|
||||
}
|
||||
if contextFlag != -1 {
|
||||
@ -226,6 +231,7 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
base.Fatalf("no packages to %s", cmd.Name())
|
||||
}
|
||||
|
||||
// Build action graph.
|
||||
b := work.NewBuilder("", moduleLoaderState.VendorDirOrEmpty)
|
||||
defer func() {
|
||||
if err := b.Close(); err != nil {
|
||||
@ -233,6 +239,13 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
}
|
||||
}()
|
||||
|
||||
root := &work.Action{Mode: "go " + cmd.Name()}
|
||||
|
||||
addVetAction := func(p *load.Package) {
|
||||
act := b.VetAction(moduleLoaderState, work.ModeBuild, work.ModeBuild, applyFixes, p)
|
||||
root.Deps = append(root.Deps, act)
|
||||
}
|
||||
|
||||
// To avoid file corruption from duplicate application of
|
||||
// fixes (in fix mode), and duplicate reporting of diagnostics
|
||||
// (in vet mode), we must run the tool only once for each
|
||||
@ -248,9 +261,16 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
// We needn't worry about intermediate test variants, as they
|
||||
// will only be executed in VetxOnly mode, for facts but not
|
||||
// diagnostics.
|
||||
|
||||
root := &work.Action{Mode: "go " + cmd.Name()}
|
||||
for _, p := range pkgs {
|
||||
// Don't apply fixes to vendored packages, including
|
||||
// the GOROOT vendor packages that are part of std,
|
||||
// or to packages from non-main modules (#76479).
|
||||
if applyFixes {
|
||||
if p.Standard && strings.HasPrefix(p.ImportPath, "vendor/") ||
|
||||
p.Module != nil && !p.Module.Main {
|
||||
continue
|
||||
}
|
||||
}
|
||||
_, ptest, pxtest, perr := load.TestPackagesFor(moduleLoaderState, ctx, pkgOpts, p, nil)
|
||||
if perr != nil {
|
||||
base.Errorf("%v", perr.Error)
|
||||
@ -262,13 +282,65 @@ func run(ctx context.Context, cmd *base.Command, args []string) {
|
||||
}
|
||||
if len(ptest.GoFiles) > 0 || len(ptest.CgoFiles) > 0 {
|
||||
// The test package includes all the files of primary package.
|
||||
root.Deps = append(root.Deps, b.VetAction(moduleLoaderState, work.ModeBuild, work.ModeBuild, ptest))
|
||||
addVetAction(ptest)
|
||||
}
|
||||
if pxtest != nil {
|
||||
root.Deps = append(root.Deps, b.VetAction(moduleLoaderState, work.ModeBuild, work.ModeBuild, pxtest))
|
||||
addVetAction(pxtest)
|
||||
}
|
||||
}
|
||||
b.Do(ctx, root)
|
||||
|
||||
// Apply fixes.
|
||||
//
|
||||
// We do this as a separate phase after the build to avoid
|
||||
// races between source file updates and reads of those same
|
||||
// files by concurrent actions of the ongoing build.
|
||||
//
|
||||
// If a file is fixed by multiple actions, they must be consistent.
|
||||
if applyFixes {
|
||||
contents := make(map[string][]byte)
|
||||
// Gather the fixes.
|
||||
for _, act := range root.Deps {
|
||||
if act.FixArchive != "" {
|
||||
if err := readZip(act.FixArchive, contents); err != nil {
|
||||
base.Errorf("reading archive of fixes: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply them.
|
||||
for filename, content := range contents {
|
||||
if err := os.WriteFile(filename, content, 0644); err != nil {
|
||||
base.Errorf("applying fix: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readZip reads the zipfile entries into the provided map.
|
||||
// It reports an error if updating the map would change an existing entry.
|
||||
func readZip(zipfile string, out map[string][]byte) error {
|
||||
r, err := zip.OpenReader(zipfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close() // ignore error
|
||||
for _, f := range r.File {
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := io.ReadAll(rc)
|
||||
rc.Close() // ignore error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if prev, ok := out[f.Name]; ok && !bytes.Equal(prev, content) {
|
||||
return fmt.Errorf("inconsistent fixes to file %v", f.Name)
|
||||
}
|
||||
out[f.Name] = content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// printJSONDiagnostics parses JSON (from the tool's stdout) and
|
||||
|
||||
@ -109,11 +109,13 @@ type Action struct {
|
||||
actionID cache.ActionID // cache ID of action input
|
||||
buildID string // build ID of action output
|
||||
|
||||
VetxOnly bool // Mode=="vet": only being called to supply info about dependencies
|
||||
needVet bool // Mode=="build": need to fill in vet config
|
||||
needBuild bool // Mode=="build": need to do actual build (can be false if needVet is true)
|
||||
vetCfg *vetConfig // vet config
|
||||
output []byte // output redirect buffer (nil means use b.Print)
|
||||
VetxOnly bool // Mode=="vet": only being called to supply info about dependencies
|
||||
needVet bool // Mode=="build": need to fill in vet config
|
||||
needBuild bool // Mode=="build": need to do actual build (can be false if needVet is true)
|
||||
needFix bool // Mode=="vet": need secondary target, a .zip file containing fixes
|
||||
vetCfg *vetConfig // vet config
|
||||
FixArchive string // the created .zip file containing fixes (if needFix)
|
||||
output []byte // output redirect buffer (nil means use b.Print)
|
||||
|
||||
sh *Shell // lazily created per-Action shell; see Builder.Shell
|
||||
|
||||
@ -869,9 +871,10 @@ func (b *Builder) cgoAction(p *load.Package, objdir string, deps []*Action, hasC
|
||||
// It depends on the action for compiling p.
|
||||
// If the caller may be causing p to be installed, it is up to the caller
|
||||
// to make sure that the install depends on (runs after) vet.
|
||||
func (b *Builder) VetAction(s *modload.State, mode, depMode BuildMode, p *load.Package) *Action {
|
||||
func (b *Builder) VetAction(s *modload.State, mode, depMode BuildMode, needFix bool, p *load.Package) *Action {
|
||||
a := b.vetAction(s, mode, depMode, p)
|
||||
a.VetxOnly = false
|
||||
a.needFix = needFix
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@ -1187,6 +1187,7 @@ type vetConfig struct {
|
||||
VetxOutput string // write vetx data to this output file
|
||||
Stdout string // write stdout (JSON, unified diff) to this output file
|
||||
GoVersion string // Go version for package
|
||||
FixArchive string // write fixed files to this zip archive, if non-empty
|
||||
|
||||
SucceedOnTypecheckFailure bool // awful hack; see #18395 and below
|
||||
}
|
||||
@ -1308,6 +1309,9 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
|
||||
vcfg.VetxOnly = a.VetxOnly
|
||||
vcfg.VetxOutput = a.Objdir + "vet.out"
|
||||
vcfg.Stdout = a.Objdir + "vet.stdout"
|
||||
if a.needFix {
|
||||
vcfg.FixArchive = a.Objdir + "vet.fix.zip"
|
||||
}
|
||||
vcfg.PackageVetx = make(map[string]string)
|
||||
|
||||
h := cache.NewHash("vet " + a.Package.ImportPath)
|
||||
@ -1368,31 +1372,60 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
|
||||
vcfg.PackageVetx[a1.Package.ImportPath] = a1.built
|
||||
}
|
||||
}
|
||||
vetxKey := cache.ActionID(h.Sum()) // for .vetx file
|
||||
|
||||
fmt.Fprintf(h, "stdout\n")
|
||||
stdoutKey := cache.ActionID(h.Sum()) // for .stdout file
|
||||
var (
|
||||
id = cache.ActionID(h.Sum()) // for .vetx file
|
||||
stdoutKey = cache.Subkey(id, "stdout") // for .stdout file
|
||||
fixArchiveKey = cache.Subkey(id, "fix.zip") // for .fix.zip file
|
||||
)
|
||||
|
||||
// Check the cache; -a forces a rebuild.
|
||||
if !cfg.BuildA {
|
||||
c := cache.Default()
|
||||
if vcfg.VetxOnly {
|
||||
if file, _, err := cache.GetFile(c, vetxKey); err == nil {
|
||||
a.built = file
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Copy cached vet.std files to stdout.
|
||||
if file, _, err := cache.GetFile(c, stdoutKey); err == nil {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close() // ignore error (can't fail)
|
||||
return VetHandleStdout(f)
|
||||
}
|
||||
|
||||
// There may be multiple artifacts in the cache.
|
||||
// We need to retrieve them all, or none:
|
||||
// the effect must be transactional.
|
||||
var (
|
||||
vetxFile string // name of cached .vetx file
|
||||
fixArchive string // name of cached .fix.zip file
|
||||
stdout io.Reader = bytes.NewReader(nil) // cached stdout stream
|
||||
)
|
||||
|
||||
// Obtain location of cached .vetx file.
|
||||
vetxFile, _, err := cache.GetFile(c, id)
|
||||
if err != nil {
|
||||
goto cachemiss
|
||||
}
|
||||
|
||||
// Obtain location of cached .fix.zip file (if needed).
|
||||
if a.needFix {
|
||||
file, _, err := cache.GetFile(c, fixArchiveKey)
|
||||
if err != nil {
|
||||
goto cachemiss
|
||||
}
|
||||
fixArchive = file
|
||||
}
|
||||
|
||||
// Copy cached .stdout file to stdout.
|
||||
if file, _, err := cache.GetFile(c, stdoutKey); err == nil {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
goto cachemiss
|
||||
}
|
||||
defer f.Close() // ignore error (can't fail)
|
||||
stdout = f
|
||||
}
|
||||
|
||||
// Cache hit: commit transaction.
|
||||
a.built = vetxFile
|
||||
a.FixArchive = fixArchive
|
||||
if err := VetHandleStdout(stdout); err != nil {
|
||||
return err // internal error (don't fall through to cachemiss)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
cachemiss:
|
||||
|
||||
js, err := json.MarshalIndent(vcfg, "", "\t")
|
||||
if err != nil {
|
||||
@ -1419,13 +1452,23 @@ func (b *Builder) vet(ctx context.Context, a *Action) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Vet tool succeeded, possibly with facts and JSON stdout. Save both in cache.
|
||||
// Vet tool succeeded, possibly with facts, fixes, or JSON stdout.
|
||||
// Save all in cache.
|
||||
|
||||
// Save facts
|
||||
// Save facts.
|
||||
if f, err := os.Open(vcfg.VetxOutput); err == nil {
|
||||
defer f.Close() // ignore error
|
||||
a.built = vcfg.VetxOutput
|
||||
cache.Default().Put(vetxKey, f) // ignore error
|
||||
cache.Default().Put(id, f) // ignore error
|
||||
}
|
||||
|
||||
// Save fix archive (if any).
|
||||
if a.needFix {
|
||||
if f, err := os.Open(vcfg.FixArchive); err == nil {
|
||||
defer f.Close() // ignore error
|
||||
a.FixArchive = vcfg.FixArchive
|
||||
cache.Default().Put(fixArchiveKey, f) // ignore error
|
||||
}
|
||||
}
|
||||
|
||||
// Save stdout.
|
||||
|
||||
44
src/cmd/go/testdata/script/fix_vendor.txt
vendored
Normal file
44
src/cmd/go/testdata/script/fix_vendor.txt
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
# Test that go fix skips fixes to non-main and/or vendored packages.
|
||||
# (It uses the interface{} -> any modernizer.)
|
||||
|
||||
# Create vendor tree programmatically to avoid
|
||||
# having to hardcode sums in this txtar archive.
|
||||
go mod vendor
|
||||
|
||||
# Show fixes on two packages, one in the main module
|
||||
# and one in a vendored dependency.
|
||||
# Only the main one (a) is shown.
|
||||
go fix -diff example.com/a example.com/b
|
||||
stdout 'a[/\\]a.go'
|
||||
stdout '\-var _ interface\{\}'
|
||||
stdout '\+var _ any'
|
||||
! stdout 'b[/\\]b.go'
|
||||
|
||||
# Apply fixes to the same two packages.
|
||||
# Only the main module was modified.
|
||||
go fix example.com/a example.com/b
|
||||
grep 'var _ any' a/a.go
|
||||
grep 'var _ interface{}' b/b.go
|
||||
grep 'var _ interface{}' vendor/example.com/b/b.go
|
||||
|
||||
-- go.mod --
|
||||
module example.com
|
||||
go 1.26
|
||||
|
||||
require "example.com/b" v0.0.0
|
||||
replace "example.com/b" => ./b
|
||||
|
||||
-- a/a.go --
|
||||
package a
|
||||
|
||||
import _ "example.com/b"
|
||||
|
||||
var _ interface{}
|
||||
|
||||
-- b/go.mod --
|
||||
module example.com/b
|
||||
|
||||
-- b/b.go --
|
||||
package b
|
||||
|
||||
var _ interface{}
|
||||
15
src/cmd/go/testdata/script/list_template_context_function.txt
vendored
Normal file
15
src/cmd/go/testdata/script/list_template_context_function.txt
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
# This is a script test conversion of TestListTemplateContextFunction
|
||||
# originally added in CL 20010, which fixed #14547.
|
||||
# Test the ability to use the build context in the go list template.
|
||||
|
||||
go list -f '{{context.GOARCH}} {{context.GOOS}} {{context.GOROOT}} {{context.GOPATH}}'
|
||||
cmpenv stdout want.txt
|
||||
|
||||
go list -f '{{context.CgoEnabled}} {{context.UseAllFiles}} {{context.Compiler}} {{context.BuildTags}} {{context.ReleaseTags}} {{context.InstallSuffix}}'
|
||||
|
||||
-- go.mod --
|
||||
module foo
|
||||
-- foo.go --
|
||||
package foo
|
||||
-- want.txt --
|
||||
$GOARCH $GOOS $GOROOT $GOPATH
|
||||
33
src/cmd/go/testdata/script/list_test.txt
vendored
Normal file
33
src/cmd/go/testdata/script/list_test.txt
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# This is a script test conversion of TestGoListTest which was added in
|
||||
# CL 107916, which added support for go list -test.
|
||||
# Test the behavior of go list -test.
|
||||
|
||||
[compiler:gccgo] skip 'gccgo does not have standard packages'
|
||||
|
||||
go list -test -deps bytes
|
||||
stdout '^bytes.test$' # test main
|
||||
stdout '^bytes$' # real bytes
|
||||
stdout '^bytes \[bytes.test\]$' # test copy of bytes
|
||||
stdout 'testing \[bytes.test\]$' # test copy of testing
|
||||
! stdout ^testing$ # should not have real testing
|
||||
|
||||
go list -test bytes
|
||||
stdout '^bytes.test$' # test main
|
||||
stdout '^bytes$' # real bytes
|
||||
stdout '^bytes \[bytes.test\]$' # test copy of bytes
|
||||
! stdout '^testing \[bytes.test\]$' # should not have test copy of testing
|
||||
! stdout '^testing$' # should not have real testing
|
||||
|
||||
go list -test cmd/buildid cmd/gofmt
|
||||
stdout '^cmd/buildid$' # cmd/buildid
|
||||
stdout '^cmd/gofmt$' # cmd/gofmt
|
||||
stdout '^cmd/gofmt\.test$' # cmd/gofmt test
|
||||
! stdout '^cmd/buildid\.test$' # should not have cmd/buildid test
|
||||
! stdout '^testing' # should not have real testing
|
||||
|
||||
go list -test runtime/cgo
|
||||
stdout '^runtime/cgo$' # runtime/cgo
|
||||
|
||||
go list -deps -f '{{if .DepOnly}}{{.ImportPath}}{{end}}' sort
|
||||
stdout '^internal/reflectlite$' # internal/reflectlite
|
||||
! stdout '^sort' # should not have sort
|
||||
11
src/cmd/go/testdata/script/list_toolexec_stderr_issue22588.txt
vendored
Normal file
11
src/cmd/go/testdata/script/list_toolexec_stderr_issue22588.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# This is a script test conversion of TestIssue22588 which was added in CL 76017.
|
||||
# Test that the stderr of a tool run under toolexec doesn't affect caching.
|
||||
|
||||
# Don't get confused by stderr coming from tools.
|
||||
[!exec:/usr/bin/time] skip
|
||||
|
||||
! stale runtime 'must be non-stale to compare staleness under -toolexec'
|
||||
|
||||
go list -f '{{.Stale}}' runtime
|
||||
go list -toolexec /usr/bin/time -f '{{.Stale}}' runtime
|
||||
stdout 'false' # runtime should not be reported as stale
|
||||
22
src/cmd/go/testdata/script/test_main_compiler_flags.txt
vendored
Normal file
22
src/cmd/go/testdata/script/test_main_compiler_flags.txt
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# This is a script test conversion of TestPackageMainTestCompilerFlags
|
||||
# originally added in CL 86265, which fixed #23180.
|
||||
# Test that we don't pass the package name 'main' to -p when building the
|
||||
# test package for a main package.
|
||||
|
||||
go test -c -n p1
|
||||
# should not have run compile -p main p1.go
|
||||
! stdout '([\\/]compile|gccgo).* (-p main|-fgo-pkgpath=main).*p1\.go'
|
||||
! stderr '([\\/]compile|gccgo).* (-p main|-fgo-pkgpath=main).*p1\.go'
|
||||
# should have run compile -p p1 p1.go
|
||||
stderr '([\\/]compile|gccgo).* (-p p1|-fgo-pkgpath=p1).*p1\.go'
|
||||
|
||||
-- go.mod --
|
||||
module p1
|
||||
-- p1.go --
|
||||
package main
|
||||
-- p1_test.go --
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test(t *testing.T){}
|
||||
1
src/cmd/go/testdata/script/vet_basic.txt
vendored
1
src/cmd/go/testdata/script/vet_basic.txt
vendored
@ -91,6 +91,7 @@ stderr '-json and -diff cannot be used together'
|
||||
# Legacy way of selecting fixers is a no-op.
|
||||
go fix -fix=old1,old2 example.com/x
|
||||
stderr 'go fix: the -fix=old1,old2 flag is obsolete and has no effect'
|
||||
cp x.go.bak x.go
|
||||
|
||||
# -c=n flag shows n lines of context.
|
||||
! go vet -c=2 -printf example.com/x
|
||||
|
||||
2
src/cmd/go/testdata/script/vet_cache.txt
vendored
2
src/cmd/go/testdata/script/vet_cache.txt
vendored
@ -1,6 +1,8 @@
|
||||
# Test that go vet's caching of vet tool actions replays
|
||||
# the recorded stderr output even after a cache hit.
|
||||
|
||||
[short] skip 'uses a fresh build cache'
|
||||
|
||||
# Set up fresh GOCACHE.
|
||||
env GOCACHE=$WORK/gocache
|
||||
|
||||
|
||||
22
src/cmd/internal/fuzztest/script_test.go
Normal file
22
src/cmd/internal/fuzztest/script_test.go
Normal file
@ -0,0 +1,22 @@
|
||||
// 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 fuzztest
|
||||
|
||||
import (
|
||||
"cmd/internal/script/scripttest"
|
||||
"flag"
|
||||
"internal/testenv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//go:generate go test cmd/internal/fuzztest -v -run=TestScript/README --fixreadme
|
||||
|
||||
var fixReadme = flag.Bool("fixreadme", false, "if true, update README for script tests")
|
||||
|
||||
func TestScript(t *testing.T) {
|
||||
testenv.MustHaveGoBuild(t)
|
||||
testenv.SkipIfShortAndSlow(t)
|
||||
scripttest.RunToolScriptTest(t, nil, "testdata/script", *fixReadme)
|
||||
}
|
||||
286
src/cmd/internal/fuzztest/testdata/script/README
vendored
Normal file
286
src/cmd/internal/fuzztest/testdata/script/README
vendored
Normal file
@ -0,0 +1,286 @@
|
||||
This file is generated by 'go generate'. DO NOT EDIT.
|
||||
|
||||
This directory holds test scripts *.txt run during 'go test cmd/<toolname>'.
|
||||
To run a specific script foo.txt
|
||||
|
||||
go test cmd/<toolname> -run=Script/^foo$
|
||||
|
||||
In general script files should have short names: a few words,
|
||||
not whole sentences.
|
||||
The first word should be the general category of behavior being tested,
|
||||
often the name of a go subcommand (build, link, compile, ...) or concept (vendor, pattern).
|
||||
|
||||
Each script is a text archive (go doc internal/txtar).
|
||||
The script begins with an actual command script to run
|
||||
followed by the content of zero or more supporting files to
|
||||
create in the script's temporary file system before it starts executing.
|
||||
|
||||
As an example, run_hello.txt says:
|
||||
|
||||
# hello world
|
||||
go run hello.go
|
||||
stderr 'hello world'
|
||||
! stdout .
|
||||
|
||||
-- hello.go --
|
||||
package main
|
||||
func main() { println("hello world") }
|
||||
|
||||
Each script runs in a fresh temporary work directory tree, available to scripts as $WORK.
|
||||
Scripts also have access to other environment variables, including:
|
||||
|
||||
GOARCH=<target GOARCH>
|
||||
GOOS=<target GOOS>
|
||||
TMPDIR=$WORK/tmp
|
||||
devnull=<value of os.DevNull>
|
||||
goversion=<current Go version; for example, 1.12>
|
||||
|
||||
On Plan 9, the variables $path and $home are set instead of $PATH and $HOME.
|
||||
On Windows, the variables $USERPROFILE and $TMP are set instead of
|
||||
$HOME and $TMPDIR.
|
||||
|
||||
The lines at the top of the script are a sequence of commands to be executed by
|
||||
a small script engine configured in .../cmd/internal/script/scripttest/run.go (not the system shell).
|
||||
|
||||
Each line of a script is parsed into a sequence of space-separated command
|
||||
words, with environment variable expansion within each word and # marking
|
||||
an end-of-line comment. Additional variables named ':' and '/' are expanded
|
||||
within script arguments (expanding to the value of os.PathListSeparator and
|
||||
os.PathSeparator respectively) but are not inherited in subprocess environments.
|
||||
|
||||
Adding single quotes around text keeps spaces in that text from being treated
|
||||
as word separators and also disables environment variable expansion. Inside a
|
||||
single-quoted block of text, a repeated single quote indicates a literal single
|
||||
quote, as in:
|
||||
|
||||
'Don''t communicate by sharing memory.'
|
||||
|
||||
A line beginning with # is a comment and conventionally explains what is being
|
||||
done or tested at the start of a new section of the script.
|
||||
|
||||
Commands are executed one at a time, and errors are checked for each command;
|
||||
if any command fails unexpectedly, no subsequent commands in the script are
|
||||
executed. The command prefix ! indicates that the command on the rest of the
|
||||
line (typically go or a matching predicate) must fail instead of succeeding.
|
||||
The command prefix ? indicates that the command may or may not succeed, but the
|
||||
script should continue regardless.
|
||||
|
||||
The command prefix [cond] indicates that the command on the rest of the line
|
||||
should only run when the condition is satisfied.
|
||||
|
||||
A condition can be negated: [!root] means to run the rest of the line only if
|
||||
the user is not root. Multiple conditions may be given for a single command,
|
||||
for example, '[linux] [amd64] skip'. The command will run if all conditions are
|
||||
satisfied.
|
||||
|
||||
When TestScript runs a script and the script fails, by default TestScript shows
|
||||
the execution of the most recent phase of the script (since the last # comment)
|
||||
and only shows the # comments for earlier phases.
|
||||
|
||||
Note also that in reported output, the actual name of the per-script temporary directory
|
||||
has been consistently replaced with the literal string $WORK.
|
||||
|
||||
The available commands are:
|
||||
cat files...
|
||||
concatenate files and print to the script's stdout buffer
|
||||
|
||||
|
||||
cc args...
|
||||
run the platform C compiler
|
||||
|
||||
|
||||
cd dir
|
||||
change the working directory
|
||||
|
||||
|
||||
chmod perm paths...
|
||||
change file mode bits
|
||||
|
||||
Changes the permissions of the named files or directories to
|
||||
be equal to perm.
|
||||
Only numerical permissions are supported.
|
||||
|
||||
cmp [-q] file1 file2
|
||||
compare files for differences
|
||||
|
||||
By convention, file1 is the actual data and file2 is the
|
||||
expected data.
|
||||
The command succeeds if the file contents are identical.
|
||||
File1 can be 'stdout' or 'stderr' to compare the stdout or
|
||||
stderr buffer from the most recent command.
|
||||
|
||||
cmpenv [-q] file1 file2
|
||||
compare files for differences, with environment expansion
|
||||
|
||||
By convention, file1 is the actual data and file2 is the
|
||||
expected data.
|
||||
The command succeeds if the file contents are identical
|
||||
after substituting variables from the script environment.
|
||||
File1 can be 'stdout' or 'stderr' to compare the script's
|
||||
stdout or stderr buffer.
|
||||
|
||||
cp src... dst
|
||||
copy files to a target file or directory
|
||||
|
||||
src can include 'stdout' or 'stderr' to copy from the
|
||||
script's stdout or stderr buffer.
|
||||
|
||||
echo string...
|
||||
display a line of text
|
||||
|
||||
|
||||
env [key[=value]...]
|
||||
set or log the values of environment variables
|
||||
|
||||
With no arguments, print the script environment to the log.
|
||||
Otherwise, add the listed key=value pairs to the environment
|
||||
or print the listed keys.
|
||||
|
||||
exec program [args...] [&]
|
||||
run an executable program with arguments
|
||||
|
||||
Note that 'exec' does not terminate the script (unlike Unix
|
||||
shells).
|
||||
|
||||
exists [-readonly] [-exec] file...
|
||||
check that files exist
|
||||
|
||||
|
||||
go [args...] [&]
|
||||
run the 'go' program provided by the script host
|
||||
|
||||
|
||||
grep [-count=N] [-q] 'pattern' file
|
||||
find lines in a file that match a pattern
|
||||
|
||||
The command succeeds if at least one match (or the exact
|
||||
count, if given) is found.
|
||||
The -q flag suppresses printing of matches.
|
||||
|
||||
help [-v] name...
|
||||
log help text for commands and conditions
|
||||
|
||||
To display help for a specific condition, enclose it in
|
||||
brackets: 'help [amd64]'.
|
||||
To display complete documentation when listing all commands,
|
||||
pass the -v flag.
|
||||
|
||||
mkdir path...
|
||||
create directories, if they do not already exist
|
||||
|
||||
Unlike Unix mkdir, parent directories are always created if
|
||||
needed.
|
||||
|
||||
mv old new
|
||||
rename a file or directory to a new path
|
||||
|
||||
OS-specific restrictions may apply when old and new are in
|
||||
different directories.
|
||||
|
||||
replace [old new]... file
|
||||
replace strings in a file
|
||||
|
||||
The 'old' and 'new' arguments are unquoted as if in quoted
|
||||
Go strings.
|
||||
|
||||
rm path...
|
||||
remove a file or directory
|
||||
|
||||
If the path is a directory, its contents are removed
|
||||
recursively.
|
||||
|
||||
skip [msg]
|
||||
skip the current test
|
||||
|
||||
|
||||
sleep duration [&]
|
||||
sleep for a specified duration
|
||||
|
||||
The duration must be given as a Go time.Duration string.
|
||||
|
||||
stderr [-count=N] [-q] 'pattern' file
|
||||
find lines in the stderr buffer that match a pattern
|
||||
|
||||
The command succeeds if at least one match (or the exact
|
||||
count, if given) is found.
|
||||
The -q flag suppresses printing of matches.
|
||||
|
||||
stdout [-count=N] [-q] 'pattern' file
|
||||
find lines in the stdout buffer that match a pattern
|
||||
|
||||
The command succeeds if at least one match (or the exact
|
||||
count, if given) is found.
|
||||
The -q flag suppresses printing of matches.
|
||||
|
||||
stop [msg]
|
||||
stop execution of the script
|
||||
|
||||
The message is written to the script log, but no error is
|
||||
reported from the script engine.
|
||||
|
||||
symlink path -> target
|
||||
create a symlink
|
||||
|
||||
Creates path as a symlink to target.
|
||||
The '->' token (like in 'ls -l' output on Unix) is required.
|
||||
|
||||
wait
|
||||
wait for completion of background commands
|
||||
|
||||
Waits for all background commands to complete.
|
||||
The output (and any error) from each command is printed to
|
||||
the log in the order in which the commands were started.
|
||||
After the call to 'wait', the script's stdout and stderr
|
||||
buffers contain the concatenation of the background
|
||||
commands' outputs.
|
||||
|
||||
|
||||
|
||||
The available conditions are:
|
||||
[GOARCH:*]
|
||||
runtime.GOARCH == <suffix>
|
||||
[GODEBUG:*]
|
||||
GODEBUG contains <suffix>
|
||||
[GOEXPERIMENT:*]
|
||||
GOEXPERIMENT <suffix> is enabled
|
||||
[GOOS:*]
|
||||
runtime.GOOS == <suffix>
|
||||
[asan]
|
||||
GOOS/GOARCH supports -asan
|
||||
[buildmode:*]
|
||||
go supports -buildmode=<suffix>
|
||||
[cgo]
|
||||
host CGO_ENABLED
|
||||
[cgolinkext]
|
||||
platform requires external linking for cgo
|
||||
[compiler:*]
|
||||
runtime.Compiler == <suffix>
|
||||
[cross]
|
||||
cmd/go GOOS/GOARCH != GOHOSTOS/GOHOSTARCH
|
||||
[exec:*]
|
||||
<suffix> names an executable in the test binary's PATH
|
||||
[fuzz]
|
||||
GOOS/GOARCH supports -fuzz
|
||||
[fuzz-instrumented]
|
||||
GOOS/GOARCH supports -fuzz with instrumentation
|
||||
[go-builder]
|
||||
GO_BUILDER_NAME is non-empty
|
||||
[link]
|
||||
testenv.HasLink()
|
||||
[msan]
|
||||
GOOS/GOARCH supports -msan
|
||||
[mustlinkext]
|
||||
platform always requires external linking
|
||||
[pielinkext]
|
||||
platform requires external linking for PIE
|
||||
[race]
|
||||
GOOS/GOARCH supports -race
|
||||
[root]
|
||||
os.Geteuid() == 0
|
||||
[short]
|
||||
testing.Short()
|
||||
[symlink]
|
||||
testenv.HasSymlink()
|
||||
[verbose]
|
||||
testing.Verbose()
|
||||
|
||||
@ -137,6 +137,9 @@ func RunToolScriptTest(t *testing.T, repls []ToolReplacement, scriptsdir string,
|
||||
env := os.Environ()
|
||||
prependToPath(env, filepath.Join(tgr, "bin"))
|
||||
env = setenv(env, "GOROOT", tgr)
|
||||
// GOOS and GOARCH are expected to be set by the toolchain script conditions.
|
||||
env = setenv(env, "GOOS", runtime.GOOS)
|
||||
env = setenv(env, "GOARCH", runtime.GOARCH)
|
||||
for _, repl := range repls {
|
||||
// consistency check
|
||||
chunks := strings.Split(repl.EnvVar, "=")
|
||||
|
||||
@ -156,10 +156,102 @@ func testDWARF(t *testing.T, buildmode string, expectDWARF bool, env ...string)
|
||||
if !strings.HasSuffix(line.File.Name, wantFile) || line.Line != wantLine {
|
||||
t.Errorf("%#x is %s:%d, want %s:%d", addr, line.File.Name, line.Line, filepath.Join("...", wantFile), wantLine)
|
||||
}
|
||||
|
||||
if buildmode != "c-archive" {
|
||||
testModuledata(t, d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testModuledata makes sure that runtime.firstmoduledata exists
|
||||
// and has a type. Issue #76731.
|
||||
func testModuledata(t *testing.T, d *dwarf.Data) {
|
||||
const symName = "runtime.firstmoduledata"
|
||||
|
||||
r := d.Reader()
|
||||
for {
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if e == nil {
|
||||
t.Errorf("did not find DWARF entry for %s", symName)
|
||||
return
|
||||
}
|
||||
|
||||
switch e.Tag {
|
||||
case dwarf.TagVariable:
|
||||
// carry on after switch
|
||||
case dwarf.TagCompileUnit, dwarf.TagSubprogram:
|
||||
continue
|
||||
default:
|
||||
r.SkipChildren()
|
||||
continue
|
||||
}
|
||||
|
||||
nameIdx, typeIdx := -1, -1
|
||||
for i := range e.Field {
|
||||
f := &e.Field[i]
|
||||
switch f.Attr {
|
||||
case dwarf.AttrName:
|
||||
nameIdx = i
|
||||
case dwarf.AttrType:
|
||||
typeIdx = i
|
||||
}
|
||||
}
|
||||
if nameIdx == -1 {
|
||||
// unnamed variable?
|
||||
r.SkipChildren()
|
||||
continue
|
||||
}
|
||||
nameStr, ok := e.Field[nameIdx].Val.(string)
|
||||
if !ok {
|
||||
// variable name is not a string?
|
||||
r.SkipChildren()
|
||||
continue
|
||||
}
|
||||
if nameStr != symName {
|
||||
r.SkipChildren()
|
||||
continue
|
||||
}
|
||||
|
||||
if typeIdx == -1 {
|
||||
t.Errorf("%s has no DWARF type", symName)
|
||||
return
|
||||
}
|
||||
off, ok := e.Field[typeIdx].Val.(dwarf.Offset)
|
||||
if !ok {
|
||||
t.Errorf("unexpected Go type %T for DWARF type for %s; expected %T", e.Field[typeIdx].Val, symName, dwarf.Offset(0))
|
||||
return
|
||||
}
|
||||
|
||||
typeInfo, err := d.Type(off)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
typeName := typeInfo.Common().Name
|
||||
if want := "runtime.moduledata"; typeName != want {
|
||||
t.Errorf("type of %s is %s, expected %s", symName, typeName, want)
|
||||
}
|
||||
for {
|
||||
typedef, ok := typeInfo.(*dwarf.TypedefType)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
typeInfo = typedef.Type
|
||||
}
|
||||
if _, ok := typeInfo.(*dwarf.StructType); !ok {
|
||||
t.Errorf("type of %s is %T, expected %T", symName, typeInfo, dwarf.StructType{})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDWARF(t *testing.T) {
|
||||
testDWARF(t, "", true)
|
||||
if !testing.Short() {
|
||||
|
||||
@ -2036,7 +2036,7 @@ func dwarfGenerateDebugInfo(ctxt *Link) {
|
||||
t := d.ldr.SymType(idx)
|
||||
switch {
|
||||
case t.IsRODATA(), t.IsDATA(), t.IsNOPTRDATA(),
|
||||
t == sym.STYPE, t == sym.SBSS, t == sym.SNOPTRBSS, t == sym.STLSBSS:
|
||||
t == sym.STYPE, t == sym.SBSS, t == sym.SNOPTRBSS, t == sym.STLSBSS, t == sym.SMODULEDATA:
|
||||
// ok
|
||||
default:
|
||||
continue
|
||||
|
||||
@ -55,13 +55,20 @@ type pclntab struct {
|
||||
|
||||
// addGeneratedSym adds a generator symbol to pclntab, returning the new Sym.
|
||||
// It is the caller's responsibility to save the symbol in state.
|
||||
func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, f generatorFunc) loader.Sym {
|
||||
func (state *pclntab) addGeneratedSym(ctxt *Link, name string, size int64, align int32, f generatorFunc) loader.Sym {
|
||||
size = Rnd(size, int64(ctxt.Arch.PtrSize))
|
||||
state.size += size
|
||||
s := ctxt.createGeneratorSymbol(name, 0, sym.SPCLNTAB, size, f)
|
||||
ctxt.loader.SetAttrReachable(s, true)
|
||||
ctxt.loader.SetCarrierSym(s, state.carrier)
|
||||
ctxt.loader.SetAttrNotInSymbolTable(s, true)
|
||||
ldr := ctxt.loader
|
||||
ldr.SetSymAlign(s, align)
|
||||
ldr.SetAttrReachable(s, true)
|
||||
ldr.SetCarrierSym(s, state.carrier)
|
||||
ldr.SetAttrNotInSymbolTable(s, true)
|
||||
|
||||
if align > ldr.SymAlign(state.carrier) {
|
||||
ldr.SetSymAlign(state.carrier, align)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
@ -277,7 +284,7 @@ func (state *pclntab) generatePCHeader(ctxt *Link) {
|
||||
}
|
||||
}
|
||||
|
||||
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, writeHeader)
|
||||
state.pcheader = state.addGeneratedSym(ctxt, "runtime.pcheader", size, int32(ctxt.Arch.PtrSize), writeHeader)
|
||||
}
|
||||
|
||||
// walkFuncs iterates over the funcs, calling a function for each unique
|
||||
@ -326,7 +333,7 @@ func (state *pclntab) generateFuncnametab(ctxt *Link, funcs []loader.Sym) map[lo
|
||||
size += int64(len(ctxt.loader.SymName(s)) + 1) // NULL terminate
|
||||
})
|
||||
|
||||
state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, writeFuncNameTab)
|
||||
state.funcnametab = state.addGeneratedSym(ctxt, "runtime.funcnametab", size, 1, writeFuncNameTab)
|
||||
return nameOffsets
|
||||
}
|
||||
|
||||
@ -442,7 +449,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat
|
||||
}
|
||||
}
|
||||
}
|
||||
state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), writeCutab)
|
||||
state.cutab = state.addGeneratedSym(ctxt, "runtime.cutab", int64(totalEntries*4), 4, writeCutab)
|
||||
|
||||
// Write filetab.
|
||||
writeFiletab := func(ctxt *Link, s loader.Sym) {
|
||||
@ -454,7 +461,7 @@ func (state *pclntab) generateFilenameTabs(ctxt *Link, compUnits []*sym.Compilat
|
||||
}
|
||||
}
|
||||
state.nfiles = uint32(len(fileOffsets))
|
||||
state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, writeFiletab)
|
||||
state.filetab = state.addGeneratedSym(ctxt, "runtime.filetab", fileSize, 1, writeFiletab)
|
||||
|
||||
return cuOffsets
|
||||
}
|
||||
@ -518,7 +525,7 @@ func (state *pclntab) generatePctab(ctxt *Link, funcs []loader.Sym) {
|
||||
}
|
||||
}
|
||||
|
||||
state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, writePctab)
|
||||
state.pctab = state.addGeneratedSym(ctxt, "runtime.pctab", size, 1, writePctab)
|
||||
}
|
||||
|
||||
// generateFuncdata writes out the funcdata information.
|
||||
@ -647,7 +654,7 @@ func (state *pclntab) generateFuncdata(ctxt *Link, funcs []loader.Sym, inlsyms m
|
||||
}
|
||||
}
|
||||
|
||||
state.funcdata = state.addGeneratedSym(ctxt, "go:func.*", size, writeFuncData)
|
||||
state.funcdata = state.addGeneratedSym(ctxt, "go:func.*", size, maxAlign, writeFuncData)
|
||||
|
||||
// Because the funcdata previously was not in pclntab,
|
||||
// we need to keep the visible symbol so that tools can find it.
|
||||
@ -703,7 +710,7 @@ func (state *pclntab) generateFunctab(ctxt *Link, funcs []loader.Sym, inlSyms ma
|
||||
writePCToFunc(ctxt, sb, funcs, startLocations)
|
||||
writeFuncs(ctxt, sb, funcs, inlSyms, startLocations, cuOffsets, nameOffsets)
|
||||
}
|
||||
state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, writePcln)
|
||||
state.pclntab = state.addGeneratedSym(ctxt, "runtime.functab", size, 4, writePcln)
|
||||
}
|
||||
|
||||
// funcData returns the funcdata and offsets for the FuncInfo.
|
||||
@ -967,6 +974,10 @@ func (ctxt *Link) pclntab(container loader.Bitmap) *pclntab {
|
||||
ldr.SetAttrReachable(state.carrier, true)
|
||||
setCarrierSym(sym.SPCLNTAB, state.carrier)
|
||||
|
||||
// Aign pclntab to at least a pointer boundary,
|
||||
// for pcHeader. This may be raised further by subsymbols.
|
||||
ldr.SetSymAlign(state.carrier, int32(ctxt.Arch.PtrSize))
|
||||
|
||||
state.generatePCHeader(ctxt)
|
||||
nameOffsets := state.generateFuncnametab(ctxt, funcs)
|
||||
cuOffsets := state.generateFilenameTabs(ctxt, compUnits, funcs)
|
||||
@ -1076,6 +1087,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
|
||||
}
|
||||
|
||||
state.findfunctab = ctxt.createGeneratorSymbol("runtime.findfunctab", 0, sym.SPCLNTAB, size, writeFindFuncTab)
|
||||
ldr.SetSymAlign(state.findfunctab, 4)
|
||||
ldr.SetAttrReachable(state.findfunctab, true)
|
||||
ldr.SetAttrLocal(state.findfunctab, true)
|
||||
}
|
||||
|
||||
@ -672,6 +672,7 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
|
||||
addRef("runtime.rodata")
|
||||
addRef("runtime.erodata")
|
||||
addRef("runtime.epclntab")
|
||||
addRef("go:func.*")
|
||||
// As we use relative addressing for text symbols in functab, it is
|
||||
// important that the offsets we computed stay unchanged by the external
|
||||
// linker, i.e. all symbols in Textp should not be removed.
|
||||
|
||||
@ -603,14 +603,20 @@ func xcoffUpdateOuterSize(ctxt *Link, size int64, stype sym.SymKind) {
|
||||
outerSymSize["go:string.*"] = size
|
||||
case sym.SGOFUNC:
|
||||
if !ctxt.DynlinkingGo() {
|
||||
outerSymSize["go:func.*"] = size
|
||||
outerSymSize["go:funcdesc"] = size
|
||||
}
|
||||
case sym.SGOFUNCRELRO:
|
||||
outerSymSize["go:funcrel.*"] = size
|
||||
outerSymSize["go:funcdescrel"] = size
|
||||
case sym.SGCBITS:
|
||||
outerSymSize["runtime.gcbits.*"] = size
|
||||
case sym.SPCLNTAB:
|
||||
outerSymSize["runtime.pclntab"] = size
|
||||
// go:func.* size must be removed from pclntab,
|
||||
// as it's a real symbol. Same for runtime.findfunctab.
|
||||
fsize := ldr.SymSize(ldr.Lookup("go:func.*", 0))
|
||||
fft := ldr.Lookup("runtime.findfunctab", 0)
|
||||
fsize = Rnd(fsize, int64(symalign(ldr, fft)))
|
||||
tsize := ldr.SymSize(fft)
|
||||
outerSymSize["runtime.pclntab"] = size - (fsize + tsize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1960,33 +1960,55 @@ func TestFuncdataPlacement(t *testing.T) {
|
||||
case xf != nil:
|
||||
defer xf.Close()
|
||||
|
||||
var moddataSym, gofuncSym, pclntabSym, epclntabSym *xcoff.Symbol
|
||||
for _, sym := range xf.Symbols {
|
||||
switch sym.Name {
|
||||
case moddataSymName:
|
||||
moddataAddr = sym.Value
|
||||
moddataSym = sym
|
||||
case gofuncSymName:
|
||||
gofuncAddr = sym.Value
|
||||
gofuncSym = sym
|
||||
case "runtime.pclntab":
|
||||
pclntabSym = sym
|
||||
case "runtime.epclntab":
|
||||
epclntabSym = sym
|
||||
}
|
||||
}
|
||||
|
||||
for _, sec := range xf.Sections {
|
||||
if sec.Name == ".go.pclntab" {
|
||||
data, err := sec.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pclntab = data
|
||||
pclntabAddr = sec.VirtualAddress
|
||||
pclntabEnd = sec.VirtualAddress + sec.Size
|
||||
}
|
||||
if moddataAddr >= sec.VirtualAddress && moddataAddr < sec.VirtualAddress+sec.Size {
|
||||
data, err := sec.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
moddataBytes = data[moddataAddr-sec.VirtualAddress:]
|
||||
}
|
||||
if moddataSym == nil {
|
||||
t.Fatalf("could not find symbol %s", moddataSymName)
|
||||
}
|
||||
if gofuncSym == nil {
|
||||
t.Fatalf("could not find symbol %s", gofuncSymName)
|
||||
}
|
||||
if pclntabSym == nil {
|
||||
t.Fatal("could not find symbol runtime.pclntab")
|
||||
}
|
||||
if epclntabSym == nil {
|
||||
t.Fatal("could not find symbol runtime.epclntab")
|
||||
}
|
||||
|
||||
sec := xf.Sections[moddataSym.SectionNumber-1]
|
||||
data, err := sec.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
moddataBytes = data[moddataSym.Value:]
|
||||
moddataAddr = uint64(sec.VirtualAddress + moddataSym.Value)
|
||||
|
||||
sec = xf.Sections[gofuncSym.SectionNumber-1]
|
||||
gofuncAddr = uint64(sec.VirtualAddress + gofuncSym.Value)
|
||||
|
||||
if pclntabSym.SectionNumber != epclntabSym.SectionNumber {
|
||||
t.Fatalf("runtime.pclntab section %d != runtime.epclntab section %d", pclntabSym.SectionNumber, epclntabSym.SectionNumber)
|
||||
}
|
||||
sec = xf.Sections[pclntabSym.SectionNumber-1]
|
||||
data, err = sec.Data()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pclntab = data[pclntabSym.Value:epclntabSym.Value]
|
||||
pclntabAddr = uint64(sec.VirtualAddress + pclntabSym.Value)
|
||||
pclntabEnd = uint64(sec.VirtualAddress + epclntabSym.Value)
|
||||
|
||||
default:
|
||||
panic("can't happen")
|
||||
@ -2183,31 +2205,16 @@ func TestModuledataPlacement(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
case pf != nil:
|
||||
defer pf.Close()
|
||||
|
||||
// On Windows all the Go specific sections seem to
|
||||
// get stuffed into a few Windows sections,
|
||||
// so there is nothing to test here.
|
||||
|
||||
case xf != nil:
|
||||
defer xf.Close()
|
||||
|
||||
for _, sym := range xf.Symbols {
|
||||
if sym.Name == moddataSymName {
|
||||
if sym.SectionNumber == 0 {
|
||||
t.Errorf("moduledata not in a section")
|
||||
} else {
|
||||
sec := xf.Sections[sym.SectionNumber-1]
|
||||
if sec.Name != ".go.module" {
|
||||
t.Errorf("moduledata in section %s, not .go.module", sec.Name)
|
||||
}
|
||||
if sym.Value != sec.VirtualAddress {
|
||||
t.Errorf("moduledata address %#x != section start address %#x", sym.Value, sec.VirtualAddress)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case pf != nil, xf != nil:
|
||||
if pf != nil {
|
||||
defer pf.Close()
|
||||
}
|
||||
if xf != nil {
|
||||
defer xf.Close()
|
||||
}
|
||||
|
||||
// On Windows and AIX all the Go specific sections
|
||||
// get stuffed into a few sections,
|
||||
// so there is nothing to test here.
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,11 +118,6 @@ func testGoExec(t *testing.T, iscgo, isexternallinker bool) {
|
||||
"runtime.noptrdata": "D",
|
||||
}
|
||||
|
||||
if runtime.GOOS == "aix" && iscgo {
|
||||
// pclntab is moved to .data section on AIX.
|
||||
runtimeSyms["runtime.epclntab"] = "D"
|
||||
}
|
||||
|
||||
out, err = testenv.Command(t, testenv.Executable(t), exe).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("go tool nm: %v\n%s", err, string(out))
|
||||
|
||||
13
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
generated
vendored
13
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
generated
vendored
@ -199,12 +199,19 @@ are often used to express optionality.
|
||||
|
||||
omitzero: suggest replacing omitempty with omitzero for struct fields
|
||||
|
||||
The omitzero analyzer identifies uses of the `omitempty` JSON struct tag on
|
||||
fields that are themselves structs. The `omitempty` tag has no effect on
|
||||
struct-typed fields. The analyzer offers two suggestions: either remove the
|
||||
The omitzero analyzer identifies uses of the `omitempty` JSON struct
|
||||
tag on fields that are themselves structs. For struct-typed fields,
|
||||
the `omitempty` tag has no effect on the behavior of json.Marshal and
|
||||
json.Unmarshal. The analyzer offers two suggestions: either remove the
|
||||
tag, or replace it with `omitzero` (added in Go 1.24), which correctly
|
||||
omits the field if the struct value is zero.
|
||||
|
||||
However, some other serialization packages (notably kubebuilder, see
|
||||
https://book.kubebuilder.io/reference/markers.html) may have their own
|
||||
interpretation of the `json:",omitzero"` tag, so removing it may affect
|
||||
program behavior. For this reason, the omitzero modernizer will not
|
||||
make changes in any package that contains +kubebuilder annotations.
|
||||
|
||||
Replacing `omitempty` with `omitzero` is a change in behavior. The
|
||||
original code would always encode the struct field, whereas the
|
||||
modified code will omit it if it is a zero-value.
|
||||
|
||||
158
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go
generated
vendored
158
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go
generated
vendored
@ -9,6 +9,8 @@ import (
|
||||
"go/types"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
@ -25,82 +27,106 @@ var OmitZeroAnalyzer = &analysis.Analyzer{
|
||||
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) {
|
||||
typ := info.TypeOf(curField.Type)
|
||||
_, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
// Not a struct
|
||||
return
|
||||
}
|
||||
tag := curField.Tag
|
||||
if tag == nil {
|
||||
// No tag to check
|
||||
return
|
||||
}
|
||||
// The omitempty tag may be used by other packages besides json, but we should only modify its use with json
|
||||
tagconv, _ := strconv.Unquote(tag.Value)
|
||||
match := omitemptyRegex.FindStringSubmatchIndex(tagconv)
|
||||
if match == nil {
|
||||
// No omitempty in json tag
|
||||
return
|
||||
}
|
||||
omitEmpty, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
remove = curField.Tag
|
||||
} else {
|
||||
// Remove the json tag if omitempty is the only field
|
||||
remove, err = astutil.RangeInStringLiteral(curField.Tag, match[0], match[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: curField.Tag.Pos(),
|
||||
End: curField.Tag.End(),
|
||||
Message: "Omitempty has no effect on nested struct fields",
|
||||
SuggestedFixes: []analysis.SuggestedFix{
|
||||
{
|
||||
Message: "Remove redundant omitempty tag",
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: remove.Pos(),
|
||||
End: remove.End(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Replace omitempty with omitzero (behavior change)",
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: omitEmpty.Pos(),
|
||||
End: omitEmpty.End(),
|
||||
NewText: []byte(",omitzero"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
// The omitzero pass searches for instances of "omitempty" in a json field tag on a
|
||||
// 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) {
|
||||
// usesKubebuilder reports whether "+kubebuilder:" appears in
|
||||
// any comment in the package, since it has its own
|
||||
// interpretation of what omitzero means; see go.dev/issue/76649.
|
||||
// It is computed once, on demand.
|
||||
usesKubebuilder := sync.OnceValue[bool](func() bool {
|
||||
for _, file := range pass.Files {
|
||||
for _, comment := range file.Comments {
|
||||
if strings.Contains(comment.Text(), "+kubebuilder:") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
checkField := func(field *ast.Field) {
|
||||
typ := pass.TypesInfo.TypeOf(field.Type)
|
||||
_, ok := typ.Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
// Not a struct
|
||||
return
|
||||
}
|
||||
tag := field.Tag
|
||||
if tag == nil {
|
||||
// No tag to check
|
||||
return
|
||||
}
|
||||
// The omitempty tag may be used by other packages besides json, but we should only modify its use with json
|
||||
tagconv, _ := strconv.Unquote(tag.Value)
|
||||
match := omitemptyRegex.FindStringSubmatchIndex(tagconv)
|
||||
if match == nil {
|
||||
// No omitempty in json tag
|
||||
return
|
||||
}
|
||||
omitEmpty, err := astutil.RangeInStringLiteral(field.Tag, match[2], match[3])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
remove = field.Tag
|
||||
} else {
|
||||
// Remove the json tag if omitempty is the only field
|
||||
remove, err = astutil.RangeInStringLiteral(field.Tag, match[0], match[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't offer a fix if the package seems to use kubebuilder,
|
||||
// as it has its own intepretation of "omitzero" tags.
|
||||
// https://book.kubebuilder.io/reference/markers.html
|
||||
if usesKubebuilder() {
|
||||
return
|
||||
}
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: field.Tag.Pos(),
|
||||
End: field.Tag.End(),
|
||||
Message: "Omitempty has no effect on nested struct fields",
|
||||
SuggestedFixes: []analysis.SuggestedFix{
|
||||
{
|
||||
Message: "Remove redundant omitempty tag",
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: remove.Pos(),
|
||||
End: remove.End(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Message: "Replace omitempty with omitzero (behavior change)",
|
||||
TextEdits: []analysis.TextEdit{
|
||||
{
|
||||
Pos: omitEmpty.Pos(),
|
||||
End: omitEmpty.End(),
|
||||
NewText: []byte(",omitzero"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}})
|
||||
}
|
||||
|
||||
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, pass.TypesInfo, curField)
|
||||
checkField(curField)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
132
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
generated
vendored
132
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
generated
vendored
@ -50,9 +50,14 @@ func init() {
|
||||
// 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
|
||||
//
|
||||
// Binary expressions must be inequalities equivalent to
|
||||
// "Index failed" (e.g. i < 0) or "Index succeeded" (i >= 0),
|
||||
// or identities such as these (and their negations):
|
||||
//
|
||||
// 0 > i (flips left and right)
|
||||
// i <= -1, -1 >= i (replace strict inequality by non-strict)
|
||||
// i == -1, -1 == i (Index() guarantees i < 0 => i == -1)
|
||||
//
|
||||
// Slice expressions:
|
||||
// a: s[:i], s[0:i]
|
||||
@ -86,9 +91,9 @@ func init() {
|
||||
// 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 condition involving `i` is equivalent to i >= 0, then we replace it with
|
||||
// `if ok“.
|
||||
// If the condition is negated (e.g. equivalent to `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.
|
||||
//
|
||||
@ -178,16 +183,16 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
||||
// 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)
|
||||
negative, nonnegative, 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 {
|
||||
if negative == nil && nonnegative == 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
|
||||
isContains := (len(negative) > 0 || len(nonnegative) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
|
||||
|
||||
scope := iObj.Parent()
|
||||
var (
|
||||
@ -200,7 +205,7 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
||||
|
||||
// If there will be no uses of ok, before, or after, use the
|
||||
// blank identifier instead.
|
||||
if len(lessZero) == 0 && len(greaterNegOne) == 0 {
|
||||
if len(negative) == 0 && len(nonnegative) == 0 {
|
||||
okVarName = "_"
|
||||
}
|
||||
if len(beforeSlice) == 0 {
|
||||
@ -226,8 +231,8 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
||||
replacedFunc := "Cut"
|
||||
if isContains {
|
||||
replacedFunc = "Contains"
|
||||
replace(lessZero, "!"+foundVarName) // idx < 0 -> !found
|
||||
replace(greaterNegOne, foundVarName) // idx > -1 -> found
|
||||
replace(negative, "!"+foundVarName) // idx < 0 -> !found
|
||||
replace(nonnegative, foundVarName) // idx > -1 -> found
|
||||
|
||||
// Replace the assignment with found, and replace the call to
|
||||
// Index or IndexByte with a call to Contains.
|
||||
@ -244,8 +249,8 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
||||
NewText: []byte("Contains"),
|
||||
})
|
||||
} else {
|
||||
replace(lessZero, "!"+okVarName) // idx < 0 -> !ok
|
||||
replace(greaterNegOne, okVarName) // idx > -1 -> ok
|
||||
replace(negative, "!"+okVarName) // idx < 0 -> !ok
|
||||
replace(nonnegative, okVarName) // idx > -1 -> ok
|
||||
replace(beforeSlice, beforeVarName) // s[:idx] -> before
|
||||
replace(afterSlice, afterVarName) // s[idx+k:] -> after
|
||||
|
||||
@ -364,11 +369,11 @@ func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afte
|
||||
// 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)
|
||||
// 1. negative - a condition equivalent to i < 0
|
||||
// 2. nonnegative - a condition equivalent to i >= 0
|
||||
// 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) {
|
||||
func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) {
|
||||
use := func(cur inspector.Cursor) bool {
|
||||
ek, _ := cur.ParentEdge()
|
||||
n := cur.Parent().Node()
|
||||
@ -377,13 +382,13 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a
|
||||
check := n.(*ast.BinaryExpr)
|
||||
switch checkIdxComparison(info, check) {
|
||||
case -1:
|
||||
lessZero = append(lessZero, check)
|
||||
negative = append(negative, check)
|
||||
return true
|
||||
case 1:
|
||||
greaterNegOne = append(greaterNegOne, check)
|
||||
nonnegative = append(nonnegative, check)
|
||||
return true
|
||||
}
|
||||
// Check does not establish that i < 0 or i > -1.
|
||||
// Check is not equivalent to that i < 0 or i >= 0.
|
||||
// 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
|
||||
@ -421,7 +426,7 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a
|
||||
return nil, nil, nil, nil
|
||||
}
|
||||
}
|
||||
return lessZero, greaterNegOne, beforeSlice, afterSlice
|
||||
return negative, nonnegative, beforeSlice, afterSlice
|
||||
}
|
||||
|
||||
// hasModifyingUses reports whether any of the uses involve potential
|
||||
@ -451,52 +456,57 @@ func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPo
|
||||
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.
|
||||
// checkIdxComparison reports whether the check is equivalent to i < 0 or its negation, or neither.
|
||||
// For equivalent to i >= 0, we only accept this exact BinaryExpr since
|
||||
// expressions like i > 0 or i >= 1 make a stronger statement about the value of i.
|
||||
// We avoid suggesting a fix in this case since it may result in an invalid
|
||||
// transformation (See golang/go#76687).
|
||||
// Since strings.Index returns exactly -1 if the substring is not found, we
|
||||
// don't need to handle expressions like i <= -3.
|
||||
// We return 0 if the expression does not match any of these options.
|
||||
// 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
|
||||
// Ensure that the constant (if any) is on the right.
|
||||
x, op, y := check.X, check.Op, check.Y
|
||||
if info.Types[x].Value != nil {
|
||||
x, op, y = y, flip(op), x
|
||||
}
|
||||
// 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
|
||||
|
||||
yIsInt := func(k int64) bool {
|
||||
return isIntLiteral(info, y, k)
|
||||
}
|
||||
return 0
|
||||
|
||||
if op == token.LSS && yIsInt(0) || // i < 0
|
||||
op == token.EQL && yIsInt(-1) || // i == -1
|
||||
op == token.LEQ && yIsInt(-1) { // i <= -1
|
||||
return -1 // check <=> i is negative
|
||||
}
|
||||
|
||||
if op == token.GEQ && yIsInt(0) || // i >= 0
|
||||
op == token.NEQ && yIsInt(-1) || // i != -1
|
||||
op == token.GTR && yIsInt(-1) { // i > -1
|
||||
return +1 // check <=> i is non-negative
|
||||
}
|
||||
|
||||
return 0 // unknown
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// flip changes the comparison token as if the operands were flipped.
|
||||
// It is defined only for == and the four inequalities.
|
||||
func flip(op token.Token) token.Token {
|
||||
switch op {
|
||||
case token.EQL:
|
||||
return token.EQL // (same)
|
||||
case token.GEQ:
|
||||
return token.LEQ
|
||||
case token.GTR:
|
||||
return token.LSS
|
||||
case token.LEQ:
|
||||
return token.GEQ
|
||||
case token.LSS:
|
||||
return token.GTR
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isNonNegativeConst 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
|
||||
return op
|
||||
}
|
||||
|
||||
// isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i].
|
||||
|
||||
41
src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
generated
vendored
41
src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
generated
vendored
@ -27,6 +27,7 @@ package unitchecker
|
||||
// printf checker.
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
@ -74,6 +75,7 @@ type Config struct {
|
||||
VetxOnly bool // run analysis only for facts, not diagnostics
|
||||
VetxOutput string // where to write file of fact information
|
||||
Stdout string // write stdout (e.g. JSON, unified diff) to this file
|
||||
FixArchive string // write fixed files to this zip archive, if non-empty
|
||||
SucceedOnTypecheckFailure bool // obsolete awful hack; see #18395 and below
|
||||
}
|
||||
|
||||
@ -153,7 +155,7 @@ func Run(configFile string, analyzers []*analysis.Analyzer) {
|
||||
|
||||
// In VetxOnly mode, the analysis is run only for facts.
|
||||
if !cfg.VetxOnly {
|
||||
code = processResults(fset, cfg.ID, results)
|
||||
code = processResults(fset, cfg.ID, cfg.FixArchive, results)
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
@ -177,7 +179,7 @@ func readConfig(filename string) (*Config, error) {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func processResults(fset *token.FileSet, id string, results []result) (exit int) {
|
||||
func processResults(fset *token.FileSet, id, fixArchive string, results []result) (exit int) {
|
||||
if analysisflags.Fix {
|
||||
// Don't print the diagnostics,
|
||||
// but apply all fixes from the root actions.
|
||||
@ -194,7 +196,40 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
|
||||
Diagnostics: res.diagnostics,
|
||||
}
|
||||
}
|
||||
if err := driverutil.ApplyFixes(fixActions, analysisflags.Diff, false); err != nil {
|
||||
|
||||
// By default, fixes overwrite the original file.
|
||||
// With the -diff flag, print the diffs to stdout.
|
||||
// If "go fix" provides a fix archive, we write files
|
||||
// into it so that mutations happen after the build.
|
||||
write := func(filename string, content []byte) error {
|
||||
return os.WriteFile(filename, content, 0644)
|
||||
}
|
||||
if fixArchive != "" {
|
||||
f, err := os.Create(fixArchive)
|
||||
if err != nil {
|
||||
log.Fatalf("can't create -fix archive: %v", err)
|
||||
}
|
||||
zw := zip.NewWriter(f)
|
||||
zw.SetComment(id) // ignore error
|
||||
defer func() {
|
||||
if err := zw.Close(); err != nil {
|
||||
log.Fatalf("closing -fix archive zip writer: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatalf("closing -fix archive file: %v", err)
|
||||
}
|
||||
}()
|
||||
write = func(filename string, content []byte) error {
|
||||
f, err := zw.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(content)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, false); err != nil {
|
||||
// Fail when applying fixes failed.
|
||||
log.Print(err)
|
||||
exit = 1
|
||||
|
||||
13
src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go
generated
vendored
13
src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go
generated
vendored
@ -93,11 +93,13 @@ type FixAction struct {
|
||||
//
|
||||
// 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.
|
||||
// fixes. (It is tempting to factor printDiff as just a variant of
|
||||
// writeFile that is provided the old and new content, but it's hard
|
||||
// to generate a good summary that way.)
|
||||
//
|
||||
// TODO(adonovan): handle file-system level aliases such as symbolic
|
||||
// links using robustio.FileID.
|
||||
func ApplyFixes(actions []FixAction, printDiff, verbose bool) error {
|
||||
func ApplyFixes(actions []FixAction, writeFile func(filename string, content []byte) error, printDiff, verbose bool) error {
|
||||
generated := make(map[*token.File]bool)
|
||||
|
||||
// Select fixes to apply.
|
||||
@ -264,12 +266,11 @@ fixloop:
|
||||
os.Stdout.WriteString(unified)
|
||||
|
||||
} else {
|
||||
// write
|
||||
// write file
|
||||
totalFiles++
|
||||
// TODO(adonovan): abstract the I/O.
|
||||
if err := os.WriteFile(file, final, 0644); err != nil {
|
||||
if err := writeFile(file, final); err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
continue // (causes ApplyFix to return an error)
|
||||
}
|
||||
filesUpdated++
|
||||
}
|
||||
|
||||
44
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
generated
vendored
44
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
generated
vendored
@ -725,8 +725,48 @@ func analyzeAssignment(info *types.Info, stack []ast.Node) (assignable, ifaceAss
|
||||
paramType := paramTypeAtIndex(sig, call, i)
|
||||
ifaceAssign := paramType == nil || types.IsInterface(paramType)
|
||||
affectsInference := false
|
||||
if fn := typeutil.StaticCallee(info, call); fn != nil {
|
||||
if sig2 := fn.Type().(*types.Signature); sig2.Recv() == nil {
|
||||
switch callee := typeutil.Callee(info, call).(type) {
|
||||
case *types.Builtin:
|
||||
// Consider this litmus test:
|
||||
//
|
||||
// func f(x int64) any { return max(x) }
|
||||
// func main() { fmt.Printf("%T", f(42)) }
|
||||
//
|
||||
// If we lose the implicit conversion from untyped int
|
||||
// to int64, the type inferred for the max(x) call changes,
|
||||
// resulting in a different dynamic behavior: it prints
|
||||
// int, not int64.
|
||||
//
|
||||
// Inferred result type affected:
|
||||
// new
|
||||
// complex, real, imag
|
||||
// min, max
|
||||
//
|
||||
// Dynamic behavior change:
|
||||
// append -- dynamic type of append([]any(nil), x)[0]
|
||||
// delete(m, x) -- dynamic key type where m is map[any]unit
|
||||
// panic -- dynamic type of panic value
|
||||
//
|
||||
// Unaffected:
|
||||
// recover
|
||||
// make
|
||||
// len, cap
|
||||
// clear
|
||||
// close
|
||||
// copy
|
||||
// print, println -- only uses underlying types (?)
|
||||
//
|
||||
// The dynamic type cases are all covered by
|
||||
// the ifaceAssign logic.
|
||||
switch callee.Name() {
|
||||
case "new", "complex", "real", "imag", "min", "max":
|
||||
affectsInference = true
|
||||
}
|
||||
|
||||
case *types.Func:
|
||||
// Only standalone (non-method) functions have type
|
||||
// parameters affected by the call arguments.
|
||||
if sig2 := callee.Signature(); sig2.Recv() == nil {
|
||||
originParamType := paramTypeAtIndex(sig2, call, i)
|
||||
affectsInference = originParamType == nil || new(typeparams.Free).Has(originParamType)
|
||||
}
|
||||
|
||||
2
src/cmd/vendor/modules.txt
vendored
2
src/cmd/vendor/modules.txt
vendored
@ -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.39.1-0.20251130212600-1ad6f3d02713
|
||||
# golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2
|
||||
## explicit; go 1.24.0
|
||||
golang.org/x/tools/cmd/bisect
|
||||
golang.org/x/tools/cover
|
||||
|
||||
@ -27,7 +27,7 @@ func (c *nistCurve) String() string {
|
||||
}
|
||||
|
||||
func (c *nistCurve) GenerateKey(r io.Reader) (*PrivateKey, error) {
|
||||
if boring.Enabled && r == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(r) {
|
||||
key, bytes, err := boring.GenerateKeyECDH(c.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -334,7 +334,7 @@ func (priv *PrivateKey) Sign(random io.Reader, digest []byte, opts crypto.Signer
|
||||
// ignored unless GODEBUG=cryptocustomrand=1 is set. This setting will be removed
|
||||
// in a future Go release. Instead, use [testing/cryptotest.SetGlobalRandom].
|
||||
func GenerateKey(c elliptic.Curve, r io.Reader) (*PrivateKey, error) {
|
||||
if boring.Enabled && r == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(r) {
|
||||
x, y, d, err := boring.GenerateKeyECDSA(c.Params().Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -380,7 +380,7 @@ func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], ran
|
||||
// is set. This setting will be removed in a future Go release. Instead, use
|
||||
// [testing/cryptotest.SetGlobalRandom].
|
||||
func SignASN1(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) {
|
||||
if boring.Enabled && r == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(r) {
|
||||
b, err := boringPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -17,7 +17,6 @@ package ed25519
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/internal/fips140/drbg"
|
||||
"crypto/internal/fips140/ed25519"
|
||||
"crypto/internal/fips140cache"
|
||||
"crypto/internal/fips140only"
|
||||
@ -153,7 +152,7 @@ func GenerateKey(random io.Reader) (PublicKey, PrivateKey, error) {
|
||||
if random == nil {
|
||||
if cryptocustomrand.Value() == "1" {
|
||||
random = cryptorand.Reader
|
||||
if _, ok := random.(drbg.DefaultReader); !ok {
|
||||
if !rand.IsDefaultReader(random) {
|
||||
cryptocustomrand.IncNonDefault()
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -55,7 +55,7 @@ var cryptocustomrand = godebug.New("cryptocustomrand")
|
||||
// If returning a non-default Reader, it calls [randutil.MaybeReadByte] on it.
|
||||
func CustomReader(r io.Reader) io.Reader {
|
||||
if cryptocustomrand.Value() == "1" {
|
||||
if _, ok := r.(drbg.DefaultReader); !ok {
|
||||
if !IsDefaultReader(r) {
|
||||
randutil.MaybeReadByte(r)
|
||||
cryptocustomrand.IncNonDefault()
|
||||
}
|
||||
@ -63,3 +63,11 @@ func CustomReader(r io.Reader) io.Reader {
|
||||
}
|
||||
return Reader
|
||||
}
|
||||
|
||||
// IsDefaultReader reports whether r is the default [crypto/rand.Reader].
|
||||
//
|
||||
// If true, the Read method of r can be assumed to call [drbg.Read].
|
||||
func IsDefaultReader(r io.Reader) bool {
|
||||
_, ok := r.(drbg.DefaultReader)
|
||||
return ok
|
||||
}
|
||||
|
||||
@ -153,7 +153,7 @@ var data = Data{
|
||||
0x8d2a4c8a,
|
||||
},
|
||||
Table3: []uint32{
|
||||
// round3
|
||||
// round 3
|
||||
0xfffa3942,
|
||||
0x8771f681,
|
||||
0x6d9d6122,
|
||||
|
||||
@ -31,15 +31,7 @@ import (
|
||||
//
|
||||
// In FIPS 140-3 mode, the output passes through an SP 800-90A Rev. 1
|
||||
// Deterministric Random Bit Generator (DRBG).
|
||||
var Reader io.Reader
|
||||
|
||||
func init() {
|
||||
if boring.Enabled {
|
||||
Reader = boring.RandReader
|
||||
return
|
||||
}
|
||||
Reader = rand.Reader
|
||||
}
|
||||
var Reader io.Reader = rand.Reader
|
||||
|
||||
// fatal is [runtime.fatal], pushed via linkname.
|
||||
//
|
||||
@ -57,9 +49,12 @@ func Read(b []byte) (n int, err error) {
|
||||
// through a potentially overridden Reader, so we special-case the default
|
||||
// case which we can keep non-escaping, and in the general case we read into
|
||||
// a heap buffer and copy from it.
|
||||
if _, ok := Reader.(drbg.DefaultReader); ok {
|
||||
boring.Unreachable()
|
||||
drbg.Read(b)
|
||||
if rand.IsDefaultReader(Reader) {
|
||||
if boring.Enabled {
|
||||
_, err = io.ReadFull(boring.RandReader, b)
|
||||
} else {
|
||||
drbg.Read(b)
|
||||
}
|
||||
} else {
|
||||
bb := make([]byte, len(b))
|
||||
_, err = io.ReadFull(Reader, bb)
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"crypto/internal/fips140/rsa"
|
||||
"crypto/internal/fips140hash"
|
||||
"crypto/internal/fips140only"
|
||||
"crypto/internal/rand"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
@ -59,9 +60,9 @@ func (opts *PSSOptions) saltLength() int {
|
||||
// used. If opts.Hash is set, it overrides hash.
|
||||
//
|
||||
// The signature is randomized depending on the message, key, and salt size,
|
||||
// using bytes from rand. Most applications should use [crypto/rand.Reader] as
|
||||
// rand.
|
||||
func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) {
|
||||
// using bytes from random. Most applications should use [crypto/rand.Reader] as
|
||||
// random.
|
||||
func SignPSS(random io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) {
|
||||
if err := checkPublicKeySize(&priv.PublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -70,7 +71,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
|
||||
hash = opts.Hash
|
||||
}
|
||||
|
||||
if boring.Enabled && rand == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(random) {
|
||||
bkey, err := boringPrivateKey(priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -87,7 +88,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
|
||||
if fips140only.Enforced() && !fips140only.ApprovedHash(h) {
|
||||
return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode")
|
||||
}
|
||||
if fips140only.Enforced() && !fips140only.ApprovedRandomReader(rand) {
|
||||
if fips140only.Enforced() && !fips140only.ApprovedRandomReader(random) {
|
||||
return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode")
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte,
|
||||
}
|
||||
}
|
||||
|
||||
return fipsError2(rsa.SignPSS(rand, k, h, digest, saltLength))
|
||||
return fipsError2(rsa.SignPSS(random, k, h, digest, saltLength))
|
||||
}
|
||||
|
||||
// VerifyPSS verifies a PSS signature.
|
||||
@ -216,7 +217,7 @@ func encryptOAEP(hash hash.Hash, mgfHash hash.Hash, random io.Reader, pub *Publi
|
||||
defer hash.Reset()
|
||||
defer mgfHash.Reset()
|
||||
|
||||
if boring.Enabled && random == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(random) {
|
||||
k := pub.Size()
|
||||
if len(msg) > k-2*hash.Size()-2 {
|
||||
return nil, ErrMessageTooLong
|
||||
|
||||
@ -61,7 +61,7 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro
|
||||
return nil, ErrMessageTooLong
|
||||
}
|
||||
|
||||
if boring.Enabled && random == boring.RandReader {
|
||||
if boring.Enabled && rand.IsDefaultReader(random) {
|
||||
bkey, err := boringPublicKey(pub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -314,7 +314,7 @@ func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if boring.Enabled && random == boring.RandReader &&
|
||||
if boring.Enabled && rand.IsDefaultReader(random) &&
|
||||
(bits == 2048 || bits == 3072 || bits == 4096) {
|
||||
bN, bE, bD, bP, bQ, bDp, bDq, bQinv, err := boring.GenerateKeyRSA(bits)
|
||||
if err != nil {
|
||||
|
||||
@ -81,7 +81,7 @@ type SessionState struct {
|
||||
version uint16
|
||||
isClient bool
|
||||
cipherSuite uint16
|
||||
// createdAt is the generation time of the secret on the sever (which for
|
||||
// createdAt is the generation time of the secret on the server (which for
|
||||
// TLS 1.0–1.2 might be earlier than the current session) and the time at
|
||||
// which the ticket was received on the client.
|
||||
createdAt uint64 // seconds since UNIX epoch
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
|
||||
//go:build ignore
|
||||
|
||||
// encgen writes the helper functions for encoding. Intended to be
|
||||
// used with go generate; see the invocation in encode.go.
|
||||
// decgen writes the helper functions for decoding. Intended to be
|
||||
// used with go generate; see the invocation in decode.go.
|
||||
|
||||
// TODO: We could do more by being unsafe. Add a -unsafe flag?
|
||||
|
||||
|
||||
@ -370,7 +370,7 @@ func (f inspector) Visit(node Node) Visitor {
|
||||
// call of f(nil).
|
||||
//
|
||||
// In many cases it may be more convenient to use [Preorder], which
|
||||
// returns an iterator over the sqeuence of nodes, or [PreorderStack],
|
||||
// returns an iterator over the sequence of nodes, or [PreorderStack],
|
||||
// which (like [Inspect]) provides control over descent into subtrees,
|
||||
// but additionally reports the stack of enclosing nodes.
|
||||
func Inspect(node Node, f func(Node) bool) {
|
||||
|
||||
@ -1260,8 +1260,10 @@ func (t *table) grow(typ *abi.MapType, m *Map, newCapacity uint16) {
|
||||
|
||||
// probeSeq maintains the state for a probe sequence that iterates through the
|
||||
// groups in a table. The sequence is a triangular progression of the form
|
||||
// hash, hash + 1, hash + 1 + 2, hash + 1 + 2 + 3, ..., modulo mask + 1.
|
||||
// The i-th term of the sequence is
|
||||
//
|
||||
// p(i) := (i^2 + i)/2 + hash (mod mask+1)
|
||||
// p(i) := hash + (i^2 + i)/2 (mod mask+1)
|
||||
//
|
||||
// The sequence effectively outputs the indexes of *groups*. The group
|
||||
// machinery allows us to check an entire group with minimal branching.
|
||||
|
||||
@ -9,6 +9,11 @@ TEXT ·EnableDIT(SB),$0-1
|
||||
UBFX $24, R0, $1, R1
|
||||
MOVB R1, ret+0(FP)
|
||||
MSR $1, DIT
|
||||
// TODO(roland): the SB instruction is significantly more
|
||||
// performant when available. We should detect its availability
|
||||
// and use it when we can.
|
||||
DSB $7 // nsh
|
||||
ISB $15 // sy
|
||||
RET
|
||||
|
||||
TEXT ·DITEnabled(SB),$0-1
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
var (
|
||||
logEvents = flag.Bool("log-events", false, "whether to log high-level events; significantly slows down tests")
|
||||
dumpTraces = flag.Bool("dump-traces", false, "dump traces even on success")
|
||||
allocFree = flag.Bool("alloc-free", false, "run alloc/free trace experiment tests")
|
||||
)
|
||||
|
||||
func TestReaderGolden(t *testing.T) {
|
||||
|
||||
@ -697,6 +697,9 @@ func testTraceProg(t *testing.T, progName string, extra func(t *testing.T, trace
|
||||
runTest(t, true, "")
|
||||
})
|
||||
t.Run("AllocFree", func(t *testing.T) {
|
||||
if !*allocFree {
|
||||
t.Skip("skipping trace alloc/free tests by default; too flaky (see go.dev/issue/70838)")
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("skipping trace alloc/free tests in short mode")
|
||||
}
|
||||
|
||||
@ -443,7 +443,8 @@ func isCookieDomainName(s string) bool {
|
||||
}
|
||||
|
||||
if s[0] == '.' {
|
||||
// A cookie a domain attribute may start with a leading dot.
|
||||
// A cookie domain attribute may start with a leading dot.
|
||||
// Per RFC 6265 section 5.2.3, a leading dot is ignored.
|
||||
s = s[1:]
|
||||
}
|
||||
last := byte('.')
|
||||
|
||||
@ -97,6 +97,13 @@ func runTestProg(t *testing.T, binary, name string, env ...string) string {
|
||||
func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string {
|
||||
t.Helper()
|
||||
|
||||
out, _ := runBuiltTestProgErr(t, exe, name, env...)
|
||||
return out
|
||||
}
|
||||
|
||||
func runBuiltTestProgErr(t *testing.T, exe, name string, env ...string) (string, error) {
|
||||
t.Helper()
|
||||
|
||||
if *flagQuick {
|
||||
t.Skip("-quick")
|
||||
}
|
||||
@ -120,7 +127,7 @@ func runBuiltTestProg(t *testing.T, exe, name string, env ...string) string {
|
||||
t.Fatalf("%v failed to start: %v", cmd, err)
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
var serializeBuild = make(chan bool, 2)
|
||||
|
||||
@ -551,8 +551,11 @@ func MapNextArenaHint() (start, end uintptr, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func GetNextArenaHint() uintptr {
|
||||
return mheap_.arenaHints.addr
|
||||
func NextArenaHint() (uintptr, bool) {
|
||||
if mheap_.arenaHints == nil {
|
||||
return 0, false
|
||||
}
|
||||
return mheap_.arenaHints.addr, true
|
||||
}
|
||||
|
||||
type G = g
|
||||
|
||||
@ -14,10 +14,13 @@ import (
|
||||
)
|
||||
|
||||
func TestGoroutineLeakProfile(t *testing.T) {
|
||||
if strings.Contains(os.Getenv("GOFLAGS"), "mayMoreStackPreempt") {
|
||||
// Some tests have false negatives under mayMoreStackPreempt. This may be a test-only issue,
|
||||
// but needs more investigation.
|
||||
testenv.SkipFlaky(t, 75729)
|
||||
// Some tests have false negatives under mayMoreStackPreempt and mayMoreStackMove.
|
||||
// This may be a test-only issue in that they're just sensitive to scheduling, but it
|
||||
// needs more investigation.
|
||||
for _, cfg := range []string{"mayMoreStackPreempt", "mayMoreStackMove"} {
|
||||
if strings.Contains(os.Getenv("GOFLAGS"), cfg) {
|
||||
testenv.SkipFlaky(t, 75729)
|
||||
}
|
||||
}
|
||||
|
||||
// Goroutine leak test case.
|
||||
@ -486,10 +489,7 @@ func TestGoroutineLeakProfile(t *testing.T) {
|
||||
testCases := append(microTests, stressTestCases...)
|
||||
testCases = append(testCases, patternTestCases...)
|
||||
|
||||
// Test cases must not panic or cause fatal exceptions.
|
||||
failStates := regexp.MustCompile(`fatal|panic|DATA RACE`)
|
||||
|
||||
testApp := func(exepath string, testCases []testCase) {
|
||||
runTests := func(exepath string, testCases []testCase) {
|
||||
|
||||
// Build the test program once.
|
||||
exe, err := buildTestProg(t, exepath)
|
||||
@ -503,7 +503,7 @@ func TestGoroutineLeakProfile(t *testing.T) {
|
||||
|
||||
cmdEnv := []string{
|
||||
"GODEBUG=asyncpreemptoff=1",
|
||||
"GOEXPERIMENT=greenteagc,goroutineleakprofile",
|
||||
"GOEXPERIMENT=goroutineleakprofile",
|
||||
}
|
||||
|
||||
if tcase.simple {
|
||||
@ -515,14 +515,14 @@ func TestGoroutineLeakProfile(t *testing.T) {
|
||||
var output string
|
||||
for i := 0; i < tcase.repetitions; i++ {
|
||||
// Run program for one repetition and get runOutput trace.
|
||||
runOutput := runBuiltTestProg(t, exe, tcase.name, cmdEnv...)
|
||||
runOutput, err := runBuiltTestProgErr(t, exe, tcase.name, cmdEnv...)
|
||||
if len(runOutput) == 0 {
|
||||
t.Errorf("Test %s produced no output. Is the goroutine leak profile collected?", tcase.name)
|
||||
}
|
||||
|
||||
// Zero tolerance policy for fatal exceptions, panics, or data races.
|
||||
if failStates.MatchString(runOutput) {
|
||||
t.Errorf("unexpected fatal exception or panic\noutput:\n%s\n\n", runOutput)
|
||||
// Test cases must not end in a non-zero exit code, or otherwise experience a failure to
|
||||
// actually execute.
|
||||
if err != nil {
|
||||
t.Errorf("unexpected failure\noutput:\n%s\n\n", runOutput)
|
||||
}
|
||||
|
||||
output += runOutput + "\n\n"
|
||||
@ -598,6 +598,6 @@ func TestGoroutineLeakProfile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
testApp("testgoroutineleakprofile", testCases)
|
||||
testApp("testgoroutineleakprofile/goker", gokerTestCases)
|
||||
runTests("testgoroutineleakprofile", testCases)
|
||||
runTests("testgoroutineleakprofile/goker", gokerTestCases)
|
||||
}
|
||||
|
||||
@ -664,10 +664,24 @@ func TestArenaCollision(t *testing.T) {
|
||||
}
|
||||
t.Logf("reserved [%#x, %#x)", start, end)
|
||||
disallowed = append(disallowed, [2]uintptr{start, end})
|
||||
|
||||
hint, ok := NextArenaHint()
|
||||
if !ok {
|
||||
// We're out of arena hints. There's not much we can do now except give up.
|
||||
// This might happen for a number of reasons, like if there's just something
|
||||
// else already mapped in the address space where we put our hints. This is
|
||||
// a bit more common than it used to be thanks to heap base randomization.
|
||||
t.Skip("ran out of arena hints")
|
||||
}
|
||||
|
||||
// Allocate until the runtime tries to use the hint we
|
||||
// just mapped over.
|
||||
hint := GetNextArenaHint()
|
||||
for GetNextArenaHint() == hint {
|
||||
for {
|
||||
if next, ok := NextArenaHint(); !ok {
|
||||
t.Skip("ran out of arena hints")
|
||||
} else if next != hint {
|
||||
break
|
||||
}
|
||||
ac := new(acLink)
|
||||
arenaCollisionSink = append(arenaCollisionSink, ac)
|
||||
// The allocation must not have fallen into
|
||||
|
||||
32
src/runtime/metrics_cgo_test.go
Normal file
32
src/runtime/metrics_cgo_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// 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.
|
||||
|
||||
//go:build cgo
|
||||
|
||||
package runtime_test
|
||||
|
||||
import (
|
||||
"internal/race"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNotInGoMetricCallback(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows", "plan9":
|
||||
t.Skip("unsupported on Windows and Plan9")
|
||||
case "freebsd":
|
||||
if race.Enabled {
|
||||
t.Skipf("race + cgo freebsd not supported. See https://go.dev/issue/73788.")
|
||||
}
|
||||
}
|
||||
|
||||
// This test is run in a subprocess to prevent other tests from polluting the metrics
|
||||
// and because we need to make some cgo callbacks.
|
||||
output := runTestProg(t, "testprogcgo", "NotInGoMetricCallback")
|
||||
want := "OK\n"
|
||||
if output != want {
|
||||
t.Fatalf("output:\n%s\n\nwanted:\n%s", output, want)
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
// Garbage collector (GC).
|
||||
//
|
||||
// The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple
|
||||
// GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is
|
||||
// GC threads to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is
|
||||
// non-generational and non-compacting. Allocation is done using size segregated per P allocation
|
||||
// areas to minimize fragmentation while eliminating locks in the common case.
|
||||
//
|
||||
|
||||
@ -142,6 +142,8 @@ func preemptM(mp *m) {
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
func setProcessCPUProfiler(hz int32) {}
|
||||
|
||||
@ -12,7 +12,25 @@ import (
|
||||
|
||||
// A Pinner is a set of Go objects each pinned to a fixed location in memory. The
|
||||
// [Pinner.Pin] method pins one object, while [Pinner.Unpin] unpins all pinned
|
||||
// objects. See their comments for more information.
|
||||
// objects.
|
||||
//
|
||||
// The purpose of a Pinner is two-fold.
|
||||
// First, it allows C code to safely use Go pointers that have not been passed
|
||||
// explicitly to the C code via a cgo call.
|
||||
// For example, for safely interacting with a pointer stored inside of a struct
|
||||
// whose pointer is passed to a C function.
|
||||
// Second, it allows C memory to safely retain that Go pointer even after the
|
||||
// cgo call returns, provided the object remains pinned.
|
||||
//
|
||||
// A Pinner arranges for its objects to be automatically unpinned some time after
|
||||
// it becomes unreachable, so its referents will not leak. However, this means the
|
||||
// Pinner itself must be kept alive across a cgo call, or as long as C retains a
|
||||
// reference to the pinned Go pointers.
|
||||
//
|
||||
// Reusing a Pinner is safe, and in fact encouraged, to avoid the cost of
|
||||
// initializing new Pinners on first use.
|
||||
//
|
||||
// The zero value of Pinner is ready to use.
|
||||
type Pinner struct {
|
||||
*pinner
|
||||
}
|
||||
@ -26,6 +44,7 @@ type Pinner struct {
|
||||
// are going to be accessed from C code.
|
||||
//
|
||||
// The argument must be a pointer of any type or an [unsafe.Pointer].
|
||||
//
|
||||
// It's safe to call Pin on non-Go pointers, in which case Pin will do nothing.
|
||||
func (p *Pinner) Pin(pointer any) {
|
||||
if p.pinner == nil {
|
||||
@ -63,6 +82,7 @@ func (p *Pinner) Pin(pointer any) {
|
||||
}
|
||||
|
||||
// Unpin unpins all pinned objects of the [Pinner].
|
||||
// It's safe and encouraged to reuse a Pinner after calling Unpin.
|
||||
func (p *Pinner) Unpin() {
|
||||
p.pinner.unpin()
|
||||
|
||||
|
||||
@ -2433,7 +2433,7 @@ func needm(signal bool) {
|
||||
sp := sys.GetCallerSP()
|
||||
callbackUpdateSystemStack(mp, sp, signal)
|
||||
|
||||
// Should mark we are already in Go now.
|
||||
// We must mark that we are already in Go now.
|
||||
// Otherwise, we may call needm again when we get a signal, before cgocallbackg1,
|
||||
// which means the extram list may be empty, that will cause a deadlock.
|
||||
mp.isExtraInC = false
|
||||
@ -2455,7 +2455,8 @@ func needm(signal bool) {
|
||||
// mp.curg is now a real goroutine.
|
||||
casgstatus(mp.curg, _Gdeadextra, _Gsyscall)
|
||||
sched.ngsys.Add(-1)
|
||||
sched.nGsyscallNoP.Add(1)
|
||||
// N.B. We do not update nGsyscallNoP, because isExtraInC threads are not
|
||||
// counted as real goroutines while they're in C.
|
||||
|
||||
if !signal {
|
||||
if trace.ok() {
|
||||
@ -2590,7 +2591,7 @@ func dropm() {
|
||||
casgstatus(mp.curg, _Gsyscall, _Gdeadextra)
|
||||
mp.curg.preemptStop = false
|
||||
sched.ngsys.Add(1)
|
||||
sched.nGsyscallNoP.Add(-1)
|
||||
decGSyscallNoP(mp)
|
||||
|
||||
if !mp.isExtraInSig {
|
||||
if trace.ok() {
|
||||
@ -4732,7 +4733,7 @@ func entersyscallHandleGCWait(trace traceLocker) {
|
||||
if trace.ok() {
|
||||
trace.ProcStop(pp)
|
||||
}
|
||||
sched.nGsyscallNoP.Add(1)
|
||||
addGSyscallNoP(gp.m) // We gave up our P voluntarily.
|
||||
pp.gcStopTime = nanotime()
|
||||
pp.syscalltick++
|
||||
if sched.stopwait--; sched.stopwait == 0 {
|
||||
@ -4763,7 +4764,7 @@ func entersyscallblock() {
|
||||
gp.m.syscalltick = gp.m.p.ptr().syscalltick
|
||||
gp.m.p.ptr().syscalltick++
|
||||
|
||||
sched.nGsyscallNoP.Add(1)
|
||||
addGSyscallNoP(gp.m) // We're going to give up our P.
|
||||
|
||||
// Leave SP around for GC and traceback.
|
||||
pc := sys.GetCallerPC()
|
||||
@ -5001,8 +5002,8 @@ func exitsyscallTryGetP(oldp *p) *p {
|
||||
if oldp != nil {
|
||||
if thread, ok := setBlockOnExitSyscall(oldp); ok {
|
||||
thread.takeP()
|
||||
addGSyscallNoP(thread.mp) // takeP does the opposite, but this is a net zero change.
|
||||
thread.resume()
|
||||
sched.nGsyscallNoP.Add(-1) // takeP adds 1.
|
||||
return oldp
|
||||
}
|
||||
}
|
||||
@ -5017,7 +5018,7 @@ func exitsyscallTryGetP(oldp *p) *p {
|
||||
}
|
||||
unlock(&sched.lock)
|
||||
if pp != nil {
|
||||
sched.nGsyscallNoP.Add(-1)
|
||||
decGSyscallNoP(getg().m) // We got a P for ourselves.
|
||||
return pp
|
||||
}
|
||||
}
|
||||
@ -5043,7 +5044,7 @@ func exitsyscallNoP(gp *g) {
|
||||
trace.GoSysExit(true)
|
||||
traceRelease(trace)
|
||||
}
|
||||
sched.nGsyscallNoP.Add(-1)
|
||||
decGSyscallNoP(getg().m)
|
||||
dropg()
|
||||
lock(&sched.lock)
|
||||
var pp *p
|
||||
@ -5081,6 +5082,41 @@ func exitsyscallNoP(gp *g) {
|
||||
schedule() // Never returns.
|
||||
}
|
||||
|
||||
// addGSyscallNoP must be called when a goroutine in a syscall loses its P.
|
||||
// This function updates all relevant accounting.
|
||||
//
|
||||
// nosplit because it's called on the syscall paths.
|
||||
//
|
||||
//go:nosplit
|
||||
func addGSyscallNoP(mp *m) {
|
||||
// It's safe to read isExtraInC here because it's only mutated
|
||||
// outside of _Gsyscall, and we know this thread is attached
|
||||
// to a goroutine in _Gsyscall and blocked from exiting.
|
||||
if !mp.isExtraInC {
|
||||
// Increment nGsyscallNoP since we're taking away a P
|
||||
// from a _Gsyscall goroutine, but only if isExtraInC
|
||||
// is not set on the M. If it is, then this thread is
|
||||
// back to being a full C thread, and will just inflate
|
||||
// the count of not-in-go goroutines. See go.dev/issue/76435.
|
||||
sched.nGsyscallNoP.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// decGSsyscallNoP must be called whenever a goroutine in a syscall without
|
||||
// a P exits the system call. This function updates all relevant accounting.
|
||||
//
|
||||
// nosplit because it's called from dropm.
|
||||
//
|
||||
//go:nosplit
|
||||
func decGSyscallNoP(mp *m) {
|
||||
// Update nGsyscallNoP, but only if this is not a thread coming
|
||||
// out of C. See the comment in addGSyscallNoP. This logic must match,
|
||||
// to avoid unmatched increments and decrements.
|
||||
if !mp.isExtraInC {
|
||||
sched.nGsyscallNoP.Add(-1)
|
||||
}
|
||||
}
|
||||
|
||||
// Called from syscall package before fork.
|
||||
//
|
||||
// syscall_runtime_BeforeFork is for package syscall,
|
||||
@ -6758,7 +6794,7 @@ func (s syscallingThread) releaseP(state uint32) {
|
||||
trace.ProcSteal(s.pp)
|
||||
traceRelease(trace)
|
||||
}
|
||||
sched.nGsyscallNoP.Add(1)
|
||||
addGSyscallNoP(s.mp)
|
||||
s.pp.syscalltick++
|
||||
}
|
||||
|
||||
|
||||
@ -232,11 +232,14 @@ func TestProfBufWakeup(t *testing.T) {
|
||||
// The reader shouldn't wake up for this
|
||||
b.Write(nil, 1, []uint64{1, 2}, []uintptr{3, 4})
|
||||
|
||||
// The reader should still be blocked
|
||||
//
|
||||
// TODO(nick): this is racy. We could Gosched and still have the reader
|
||||
// blocked in a buggy implementation because it just didn't get a chance
|
||||
// to run
|
||||
// The reader should still be blocked. The awaitBlockedGoroutine here
|
||||
// checks that and also gives a buggy implementation a chance to
|
||||
// actually wake up (it calls Gosched) before the next write. There is a
|
||||
// small chance that a buggy implementation would have woken up but
|
||||
// doesn't get scheduled by the time we do the next write. In that case
|
||||
// the reader will see a more-than-half-full buffer and the test will
|
||||
// pass. But if the implementation is broken, this test should fail
|
||||
// regularly, even if not 100% of the time.
|
||||
awaitBlockedGoroutine(waitStatus, "TestProfBufWakeup.func1")
|
||||
b.Write(nil, 1, []uint64{5, 6}, []uintptr{7, 8})
|
||||
b.Close()
|
||||
@ -247,7 +250,8 @@ func TestProfBufWakeup(t *testing.T) {
|
||||
|
||||
// see also runtime/pprof tests
|
||||
func awaitBlockedGoroutine(state, fName string) {
|
||||
re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s\]:\n(?:.+\n\t.+\n)*runtime_test\.%s`, regexp.QuoteMeta(state), fName)
|
||||
// NB: this matches [state] as well as [state, n minutes]
|
||||
re := fmt.Sprintf(`(?m)^goroutine \d+ \[%s.*\]:\n(?:.+\n\t.+\n)*runtime_test\.%s`, regexp.QuoteMeta(state), fName)
|
||||
r := regexp.MustCompile(re)
|
||||
|
||||
buf := make([]byte, 64<<10)
|
||||
|
||||
@ -329,11 +329,13 @@ TEXT sync∕atomic·AddUintptr(SB), NOSPLIT, $0-24
|
||||
TEXT sync∕atomic·AndInt32(SB), NOSPLIT, $0-20
|
||||
GO_ARGS
|
||||
MOVD $__tsan_go_atomic32_fetch_and(SB), R8
|
||||
ADD $32, R1, R6
|
||||
BR racecallatomic<>(SB)
|
||||
|
||||
TEXT sync∕atomic·AndInt64(SB), NOSPLIT, $0-24
|
||||
GO_ARGS
|
||||
MOVD $__tsan_go_atomic64_fetch_and(SB), R8
|
||||
ADD $32, R1, R6
|
||||
BR racecallatomic<>(SB)
|
||||
|
||||
TEXT sync∕atomic·AndUint32(SB), NOSPLIT, $0-20
|
||||
@ -352,11 +354,13 @@ TEXT sync∕atomic·AndUintptr(SB), NOSPLIT, $0-24
|
||||
TEXT sync∕atomic·OrInt32(SB), NOSPLIT, $0-20
|
||||
GO_ARGS
|
||||
MOVD $__tsan_go_atomic32_fetch_or(SB), R8
|
||||
ADD $32, R1, R6
|
||||
BR racecallatomic<>(SB)
|
||||
|
||||
TEXT sync∕atomic·OrInt64(SB), NOSPLIT, $0-24
|
||||
GO_ARGS
|
||||
MOVD $__tsan_go_atomic64_fetch_or(SB), R8
|
||||
ADD $32, R1, R6
|
||||
BR racecallatomic<>(SB)
|
||||
|
||||
TEXT sync∕atomic·OrUint32(SB), NOSPLIT, $0-20
|
||||
|
||||
@ -945,7 +945,7 @@ type schedt struct {
|
||||
nmfreed int64 // cumulative number of freed m's
|
||||
|
||||
ngsys atomic.Int32 // number of system goroutines
|
||||
nGsyscallNoP atomic.Int32 // number of goroutines in syscalls without a P
|
||||
nGsyscallNoP atomic.Int32 // number of goroutines in syscalls without a P but whose M is not isExtraInC
|
||||
|
||||
pidle puintptr // idle p's
|
||||
npidle atomic.Int32
|
||||
|
||||
@ -21,4 +21,6 @@ func asmcgocall_no_g(fn, arg unsafe.Pointer)
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -26,4 +26,6 @@ func asmcgocall_no_g(fn, arg unsafe.Pointer)
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -19,4 +19,6 @@ func unspillArgs()
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -17,4 +17,6 @@ func asmcgocall_no_g(fn, arg unsafe.Pointer)
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -12,4 +12,6 @@ func save_g()
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -23,4 +23,6 @@ func unspillArgs()
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
@ -22,4 +22,6 @@ func unspillArgs()
|
||||
|
||||
// getfp returns the frame pointer register of its caller or 0 if not implemented.
|
||||
// TODO: Make this a compiler intrinsic
|
||||
//
|
||||
//go:nosplit
|
||||
func getfp() uintptr { return 0 }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user