blob: 817cc4faebf7b477cd07d5506a4aafa41e4d02a9 [file] [log] [blame] [edit]
// 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 (
"errors"
"fmt"
"go/build"
"internal/godebugs"
"maps"
"sort"
"strconv"
"strings"
"cmd/go/internal/fips140"
"cmd/go/internal/gover"
"cmd/go/internal/modload"
)
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 err := modload.CheckGodebug("//go:debug setting", k, v); err != nil {
return "", "", err
}
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(loaderstate *modload.State, p *Package, directives, testDirectives, xtestDirectives []build.Directive) string {
if p.Name != "main" {
return ""
}
goVersion := loaderstate.MainModules.GoVersion(loaderstate)
if loaderstate.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 assume Go 1.20,
// the last version before GODEBUGs were introduced.
goVersion = p.Module.GoVersion
if goVersion == "" {
goVersion = "1.20"
}
}
var m map[string]string
// If GOFIPS140 is set to anything but "off",
// default to GODEBUG=fips140=on.
if fips140.Enabled() {
if m == nil {
m = make(map[string]string)
}
m["fips140"] = "on"
}
// Add directives from main module go.mod.
for _, g := range loaderstate.MainModules.Godebugs(loaderstate) {
if m == nil {
m = make(map[string]string)
}
m[g.Key] = g.Value
}
// Add directives from packages.
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
}
}
if v, ok := m["default"]; ok {
delete(m, "default")
v = strings.TrimPrefix(v, "go")
if gover.IsValid(v) {
goVersion = v
}
}
defaults := godebugForGoVersion(goVersion)
if defaults != nil {
// Apply m on top of defaults.
maps.Copy(defaults, m)
m = defaults
}
keys := make([]string, 0, len(m))
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 _, info := range godebugs.All {
if n < info.Changed {
def[info.Name] = info.Old
}
}
return def
}