1// Copyright 2015 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 objabi
6
7import (
8	"flag"
9	"fmt"
10	"internal/bisect"
11	"internal/buildcfg"
12	"io"
13	"log"
14	"os"
15	"reflect"
16	"sort"
17	"strconv"
18	"strings"
19)
20
21func Flagcount(name, usage string, val *int) {
22	flag.Var((*count)(val), name, usage)
23}
24
25func Flagfn1(name, usage string, f func(string)) {
26	flag.Var(fn1(f), name, usage)
27}
28
29func Flagprint(w io.Writer) {
30	flag.CommandLine.SetOutput(w)
31	flag.PrintDefaults()
32}
33
34func Flagparse(usage func()) {
35	flag.Usage = usage
36	os.Args = expandArgs(os.Args)
37	flag.Parse()
38}
39
40// expandArgs expands "response files" arguments in the provided slice.
41//
42// A "response file" argument starts with '@' and the rest of that
43// argument is a filename with CR-or-CRLF-separated arguments. Each
44// argument in the named files can also contain response file
45// arguments. See Issue 18468.
46//
47// The returned slice 'out' aliases 'in' iff the input did not contain
48// any response file arguments.
49//
50// TODO: handle relative paths of recursive expansions in different directories?
51// Is there a spec for this? Are relative paths allowed?
52func expandArgs(in []string) (out []string) {
53	// out is nil until we see a "@" argument.
54	for i, s := range in {
55		if strings.HasPrefix(s, "@") {
56			if out == nil {
57				out = make([]string, 0, len(in)*2)
58				out = append(out, in[:i]...)
59			}
60			slurp, err := os.ReadFile(s[1:])
61			if err != nil {
62				log.Fatal(err)
63			}
64			args := strings.Split(strings.TrimSpace(strings.Replace(string(slurp), "\r", "", -1)), "\n")
65			for i, arg := range args {
66				args[i] = DecodeArg(arg)
67			}
68			out = append(out, expandArgs(args)...)
69		} else if out != nil {
70			out = append(out, s)
71		}
72	}
73	if out == nil {
74		return in
75	}
76	return
77}
78
79func AddVersionFlag() {
80	flag.Var(versionFlag{}, "V", "print version and exit")
81}
82
83var buildID string // filled in by linker
84
85type versionFlag struct{}
86
87func (versionFlag) IsBoolFlag() bool { return true }
88func (versionFlag) Get() interface{} { return nil }
89func (versionFlag) String() string   { return "" }
90func (versionFlag) Set(s string) error {
91	name := os.Args[0]
92	name = name[strings.LastIndex(name, `/`)+1:]
93	name = name[strings.LastIndex(name, `\`)+1:]
94	name = strings.TrimSuffix(name, ".exe")
95
96	p := ""
97
98	if s == "goexperiment" {
99		// test/run.go uses this to discover the full set of
100		// experiment tags. Report everything.
101		p = " X:" + strings.Join(buildcfg.Experiment.All(), ",")
102	} else {
103		// If the enabled experiments differ from the baseline,
104		// include that difference.
105		if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
106			p = " X:" + goexperiment
107		}
108	}
109
110	// The go command invokes -V=full to get a unique identifier
111	// for this tool. It is assumed that the release version is sufficient
112	// for releases, but during development we include the full
113	// build ID of the binary, so that if the compiler is changed and
114	// rebuilt, we notice and rebuild all packages.
115	if s == "full" {
116		if strings.HasPrefix(buildcfg.Version, "devel") {
117			p += " buildID=" + buildID
118		}
119	}
120
121	fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
122	os.Exit(0)
123	return nil
124}
125
126// count is a flag.Value that is like a flag.Bool and a flag.Int.
127// If used as -name, it increments the count, but -name=x sets the count.
128// Used for verbose flag -v.
129type count int
130
131func (c *count) String() string {
132	return fmt.Sprint(int(*c))
133}
134
135func (c *count) Set(s string) error {
136	switch s {
137	case "true":
138		*c++
139	case "false":
140		*c = 0
141	default:
142		n, err := strconv.Atoi(s)
143		if err != nil {
144			return fmt.Errorf("invalid count %q", s)
145		}
146		*c = count(n)
147	}
148	return nil
149}
150
151func (c *count) Get() interface{} {
152	return int(*c)
153}
154
155func (c *count) IsBoolFlag() bool {
156	return true
157}
158
159func (c *count) IsCountFlag() bool {
160	return true
161}
162
163type fn1 func(string)
164
165func (f fn1) Set(s string) error {
166	f(s)
167	return nil
168}
169
170func (f fn1) String() string { return "" }
171
172// DecodeArg decodes an argument.
173//
174// This function is public for testing with the parallel encoder.
175func DecodeArg(arg string) string {
176	// If no encoding, fastpath out.
177	if !strings.ContainsAny(arg, "\\\n") {
178		return arg
179	}
180
181	var b strings.Builder
182	var wasBS bool
183	for _, r := range arg {
184		if wasBS {
185			switch r {
186			case '\\':
187				b.WriteByte('\\')
188			case 'n':
189				b.WriteByte('\n')
190			default:
191				// This shouldn't happen. The only backslashes that reach here
192				// should encode '\n' and '\\' exclusively.
193				panic("badly formatted input")
194			}
195		} else if r == '\\' {
196			wasBS = true
197			continue
198		} else {
199			b.WriteRune(r)
200		}
201		wasBS = false
202	}
203	return b.String()
204}
205
206type debugField struct {
207	name         string
208	help         string
209	concurrentOk bool        // true if this field/flag is compatible with concurrent compilation
210	val          interface{} // *int or *string
211}
212
213type DebugFlag struct {
214	tab          map[string]debugField
215	concurrentOk *bool    // this is non-nil only for compiler's DebugFlags, but only compiler has concurrent:ok fields
216	debugSSA     DebugSSA // this is non-nil only for compiler's DebugFlags.
217}
218
219// A DebugSSA function is called to set a -d ssa/... option.
220// If nil, those options are reported as invalid options.
221// If DebugSSA returns a non-empty string, that text is reported as a compiler error.
222// If phase is "help", it should print usage information and terminate the process.
223type DebugSSA func(phase, flag string, val int, valString string) string
224
225// NewDebugFlag constructs a DebugFlag for the fields of debug, which
226// must be a pointer to a struct.
227//
228// Each field of *debug is a different value, named for the lower-case of the field name.
229// Each field must be an int or string and must have a `help` struct tag.
230// There may be an "Any bool" field, which will be set if any debug flags are set.
231//
232// The returned flag takes a comma-separated list of settings.
233// Each setting is name=value; for ints, name is short for name=1.
234//
235// If debugSSA is non-nil, any debug flags of the form ssa/... will be
236// passed to debugSSA for processing.
237func NewDebugFlag(debug interface{}, debugSSA DebugSSA) *DebugFlag {
238	flag := &DebugFlag{
239		tab:      make(map[string]debugField),
240		debugSSA: debugSSA,
241	}
242
243	v := reflect.ValueOf(debug).Elem()
244	t := v.Type()
245	for i := 0; i < t.NumField(); i++ {
246		f := t.Field(i)
247		ptr := v.Field(i).Addr().Interface()
248		if f.Name == "ConcurrentOk" {
249			switch ptr := ptr.(type) {
250			default:
251				panic("debug.ConcurrentOk must have type bool")
252			case *bool:
253				flag.concurrentOk = ptr
254			}
255			continue
256		}
257		name := strings.ToLower(f.Name)
258		help := f.Tag.Get("help")
259		if help == "" {
260			panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
261		}
262		concurrent := f.Tag.Get("concurrent")
263
264		switch ptr.(type) {
265		default:
266			panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
267		case *int, *string, **bisect.Matcher:
268			// ok
269		}
270		flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
271	}
272
273	return flag
274}
275
276func (f *DebugFlag) Set(debugstr string) error {
277	if debugstr == "" {
278		return nil
279	}
280	for _, name := range strings.Split(debugstr, ",") {
281		if name == "" {
282			continue
283		}
284		// display help about the debug option itself and quit
285		if name == "help" {
286			fmt.Print(debugHelpHeader)
287			maxLen, names := 0, []string{}
288			if f.debugSSA != nil {
289				maxLen = len("ssa/help")
290			}
291			for name := range f.tab {
292				if len(name) > maxLen {
293					maxLen = len(name)
294				}
295				names = append(names, name)
296			}
297			sort.Strings(names)
298			// Indent multi-line help messages.
299			nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
300			for _, name := range names {
301				help := f.tab[name].help
302				fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.Replace(help, "\n", nl, -1))
303			}
304			if f.debugSSA != nil {
305				// ssa options have their own help
306				fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
307			}
308			os.Exit(0)
309		}
310
311		val, valstring, haveInt := 1, "", true
312		if i := strings.IndexAny(name, "=:"); i >= 0 {
313			var err error
314			name, valstring = name[:i], name[i+1:]
315			val, err = strconv.Atoi(valstring)
316			if err != nil {
317				val, haveInt = 1, false
318			}
319		}
320
321		if t, ok := f.tab[name]; ok {
322			switch vp := t.val.(type) {
323			case nil:
324				// Ignore
325			case *string:
326				*vp = valstring
327			case *int:
328				if !haveInt {
329					log.Fatalf("invalid debug value %v", name)
330				}
331				*vp = val
332			case **bisect.Matcher:
333				var err error
334				*vp, err = bisect.New(valstring)
335				if err != nil {
336					log.Fatalf("debug flag %v: %v", name, err)
337				}
338			default:
339				panic("bad debugtab type")
340			}
341			// assembler DebugFlags don't have a ConcurrentOk field to reset, so check against that.
342			if !t.concurrentOk && f.concurrentOk != nil {
343				*f.concurrentOk = false
344			}
345		} else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
346			// expect form ssa/phase/flag
347			// e.g. -d=ssa/generic_cse/time
348			// _ in phase name also matches space
349			phase := name[4:]
350			flag := "debug" // default flag is debug
351			if i := strings.Index(phase, "/"); i >= 0 {
352				flag = phase[i+1:]
353				phase = phase[:i]
354			}
355			err := f.debugSSA(phase, flag, val, valstring)
356			if err != "" {
357				log.Fatalf(err)
358			}
359			// Setting this false for -d=ssa/... preserves old behavior
360			// of turning off concurrency for any debug flags.
361			// It's not known for sure if this is necessary, but it is safe.
362			*f.concurrentOk = false
363
364		} else {
365			return fmt.Errorf("unknown debug key %s\n", name)
366		}
367	}
368
369	return nil
370}
371
372const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
373
374<key> is one of:
375
376`
377
378func (f *DebugFlag) String() string {
379	return ""
380}
381