1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package load
6
7import (
8	"errors"
9	"fmt"
10	"go/build"
11	"internal/godebugs"
12	"sort"
13	"strconv"
14	"strings"
15
16	"cmd/go/internal/gover"
17	"cmd/go/internal/modload"
18)
19
20var ErrNotGoDebug = errors.New("not //go:debug line")
21
22func ParseGoDebug(text string) (key, value string, err error) {
23	if !strings.HasPrefix(text, "//go:debug") {
24		return "", "", ErrNotGoDebug
25	}
26	i := strings.IndexAny(text, " \t")
27	if i < 0 {
28		if strings.TrimSpace(text) == "//go:debug" {
29			return "", "", fmt.Errorf("missing key=value")
30		}
31		return "", "", ErrNotGoDebug
32	}
33	k, v, ok := strings.Cut(strings.TrimSpace(text[i:]), "=")
34	if !ok {
35		return "", "", fmt.Errorf("missing key=value")
36	}
37	if err := modload.CheckGodebug("//go:debug setting", k, v); err != nil {
38		return "", "", err
39	}
40	return k, v, nil
41}
42
43// defaultGODEBUG returns the default GODEBUG setting for the main package p.
44// When building a test binary, directives, testDirectives, and xtestDirectives
45// list additional directives from the package under test.
46func defaultGODEBUG(p *Package, directives, testDirectives, xtestDirectives []build.Directive) string {
47	if p.Name != "main" {
48		return ""
49	}
50	goVersion := modload.MainModules.GoVersion()
51	if modload.RootMode == modload.NoRoot && p.Module != nil {
52		// This is go install pkg@version or go run pkg@version.
53		// Use the Go version from the package.
54		// If there isn't one, then assume Go 1.20,
55		// the last version before GODEBUGs were introduced.
56		goVersion = p.Module.GoVersion
57		if goVersion == "" {
58			goVersion = "1.20"
59		}
60	}
61
62	var m map[string]string
63	for _, g := range modload.MainModules.Godebugs() {
64		if m == nil {
65			m = make(map[string]string)
66		}
67		m[g.Key] = g.Value
68	}
69	for _, list := range [][]build.Directive{p.Internal.Build.Directives, directives, testDirectives, xtestDirectives} {
70		for _, d := range list {
71			k, v, err := ParseGoDebug(d.Text)
72			if err != nil {
73				continue
74			}
75			if m == nil {
76				m = make(map[string]string)
77			}
78			m[k] = v
79		}
80	}
81	if v, ok := m["default"]; ok {
82		delete(m, "default")
83		v = strings.TrimPrefix(v, "go")
84		if gover.IsValid(v) {
85			goVersion = v
86		}
87	}
88
89	defaults := godebugForGoVersion(goVersion)
90	if defaults != nil {
91		// Apply m on top of defaults.
92		for k, v := range m {
93			defaults[k] = v
94		}
95		m = defaults
96	}
97
98	var keys []string
99	for k := range m {
100		keys = append(keys, k)
101	}
102	sort.Strings(keys)
103	var b strings.Builder
104	for _, k := range keys {
105		if b.Len() > 0 {
106			b.WriteString(",")
107		}
108		b.WriteString(k)
109		b.WriteString("=")
110		b.WriteString(m[k])
111	}
112	return b.String()
113}
114
115func godebugForGoVersion(v string) map[string]string {
116	if strings.Count(v, ".") >= 2 {
117		i := strings.Index(v, ".")
118		j := i + 1 + strings.Index(v[i+1:], ".")
119		v = v[:j]
120	}
121
122	if !strings.HasPrefix(v, "1.") {
123		return nil
124	}
125	n, err := strconv.Atoi(v[len("1."):])
126	if err != nil {
127		return nil
128	}
129
130	def := make(map[string]string)
131	for _, info := range godebugs.All {
132		if n < info.Changed {
133			def[info.Name] = info.Old
134		}
135	}
136	return def
137}
138