cmd/go: add check for unknown godebug setting

A //go:debug line mentioning an unknown or retired setting
should be diagnosed as making the program invalid. Do that.
We agreed on this in the proposal but I forgot to implement it.

Change-Id: Ie69072a1682d4eeb6866c02adbbb426f608567c4
Reviewed-on: https://go-review.googlesource.com/c/go/+/476280
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 2023-03-14 14:25:56 -04:00
parent 44e51e60ac
commit 14ab998f95
21 changed files with 208 additions and 164 deletions

View File

@ -219,7 +219,7 @@ func cloneGOROOTDeps(goroot string) error {
for _, pkg := range pkgs {
parentFound := false
for _, prev := range pkgRoots {
if strings.HasPrefix(pkg, prev) {
if pkg == prev || strings.HasPrefix(pkg, prev+"/") {
// We will copy in the source for pkg when we copy in prev.
parentFound = true
break

View File

@ -65,6 +65,7 @@ var bootstrapDirs = []string{
"internal/coverage",
"internal/buildcfg",
"internal/goarch",
"internal/godebugs",
"internal/goexperiment",
"internal/goroot",
"internal/goversion",

View File

@ -187,7 +187,7 @@ func execGoToolchain(gotoolchain, dir, exe string) {
// propagate signals and such, but there are no signals on Windows.
// We also use the exec case when GODEBUG=gotoolchainexec=0,
// to allow testing this code even when not on Windows.
if godebug.New("gotoolchainexec").Value() == "0" || runtime.GOOS == "windows" {
if godebug.New("#gotoolchainexec").Value() == "0" || runtime.GOOS == "windows" {
cmd := exec.Command(exe, os.Args[1:]...)
if runtime.GOOS == "windows" && strings.Contains(exe, "go1.999test") {
// See testdata/script/gotoolchain.txt.

View File

@ -48,9 +48,9 @@ var (
traceFile *os.File
traceMu sync.Mutex
gofsystrace = godebug.New("gofsystrace")
gofsystracelog = godebug.New("gofsystracelog")
gofsystracestack = godebug.New("gofsystracestack")
gofsystrace = godebug.New("#gofsystrace")
gofsystracelog = godebug.New("#gofsystracelog")
gofsystracestack = godebug.New("#gofsystracestack")
)
func init() {

View File

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"go/build"
"internal/godebugs"
"sort"
"strconv"
"strings"
@ -43,7 +44,13 @@ func ParseGoDebug(text string) (key, value string, err error) {
if strings.ContainsAny(v, ",") {
return "", "", fmt.Errorf("value contains comma")
}
return k, v, nil
for _, info := range godebugs.All {
if k == info.Name {
return k, v, nil
}
}
return "", "", fmt.Errorf("unknown //go:debug setting %q", k)
}
// defaultGODEBUG returns the default GODEBUG setting for the main package p.
@ -110,19 +117,10 @@ func godebugForGoVersion(v string) map[string]string {
}
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
for _, info := range godebugs.All {
if n < info.Changed {
def[info.Name] = info.Old
}
}
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

@ -37,7 +37,7 @@ import (
// It will be removed before the release.
// TODO(matloob): Remove enabled once we have more confidence on the
// module index.
var enabled = godebug.New("goindex").Value() != "0"
var enabled = godebug.New("#goindex").Value() != "0"
// Module represents and encoded module index file. It is used to
// do the equivalent of build.Import of packages in the module and answer other

View File

@ -0,0 +1,9 @@
! go build
stderr 'p.go:1:1: invalid //go:debug: unknown //go:debug setting "x"'
-- go.mod --
module m
-- p.go --
//go:debug x=y
package main
func main() {}

View File

@ -42,7 +42,7 @@ var depsRules = `
< constraints, container/list, container/ring,
internal/cfg, internal/coverage, internal/coverage/rtcov,
internal/coverage/uleb128, internal/coverage/calloc,
internal/cpu, internal/goarch,
internal/cpu, internal/goarch, internal/godebugs,
internal/goexperiment, internal/goos,
internal/goversion, internal/nettrace, internal/platform,
log/internal,
@ -55,7 +55,7 @@ var depsRules = `
# RUNTIME is the core runtime group of packages, all of them very light-weight.
internal/abi, internal/cpu, internal/goarch,
internal/coverage/rtcov, internal/goexperiment,
internal/coverage/rtcov, internal/godebugs, internal/goexperiment,
internal/goos, unsafe
< internal/bytealg
< internal/itoa

View File

@ -48,7 +48,7 @@ func TestDisableAllCapabilities(t *testing.T) {
func TestAllCapabilitiesDisabled(t *testing.T) {
MustHaveDebugOptionsSupport(t)
if godebug.New("cpu.all").Value() != "off" {
if godebug.New("#cpu.all").Value() != "off" {
t.Skipf("skipping test: GODEBUG=cpu.all=off not set")
}

View File

@ -28,7 +28,7 @@ func TestDisableSSE3(t *testing.T) {
func TestSSE3DebugOption(t *testing.T) {
MustHaveDebugOptionsSupport(t)
if godebug.New("cpu.sse3").Value() != "off" {
if godebug.New("#cpu.sse3").Value() != "off" {
t.Skipf("skipping test: GODEBUG=cpu.sse3=off not set")
}

View File

@ -1090,7 +1090,7 @@ var zeroVals []any = []any{
uint64(0),
}
var debugInfo = godebug.New("fuzzdebug").Value() == "1"
var debugInfo = godebug.New("#fuzzdebug").Value() == "1"
func shouldPrintDebugInfo() bool {
return debugInfo

View File

@ -30,6 +30,7 @@
package godebug
import (
"internal/godebugs"
"sync"
"sync/atomic"
_ "unsafe" // go:linkname
@ -46,21 +47,38 @@ type setting struct {
value atomic.Pointer[string]
nonDefaultOnce sync.Once
nonDefault atomic.Uint64
info *godebugs.Info
}
// New returns a new Setting for the $GODEBUG setting with the given name.
//
// GODEBUGs meant for use by end users must be listed in ../godebugs/table.go,
// which is used for generating and checking various documentation.
// If the name is not listed in that table, New will succeed but calling Value
// on the returned Setting will panic.
// To disable that panic for access to an undocumented setting,
// prefix the name with a #, as in godebug.New("#gofsystrace").
// The # is a signal to New but not part of the key used in $GODEBUG.
func New(name string) *Setting {
return &Setting{name: name}
}
// Name returns the name of the setting.
func (s *Setting) Name() string {
if s.name != "" && s.name[0] == '#' {
return s.name[1:]
}
return s.name
}
// Undocumented reports whether this is an undocumented setting.
func (s *Setting) Undocumented() bool {
return s.name != "" && s.name[0] == '#'
}
// String returns a printable form for the setting: name=value.
func (s *Setting) String() string {
return s.name + "=" + s.Value()
return s.Name() + "=" + s.Value()
}
// IncNonDefault increments the non-default behavior counter
@ -69,20 +87,16 @@ func (s *Setting) String() string {
// /godebug/non-default-behavior/<name>:events.
//
// Note that Value must be called at least once before IncNonDefault.
//
// Any GODEBUG setting that can call IncNonDefault must be listed
// in three more places:
//
// - the table in ../runtime/metrics.go (search for non-default-behavior)
// - the table in ../../runtime/metrics/description.go (search for non-default-behavior; run 'go generate' afterward)
// - the table in ../../cmd/go/internal/load/godebug.go (search for defaultGodebugs)
func (s *Setting) IncNonDefault() {
s.nonDefaultOnce.Do(s.register)
s.nonDefault.Add(1)
}
func (s *Setting) register() {
registerMetric("/godebug/non-default-behavior/"+s.name+":events", s.nonDefault.Load)
if s.info == nil || s.info.Opaque {
panic("godebug: unexpected IncNonDefault of " + s.name)
}
registerMetric("/godebug/non-default-behavior/"+s.Name()+":events", s.nonDefault.Load)
}
// cache is a cache of all the GODEBUG settings,
@ -111,7 +125,10 @@ var empty string
// caching of Value's result.
func (s *Setting) Value() string {
s.once.Do(func() {
s.setting = lookup(s.name)
s.setting = lookup(s.Name())
if s.info == nil && !s.Undocumented() {
panic("godebug: Value of name not listed in godebugs.All: " + s.name)
}
})
return *s.value.Load()
}
@ -122,6 +139,7 @@ func lookup(name string) *setting {
return v.(*setting)
}
s := new(setting)
s.info = godebugs.Lookup(name)
s.value.Store(&empty)
if v, loaded := cache.LoadOrStore(name, s); loaded {
// Lost race: someone else created it. Use theirs.

View File

@ -11,13 +11,13 @@ import (
)
func TestGet(t *testing.T) {
foo := New("foo")
foo := New("#foo")
tests := []struct {
godebug string
setting *Setting
want string
}{
{"", New(""), ""},
{"", New("#"), ""},
{"", foo, ""},
{"foo=bar", foo, "bar"},
{"foo=bar,after=x", foo, "bar"},
@ -28,7 +28,7 @@ func TestGet(t *testing.T) {
{"foo=", foo, ""},
{"foo", foo, ""},
{",foo", foo, ""},
{"foo=bar,baz", New("loooooooong"), ""},
{"foo=bar,baz", New("#loooooooong"), ""},
}
for _, tt := range tests {
t.Setenv("GODEBUG", tt.godebug)

View File

@ -0,0 +1,41 @@
// 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 godebugs_test
import (
"internal/godebugs"
"os"
"strings"
"testing"
)
func TestAll(t *testing.T) {
data, err := os.ReadFile("../../../doc/godebug.md")
if err != nil {
t.Fatal(err)
}
doc := string(data)
last := ""
for _, info := range godebugs.All {
if info.Name <= last {
t.Errorf("All not sorted: %s then %s", last, info.Name)
}
last = info.Name
if info.Package == "" {
t.Errorf("Name=%s missing Package", info.Name)
}
if info.Changed != 0 && info.Old == "" {
t.Errorf("Name=%s has Changed, missing Old", info.Name)
}
if info.Old != "" && info.Changed == 0 {
t.Errorf("Name=%s has Old, missing Changed", info.Name)
}
if !strings.Contains(doc, "`"+info.Name+"`") {
t.Errorf("Name=%s not documented in doc/godebug.md", info.Name)
}
}
}

View File

@ -0,0 +1,64 @@
// 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 godebugs provides a table of known GODEBUG settings,
// for use by a variety of other packages, including internal/godebug,
// runtime, runtime/metrics, and cmd/go/internal/load.
package godebugs
// An Info describes a single known GODEBUG setting.
type Info struct {
Name string // name of the setting ("panicnil")
Package string // package that uses the setting ("runtime")
Changed int // minor version when default changed, if any; 21 means Go 1.21
Old string // value that restores behavior prior to Changed
Opaque bool // setting does not export information to runtime/metrics using [internal/godebug.Setting.IncNonDefault]
}
// All is the table of known settings, sorted by Name.
//
// Note: After adding entries to this table, run 'go generate runtime/metrics'
// to update the runtime/metrics doc comment.
// (Otherwise the runtime/metrics test will fail.)
//
// Note: After adding entries to this table, update the list in doc/godebug.md as well.
// (Otherwise the test in this package will fail.)
var All = []Info{
{Name: "execerrdot", Package: "os/exec"},
{Name: "http2client", Package: "net/http"},
{Name: "http2debug", Package: "net/http", Opaque: true},
{Name: "http2server", Package: "net/http"},
{Name: "installgoroot", Package: "go/build"},
{Name: "jstmpllitinterp", Package: "html/template"},
//{Name: "multipartfiles", Package: "mime/multipart"},
{Name: "multipartmaxheaders", Package: "mime/multipart"},
{Name: "multipartmaxparts", Package: "mime/multipart"},
{Name: "netdns", Package: "net", Opaque: true},
{Name: "panicnil", Package: "runtime", Changed: 21, Old: "1"},
{Name: "randautoseed", Package: "math/rand"},
{Name: "tarinsecurepath", Package: "archive/tar"},
{Name: "x509sha1", Package: "crypto/x509"},
{Name: "x509usefallbackroots", Package: "crypto/x509"},
{Name: "zipinsecurepath", Package: "archive/zip"},
}
// Lookup returns the Info with the given name.
func Lookup(name string) *Info {
// binary search, avoiding import of sort.
lo := 0
hi := len(All)
for lo < hi {
m := lo + (hi-lo)>>1
mid := All[m].Name
if name == mid {
return &All[m]
}
if name < mid {
hi = m
} else {
lo = m + 1
}
}
return nil
}

View File

@ -66,7 +66,7 @@ var (
valSafe = safeMap() // non-nil in safe+leaky mode
)
var intern = godebug.New("intern")
var intern = godebug.New("#intern")
// safeMap returns a non-nil map if we're in safe-but-leaky mode,
// as controlled by GODEBUG=intern=leaky

View File

@ -34,7 +34,7 @@ func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
}
var (
multipartFiles = godebug.New("multipartfiles")
multipartFiles = godebug.New("#multipartfiles") // TODO: document and remove #
multipartMaxParts = godebug.New("multipartmaxparts")
)
@ -48,7 +48,7 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
combineFiles := true
if multipartFiles.Value() == "distinct" {
combineFiles = false
multipartFiles.IncNonDefault()
// multipartFiles.IncNonDefault() // TODO: uncomment after documenting
}
maxParts := 1000
if s := multipartMaxParts.Value(); s != "" {

View File

@ -348,7 +348,7 @@ type ctxResult struct {
timer *time.Timer
}
var execwait = godebug.New("execwait")
var execwait = godebug.New("#execwait")
var execerrdot = godebug.New("execerrdot")
// Command returns the Cmd struct to execute the named program with

View File

@ -7,6 +7,7 @@ package runtime
// Metrics implementation exported to runtime/metrics.
import (
"internal/godebugs"
"unsafe"
)
@ -286,20 +287,6 @@ func initMetrics() {
out.scalar = uint64(startingStackSize)
},
},
"/godebug/non-default-behavior/execerrdot:events": {compute: compute0},
"/godebug/non-default-behavior/http2client:events": {compute: compute0},
"/godebug/non-default-behavior/http2server:events": {compute: compute0},
"/godebug/non-default-behavior/installgoroot:events": {compute: compute0},
"/godebug/non-default-behavior/jstmpllitinterp:events": {compute: compute0},
"/godebug/non-default-behavior/multipartfiles:events": {compute: compute0},
"/godebug/non-default-behavior/multipartmaxheaders:events": {compute: compute0},
"/godebug/non-default-behavior/multipartmaxparts:events": {compute: compute0},
"/godebug/non-default-behavior/panicnil:events": {compute: compute0},
"/godebug/non-default-behavior/randautoseed:events": {compute: compute0},
"/godebug/non-default-behavior/tarinsecurepath:events": {compute: compute0},
"/godebug/non-default-behavior/x509sha1:events": {compute: compute0},
"/godebug/non-default-behavior/x509usefallbackroots:events": {compute: compute0},
"/godebug/non-default-behavior/zipinsecurepath:events": {compute: compute0},
"/memory/classes/heap/free:bytes": {
deps: makeStatDepSet(heapStatsDep),
compute: func(in *statAggregate, out *metricValue) {
@ -432,6 +419,13 @@ func initMetrics() {
},
},
}
for _, info := range godebugs.All {
if !info.Opaque {
metrics["/godebug/non-default-behavior/"+info.Name+":events"] = metricData{compute: compute0}
}
}
metricsInit = true
}
@ -447,10 +441,6 @@ func (f metricReader) compute(_ *statAggregate, out *metricValue) {
out.scalar = f()
}
var godebugNonDefaults = []string{
"panicnil",
}
//go:linkname godebug_registerMetric internal/godebug.registerMetric
func godebug_registerMetric(name string, read func() uint64) {
metricsLock()

View File

@ -4,6 +4,8 @@
package metrics
import "internal/godebugs"
// Description describes a runtime metric.
type Description struct {
// Name is the full name of the metric which includes the unit.
@ -49,7 +51,7 @@ type Description struct {
}
// The English language descriptions below must be kept in sync with the
// descriptions of each metric in doc.go.
// descriptions of each metric in doc.go by running 'go generate'.
var allDesc = []Description{
{
Name: "/cgo/go-to-c-calls:calls",
@ -277,104 +279,6 @@ var allDesc = []Description{
Kind: KindUint64,
Cumulative: false,
},
{
Name: "/godebug/non-default-behavior/execerrdot:events",
Description: "The number of non-default behaviors executed by the os/exec package " +
"due to a non-default GODEBUG=execerrdot=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/http2client:events",
Description: "The number of non-default behaviors executed by the net/http package " +
"due to a non-default GODEBUG=http2client=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/http2server:events",
Description: "The number of non-default behaviors executed by the net/http package " +
"due to a non-default GODEBUG=http2server=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/installgoroot:events",
Description: "The number of non-default behaviors executed by the go/build package " +
"due to a non-default GODEBUG=installgoroot=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/jstmpllitinterp:events",
Description: "The number of non-default behaviors executed by the html/template" +
"package due to a non-default GODEBUG=jstmpllitinterp=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/multipartfiles:events",
Description: "The number of non-default behaviors executed by the mime/multipart package " +
"due to a non-default GODEBUG=multipartfiles=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/multipartmaxheaders:events",
Description: "The number of non-default behaviors executed by the mime/multipart package " +
"due to a non-default GODEBUG=multipartmaxheaders=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/multipartmaxparts:events",
Description: "The number of non-default behaviors executed by the mime/multipart package " +
"due to a non-default GODEBUG=multipartmaxparts=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/panicnil:events",
Description: "The number of non-default behaviors executed by the runtime package " +
"due to a non-default GODEBUG=panicnil=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/randautoseed:events",
Description: "The number of non-default behaviors executed by the math/rand package " +
"due to a non-default GODEBUG=randautoseed=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/tarinsecurepath:events",
Description: "The number of non-default behaviors executed by the archive/tar package " +
"due to a non-default GODEBUG=tarinsecurepath=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/x509sha1:events",
Description: "The number of non-default behaviors executed by the crypto/x509 package " +
"due to a non-default GODEBUG=x509sha1=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/x509usefallbackroots:events",
Description: "The number of non-default behaviors executed by the crypto/x509 package " +
"due to a non-default GODEBUG=x509usefallbackroots=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/godebug/non-default-behavior/zipinsecurepath:events",
Description: "The number of non-default behaviors executed by the archive/zip package " +
"due to a non-default GODEBUG=zipinsecurepath=... setting.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/memory/classes/heap/free:bytes",
Description: "Memory that is completely free and eligible to be returned to the underlying system, " +
@ -472,6 +376,30 @@ var allDesc = []Description{
},
}
func init() {
// Insert all the the non-default-reporting GODEBUGs into the table,
// preserving the overall sort order.
i := 0
for i < len(allDesc) && allDesc[i].Name < "/godebug/" {
i++
}
more := make([]Description, i, len(allDesc)+len(godebugs.All))
copy(more, allDesc)
for _, info := range godebugs.All {
if !info.Opaque {
more = append(more, Description{
Name: "/godebug/non-default-behavior/" + info.Name + ":events",
Description: "The number of non-default behaviors executed by the " +
info.Package + " package " + "due to a non-default " +
"GODEBUG=" + info.Name + "=... setting.",
Kind: KindUint64,
Cumulative: true,
})
}
}
allDesc = append(more, allDesc[i:]...)
}
// All returns a slice of containing metric descriptions for all supported metrics.
func All() []Description {
return allDesc

View File

@ -221,14 +221,9 @@ Below is the full list of supported metrics, ordered lexicographically.
/godebug/non-default-behavior/jstmpllitinterp:events
The number of non-default behaviors executed by
the html/templatepackage due to a non-default
the html/template package due to a non-default
GODEBUG=jstmpllitinterp=... setting.
/godebug/non-default-behavior/multipartfiles:events
The number of non-default behaviors executed by
the mime/multipart package due to a non-default
GODEBUG=multipartfiles=... setting.
/godebug/non-default-behavior/multipartmaxheaders:events
The number of non-default behaviors executed by
the mime/multipart package due to a non-default