1
0
mirror of https://github.com/golang/go synced 2024-11-23 20:20:01 -07:00

cmd/go: set default GODEBUG for main packages

For #56986, change the go command to compute and set the
default GODEBUG settings for each main package, based on
the work module's go version and the //go:debug lines in the
main package.

Change-Id: I2118cf0ae6d981138138661e02120c05af648872
Reviewed-on: https://go-review.googlesource.com/c/go/+/453605
Run-TryBot: Russ Cox <rsc@golang.org>
Reviewed-by: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Russ Cox 2022-11-29 15:43:08 -05:00
parent bd8ec78b08
commit d54aeeeea3
13 changed files with 411 additions and 88 deletions

View File

@ -784,44 +784,45 @@
// to -f '{{.ImportPath}}'. The struct being passed to the template is:
//
// type Package struct {
// Dir string // directory containing package sources
// ImportPath string // import path of package in dir
// ImportComment string // path in import comment on package statement
// Name string // package name
// Doc string // package documentation string
// Target string // install path
// Shlib string // the shared library that contains this package (only set when -linkshared)
// Goroot bool // is this package in the Go root?
// Standard bool // is this package part of the standard Go library?
// Stale bool // would 'go install' do anything for this package?
// StaleReason string // explanation for Stale==true
// Root string // Go root or Go path dir containing this package
// ConflictDir string // this directory shadows Dir in $GOPATH
// BinaryOnly bool // binary-only package (no longer supported)
// ForTest string // package is only for use in named test
// Export string // file containing export data (when using -export)
// BuildID string // build ID of the compiled package (when using -export)
// Module *Module // info about package's containing module, if any (can be nil)
// Match []string // command-line patterns matching this package
// DepOnly bool // package is only a dependency, not explicitly listed
// Dir string // directory containing package sources
// ImportPath string // import path of package in dir
// ImportComment string // path in import comment on package statement
// Name string // package name
// Doc string // package documentation string
// Target string // install path
// Shlib string // the shared library that contains this package (only set when -linkshared)
// Goroot bool // is this package in the Go root?
// Standard bool // is this package part of the standard Go library?
// Stale bool // would 'go install' do anything for this package?
// StaleReason string // explanation for Stale==true
// Root string // Go root or Go path dir containing this package
// ConflictDir string // this directory shadows Dir in $GOPATH
// BinaryOnly bool // binary-only package (no longer supported)
// ForTest string // package is only for use in named test
// Export string // file containing export data (when using -export)
// BuildID string // build ID of the compiled package (when using -export)
// Module *Module // info about package's containing module, if any (can be nil)
// Match []string // command-line patterns matching this package
// DepOnly bool // package is only a dependency, not explicitly listed
// DefaultGODEBUG string // default GODEBUG setting, for main packages
//
// // Source files
// GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
// CgoFiles []string // .go source files that import "C"
// CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
// IgnoredGoFiles []string // .go source files ignored due to build constraints
// GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
// CgoFiles []string // .go source files that import "C"
// CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
// IgnoredGoFiles []string // .go source files ignored due to build constraints
// IgnoredOtherFiles []string // non-.go source files ignored due to build constraints
// CFiles []string // .c source files
// CXXFiles []string // .cc, .cxx and .cpp source files
// MFiles []string // .m source files
// HFiles []string // .h, .hh, .hpp and .hxx source files
// FFiles []string // .f, .F, .for and .f90 Fortran source files
// SFiles []string // .s source files
// SwigFiles []string // .swig files
// SwigCXXFiles []string // .swigcxx files
// SysoFiles []string // .syso object files to add to archive
// TestGoFiles []string // _test.go files in package
// XTestGoFiles []string // _test.go files outside package
// CFiles []string // .c source files
// CXXFiles []string // .cc, .cxx and .cpp source files
// MFiles []string // .m source files
// HFiles []string // .h, .hh, .hpp and .hxx source files
// FFiles []string // .f, .F, .for and .f90 Fortran source files
// SFiles []string // .s source files
// SwigFiles []string // .swig files
// SwigCXXFiles []string // .swigcxx files
// SysoFiles []string // .syso object files to add to archive
// TestGoFiles []string // _test.go files in package
// XTestGoFiles []string // _test.go files outside package
//
// // Embedded files
// EmbedPatterns []string // //go:embed patterns

View File

@ -52,44 +52,45 @@ syntax of package template. The default output is equivalent
to -f '{{.ImportPath}}'. The struct being passed to the template is:
type Package struct {
Dir string // directory containing package sources
ImportPath string // import path of package in dir
ImportComment string // path in import comment on package statement
Name string // package name
Doc string // package documentation string
Target string // install path
Shlib string // the shared library that contains this package (only set when -linkshared)
Goroot bool // is this package in the Go root?
Standard bool // is this package part of the standard Go library?
Stale bool // would 'go install' do anything for this package?
StaleReason string // explanation for Stale==true
Root string // Go root or Go path dir containing this package
ConflictDir string // this directory shadows Dir in $GOPATH
BinaryOnly bool // binary-only package (no longer supported)
ForTest string // package is only for use in named test
Export string // file containing export data (when using -export)
BuildID string // build ID of the compiled package (when using -export)
Module *Module // info about package's containing module, if any (can be nil)
Match []string // command-line patterns matching this package
DepOnly bool // package is only a dependency, not explicitly listed
Dir string // directory containing package sources
ImportPath string // import path of package in dir
ImportComment string // path in import comment on package statement
Name string // package name
Doc string // package documentation string
Target string // install path
Shlib string // the shared library that contains this package (only set when -linkshared)
Goroot bool // is this package in the Go root?
Standard bool // is this package part of the standard Go library?
Stale bool // would 'go install' do anything for this package?
StaleReason string // explanation for Stale==true
Root string // Go root or Go path dir containing this package
ConflictDir string // this directory shadows Dir in $GOPATH
BinaryOnly bool // binary-only package (no longer supported)
ForTest string // package is only for use in named test
Export string // file containing export data (when using -export)
BuildID string // build ID of the compiled package (when using -export)
Module *Module // info about package's containing module, if any (can be nil)
Match []string // command-line patterns matching this package
DepOnly bool // package is only a dependency, not explicitly listed
DefaultGODEBUG string // default GODEBUG setting, for main packages
// Source files
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string // .go source files that import "C"
CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
IgnoredGoFiles []string // .go source files ignored due to build constraints
GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
CgoFiles []string // .go source files that import "C"
CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
IgnoredGoFiles []string // .go source files ignored due to build constraints
IgnoredOtherFiles []string // non-.go source files ignored due to build constraints
CFiles []string // .c source files
CXXFiles []string // .cc, .cxx and .cpp source files
MFiles []string // .m source files
HFiles []string // .h, .hh, .hpp and .hxx source files
FFiles []string // .f, .F, .for and .f90 Fortran source files
SFiles []string // .s source files
SwigFiles []string // .swig files
SwigCXXFiles []string // .swigcxx files
SysoFiles []string // .syso object files to add to archive
TestGoFiles []string // _test.go files in package
XTestGoFiles []string // _test.go files outside package
CFiles []string // .c source files
CXXFiles []string // .cc, .cxx and .cpp source files
MFiles []string // .m source files
HFiles []string // .h, .hh, .hpp and .hxx source files
FFiles []string // .f, .F, .for and .f90 Fortran source files
SFiles []string // .s source files
SwigFiles []string // .swig files
SwigCXXFiles []string // .swigcxx files
SysoFiles []string // .syso object files to add to archive
TestGoFiles []string // _test.go files in package
XTestGoFiles []string // _test.go files outside package
// Embedded files
EmbedPatterns []string // //go:embed patterns

View File

@ -0,0 +1,128 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package load
import (
"cmd/go/internal/modload"
"errors"
"fmt"
"go/build"
"sort"
"strconv"
"strings"
)
var ErrNotGoDebug = errors.New("not //go:debug line")
func ParseGoDebug(text string) (key, value string, err error) {
if !strings.HasPrefix(text, "//go:debug") {
return "", "", ErrNotGoDebug
}
i := strings.IndexAny(text, " \t")
if i < 0 {
if strings.TrimSpace(text) == "//go:debug" {
return "", "", fmt.Errorf("missing key=value")
}
return "", "", ErrNotGoDebug
}
k, v, ok := strings.Cut(strings.TrimSpace(text[i:]), "=")
if !ok {
return "", "", fmt.Errorf("missing key=value")
}
if strings.ContainsAny(k, " \t") {
return "", "", fmt.Errorf("key contains space")
}
if strings.ContainsAny(v, " \t") {
return "", "", fmt.Errorf("value contains space")
}
if strings.ContainsAny(k, ",") {
return "", "", fmt.Errorf("key contains comma")
}
if strings.ContainsAny(v, ",") {
return "", "", fmt.Errorf("value contains comma")
}
return k, v, nil
}
// defaultGODEBUG returns the default GODEBUG setting for the main package p.
// When building a test binary, directives, testDirectives, and xtestDirectives
// list additional directives from the package under test.
func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []build.Directive) string {
if p.Name != "main" {
return ""
}
goVersion := modload.MainModules.GoVersion()
if modload.RootMode == modload.NoRoot && p.Module != nil {
// This is go install pkg@version or go run pkg@version.
// Use the Go version from the package.
// If there isn't one, then
goVersion = p.Module.GoVersion
if goVersion == "" {
goVersion = "1.20"
}
}
m := godebugForGoVersion(goVersion)
for _, list := range [][]build.Directive{p.Internal.Build.Directives, directives, testDirectives, xtestDirectives} {
for _, d := range list {
k, v, err := ParseGoDebug(d.Text)
if err != nil {
continue
}
if m == nil {
m = make(map[string]string)
}
m[k] = v
}
}
var keys []string
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for _, k := range keys {
if b.Len() > 0 {
b.WriteString(",")
}
b.WriteString(k)
b.WriteString("=")
b.WriteString(m[k])
}
return b.String()
}
func godebugForGoVersion(v string) map[string]string {
if strings.Count(v, ".") >= 2 {
i := strings.Index(v, ".")
j := i + 1 + strings.Index(v[i+1:], ".")
v = v[:j]
}
if !strings.HasPrefix(v, "1.") {
return nil
}
n, err := strconv.Atoi(v[len("1."):])
if err != nil {
return nil
}
def := make(map[string]string)
for _, d := range defaultGodebugs {
if (d.before != 0 && n < d.before) || (d.first != 0 && n >= d.first) {
def[d.name] = d.value
}
}
return def
}
var defaultGodebugs = []struct {
before int // applies to Go versions up until this one (21 for Go 1.21)
first int // applies to Go versions starting at this one (21 for Go 1.21)
name string
value string
}{
{before: 21, name: "panicnil", value: "1"},
}

View File

@ -58,7 +58,7 @@ type Package struct {
type PackagePublic struct {
// Note: These fields are part of the go command's public API.
// See list.go. It is okay to add fields, but not to change or
// remove existing ones. Keep in sync with list.go
// remove existing ones. Keep in sync with ../list/list.go
Dir string `json:",omitempty"` // directory containing package sources
ImportPath string `json:",omitempty"` // import path of package in dir
ImportComment string `json:",omitempty"` // path in import comment on package statement
@ -79,6 +79,8 @@ type PackagePublic struct {
BinaryOnly bool `json:",omitempty"` // package cannot be recompiled
Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies?
DefaultGODEBUG string `json:",omitempty"` // default GODEBUG setting (only for Name=="main")
// Stale and StaleReason remain here *only* for the list command.
// They are only initialized in preparation for list execution.
// The regular build determines staleness on the fly during action execution.
@ -230,9 +232,6 @@ type PackageInternal struct {
TestmainGo *[]byte // content for _testmain.go
Embed map[string][]string // //go:embed comment mapping
OrigImportPath string // original import path before adding '_test' suffix
Directives []build.Directive
TestDirectives []build.Directive
XTestDirectives []build.Directive
Asmflags []string // -asmflags for this package
Gcflags []string // -gcflags for this package
@ -438,9 +437,6 @@ func (p *Package) copyBuild(opts PackageOpts, pp *build.Package) {
p.TestEmbedPatterns = pp.TestEmbedPatterns
p.XTestEmbedPatterns = pp.XTestEmbedPatterns
p.Internal.OrigImportPath = pp.ImportPath
p.Internal.Directives = pp.Directives
p.Internal.TestDirectives = pp.TestDirectives
p.Internal.XTestDirectives = pp.XTestDirectives
}
// A PackageError describes an error loading information about a package.
@ -1924,6 +1920,7 @@ func (p *Package) load(ctx context.Context, opts PackageOpts, path string, stk *
if cfg.ModulesEnabled {
p.Module = modload.PackageModuleInfo(ctx, pkgPath)
}
p.DefaultGODEBUG = defaultGODEBUG(p, nil, nil, nil)
p.EmbedFiles, p.Internal.Embed, err = resolveEmbed(p.Dir, p.EmbedPatterns)
if err != nil {
@ -2405,6 +2402,9 @@ func (p *Package) setBuildInfo(autoVCS bool) {
if cfg.BuildTrimpath {
appendSetting("-trimpath", "true")
}
if p.DefaultGODEBUG != "" {
appendSetting("DefaultGODEBUG", p.DefaultGODEBUG)
}
cgo := "0"
if cfg.BuildContext.CgoEnabled {
cgo = "1"

View File

@ -23,6 +23,7 @@ import (
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/slices"
"cmd/go/internal/str"
"cmd/go/internal/trace"
)
@ -205,6 +206,7 @@ func TestPackagesAndErrors(ctx context.Context, opts PackageOpts, p *Package, co
ptest.Internal.Embed = testEmbed
ptest.EmbedFiles = str.StringList(p.EmbedFiles, p.TestEmbedFiles)
ptest.Internal.OrigImportPath = p.Internal.OrigImportPath
ptest.Internal.Build.Directives = append(slices.Clip(p.Internal.Build.Directives), p.Internal.Build.TestDirectives...)
ptest.collectDeps()
} else {
ptest = p
@ -229,7 +231,8 @@ func TestPackagesAndErrors(ctx context.Context, opts PackageOpts, p *Package, co
Internal: PackageInternal{
LocalPrefix: p.Internal.LocalPrefix,
Build: &build.Package{
ImportPos: p.Internal.Build.XTestImportPos,
ImportPos: p.Internal.Build.XTestImportPos,
Directives: p.Internal.Build.XTestDirectives,
},
Imports: ximports,
RawImports: rawXTestImports,
@ -270,6 +273,9 @@ func TestPackagesAndErrors(ctx context.Context, opts PackageOpts, p *Package, co
},
}
pb := p.Internal.Build
pmain.DefaultGODEBUG = defaultGODEBUG(pmain, pb.Directives, pb.TestDirectives, pb.XTestDirectives)
// The generated main also imports testing, regexp, and os.
// Also the linker introduces implicit dependencies reported by LinkerDeps.
stk.Push("testmain")

View File

@ -221,15 +221,19 @@ func (mms *MainModuleSet) HighestReplaced() map[string]string {
// GoVersion returns the go version set on the single module, in module mode,
// or the go.work file in workspace mode.
func (mms *MainModuleSet) GoVersion() string {
if !inWorkspaceMode() {
switch {
case inWorkspaceMode():
v := mms.workFileGoVersion
if v == "" {
// Fall back to 1.18 for go.work files.
v = "1.18"
}
return v
case mms == nil || len(mms.versions) == 0:
return "1.18"
default:
return modFileGoVersion(mms.ModFile(mms.mustGetSingleMainModule()))
}
v := mms.workFileGoVersion
if v == "" {
// Fall back to 1.18 for go.work files.
v = "1.18"
}
return v
}
func (mms *MainModuleSet) WorkFileReplaceMap() map[module.Version]module.Version {
@ -726,7 +730,7 @@ func LoadModFile(ctx context.Context) *Requirements {
data, f, err := ReadModFile(gomod, fixVersion(ctx, &fixed))
if err != nil {
if inWorkspaceMode() {
base.Fatalf("go: cannot load module listed in go.work file: %v", err)
base.Fatalf("go: cannot load module %s listed in go.work file: %v", base.ShortPath(gomod), err)
} else {
base.Fatalf("go: %v", err)
}

View File

@ -13,6 +13,7 @@ import (
"encoding/json"
"errors"
"fmt"
"go/token"
"internal/coverage"
"internal/lazyregexp"
"io"
@ -527,6 +528,10 @@ func (b *Builder) build(ctx context.Context, a *Action) (err error) {
return errors.New("binary-only packages are no longer supported")
}
if err := b.checkDirectives(a); err != nil {
return err
}
if err := b.Mkdir(a.Objdir); err != nil {
return err
}
@ -956,6 +961,37 @@ OverlayLoop:
return nil
}
func (b *Builder) checkDirectives(a *Action) error {
var msg *bytes.Buffer
p := a.Package
var seen map[string]token.Position
for _, d := range p.Internal.Build.Directives {
if strings.HasPrefix(d.Text, "//go:debug") {
key, _, err := load.ParseGoDebug(d.Text)
if err != nil && err != load.ErrNotGoDebug {
if msg == nil {
msg = new(bytes.Buffer)
}
fmt.Fprintf(msg, "%s: invalid //go:debug: %v\n", d.Pos, err)
continue
}
if pos, ok := seen[key]; ok {
fmt.Fprintf(msg, "%s: repeated //go:debug for %v\n\t%s: previous //go:debug\n", d.Pos, key, pos)
continue
}
if seen == nil {
seen = make(map[string]token.Position)
}
seen[key] = d.Pos
}
}
if msg != nil {
return formatOutput(b.WorkDir, p.Dir, p.ImportPath, p.Desc(), b.processOutput(msg.Bytes()))
}
return nil
}
func (b *Builder) cacheObjdirFile(a *Action, c *cache.Cache, name string) error {
f, err := os.Open(a.Objdir + name)
if err != nil {

View File

@ -648,6 +648,11 @@ func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string)
}
}
// Store default GODEBUG in binaries.
if root.Package.DefaultGODEBUG != "" {
ldflags = append(ldflags, "-X=runtime.godebugDefault="+root.Package.DefaultGODEBUG)
}
// If the user has not specified the -extld option, then specify the
// appropriate linker. In case of C++ code, use the compiler named
// by the CXX environment variable or defaultCXX if CXX is not set.

View File

@ -0,0 +1,13 @@
rsc.io/panicnil v1.0.0
written by hand
-- .mod --
module rsc.io/panicnil
-- .info --
{"Version":"v1.0.0"}
-- fortune.go --
package main
func main() {
panic(nil)
}

View File

@ -0,0 +1,14 @@
rsc.io/panicnil v1.1.0
written by hand
-- .mod --
module rsc.io/panicnil
go 1.21
-- .info --
{"Version":"v1.1.0"}
-- fortune.go --
package main
func main() {
panic(nil)
}

View File

@ -0,0 +1,115 @@
env GO111MODULE=on
env GOTRACEBACK=single
# Go 1.21 work module should leave panicnil with an implicit default.
cp go.mod.21 go.mod
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
! stdout panicnil
stdout randautoseed=0
# Go 1.21 work module should NOT set panicnil=1 in Go 1.20 dependency.
cp go.mod.21 go.mod
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}' q
! stdout panicnil=1
! stdout randautoseed
go mod download rsc.io/panicnil # for go.sum
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}' rsc.io/panicnil
! stdout panicnil=1
! stdout randautoseed
# Go 1.20 work module should set panicnil=1.
cp go.mod.20 go.mod
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
stdout panicnil=1
stdout randautoseed=0
# Go 1.20 work module should set panicnil=1 in Go 1.20 dependency.
cp go.mod.20 go.mod
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}' q
stdout panicnil=1
! stdout randautoseed
# Go 1.21 workspace should leave panicnil with an implicit default.
cat q/go.mod
cp go.work.21 go.work
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
! stdout panicnil
stdout randautoseed=0
rm go.work
# Go 1.20 workspace should set panicnil=1 even in Go 1.21 module.
cp go.work.20 go.work
cp go.mod.21 go.mod
go list -f '{{.Module.GoVersion}} {{.DefaultGODEBUG}}'
stdout panicnil=1
stdout randautoseed=0
rm go.work
[short] skip
# Programs in Go 1.21 work module should trigger run-time error.
cp go.mod.21 go.mod
! go run .
stderr 'panic: panic called with nil argument'
! go run rsc.io/panicnil
stderr 'panic: panic called with nil argument'
# Programs in Go 1.20 work module use old panic nil behavior.
cp go.mod.20 go.mod
! go run .
stderr 'panic: nil'
! go run rsc.io/panicnil
stderr 'panic: nil'
# Programs in no module at all should use their go.mod file.
rm go.mod
! go run rsc.io/panicnil@v1.0.0
stderr 'panic: nil'
rm go.mod
! go run rsc.io/panicnil@v1.1.0
stderr 'panic: panic called with nil argument'
-- go.work.21 --
go 1.21
use .
use ./q
-- go.work.20 --
go 1.20
use .
use ./q
-- go.mod.21 --
go 1.21
module m
require q v1.0.0
replace q => ./q
require rsc.io/panicnil v1.0.0
-- go.mod.20 --
go 1.20
module m
require q v1.0.0
replace q => ./q
require rsc.io/panicnil v1.0.0
-- p.go --
//go:debug randautoseed=0
package main
func main() {
panic(nil)
}
-- q/go.mod --
go 1.20
module q
-- q/q.go --
package main
func main() {}

View File

@ -25,7 +25,7 @@ stdout 'this input caused a crash!'
! go test -run=FuzzWithNilPanic -fuzz=FuzzWithNilPanic -fuzztime=100x -fuzzminimizetime=1000x
stdout 'testdata[/\\]fuzz[/\\]FuzzWithNilPanic[/\\]'
stdout 'panic called with nil argument'
stdout 'panic called with nil argument|test executed panic.nil. or runtime.Goexit'
go run check_testdata.go FuzzWithNilPanic
! go test -run=FuzzWithGoexit -fuzz=FuzzWithGoexit -fuzztime=100x -fuzzminimizetime=1000x

View File

@ -1,5 +1,5 @@
! go list .
stderr '^go: cannot load module listed in go\.work file: open .+go\.mod:'
stderr '^go: cannot load module y.go.mod listed in go\.work file: open .+go\.mod:'
-- go.work --
use ./y