Source file src/cmd/internal/objabi/flag.go

     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  
     5  package objabi
     6  
     7  import (
     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  
    21  func Flagcount(name, usage string, val *int) {
    22  	flag.Var((*count)(val), name, usage)
    23  }
    24  
    25  func Flagfn1(name, usage string, f func(string)) {
    26  	flag.Var(fn1(f), name, usage)
    27  }
    28  
    29  func Flagprint(w io.Writer) {
    30  	flag.CommandLine.SetOutput(w)
    31  	flag.PrintDefaults()
    32  }
    33  
    34  func 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 arguments. Arguments are separated by
    44  // whitespace, and can use single quotes (literal) or double quotes
    45  // (with escape sequences). Each argument in the named files can also
    46  // contain response file arguments. See Issue 77177.
    47  //
    48  // The returned slice 'out' aliases 'in' if the input did not contain
    49  // any response file arguments.
    50  //
    51  // TODO: handle relative paths of recursive expansions in different directories?
    52  // Is there a spec for this? Are relative paths allowed?
    53  func expandArgs(in []string) (out []string) {
    54  	// out is nil until we see a "@" argument.
    55  	for i, s := range in {
    56  		if strings.HasPrefix(s, "@") {
    57  			if out == nil {
    58  				out = make([]string, 0, len(in)*2)
    59  				out = append(out, in[:i]...)
    60  			}
    61  			slurp, err := os.ReadFile(s[1:])
    62  			if err != nil {
    63  				log.Fatal(err)
    64  			}
    65  			args := ParseArgs(slurp)
    66  			out = append(out, expandArgs(args)...)
    67  		} else if out != nil {
    68  			out = append(out, s)
    69  		}
    70  	}
    71  	if out == nil {
    72  		return in
    73  	}
    74  	return
    75  }
    76  
    77  // ParseArgs parses response file content into arguments using GCC-compatible rules.
    78  // Arguments are separated by whitespace. Single quotes preserve content literally.
    79  // Double quotes allow escape sequences: \\, \", \$, \`, and backslash-newline
    80  // for line continuation (both LF and CRLF). Outside quotes, backslash escapes the
    81  // next character, backslash-newline is line continuation (both LF and CRLF).
    82  // We aim to follow GCC's buildargv implementation.
    83  // Source code: https://github.com/gcc-mirror/gcc/blob/releases/gcc-15.2.0/libiberty/argv.c#L167
    84  // Known deviations from GCC:
    85  // - CRLF is treated as line continuation to be Windows-friendly; GCC only recognizes LF.
    86  // - Obsolete \f and \v are not treated as whitespaces
    87  // This function is public to test with cmd/go/internal/work.encodeArg
    88  func ParseArgs(s []byte) []string {
    89  	var args []string
    90  	var arg strings.Builder
    91  	hasArg := false // tracks if we've started an argument (for empty quotes)
    92  	inSingleQuote := false
    93  	inDoubleQuote := false
    94  	i := 0
    95  
    96  	for i < len(s) {
    97  		c := s[i]
    98  
    99  		if inSingleQuote {
   100  			if c == '\'' {
   101  				inSingleQuote = false
   102  			} else {
   103  				arg.WriteByte(c) // No escape processing in single quotes
   104  			}
   105  			i++
   106  			continue
   107  		}
   108  
   109  		if inDoubleQuote {
   110  			if c == '\\' && i+1 < len(s) {
   111  				next := s[i+1]
   112  				switch next {
   113  				case '\\':
   114  					arg.WriteByte('\\')
   115  					i += 2
   116  				case '"':
   117  					arg.WriteByte('"')
   118  					i += 2
   119  				case '$':
   120  					arg.WriteByte('$')
   121  					i += 2
   122  				case '`':
   123  					arg.WriteByte('`')
   124  					i += 2
   125  				case '\n':
   126  					// Line continuation - skip backslash and newline
   127  					i += 2
   128  				case '\r':
   129  					// Line continuation for CRLF - skip backslash, CR, and LF
   130  					if i+2 < len(s) && s[i+2] == '\n' {
   131  						i += 3
   132  					} else {
   133  						arg.WriteByte(c)
   134  						i++
   135  					}
   136  				default:
   137  					// Unknown escape - keep backslash and char
   138  					arg.WriteByte(c)
   139  					i++
   140  				}
   141  			} else if c == '"' {
   142  				inDoubleQuote = false
   143  				i++
   144  			} else {
   145  				arg.WriteByte(c)
   146  				i++
   147  			}
   148  			continue
   149  		}
   150  
   151  		// Normal mode (outside quotes)
   152  		switch c {
   153  		case ' ', '\t', '\n', '\r':
   154  			if arg.Len() > 0 || hasArg {
   155  				args = append(args, arg.String())
   156  				arg.Reset()
   157  				hasArg = false
   158  			}
   159  		case '\'':
   160  			inSingleQuote = true
   161  			hasArg = true // Empty quotes still produce an arg
   162  		case '"':
   163  			inDoubleQuote = true
   164  			hasArg = true // Empty quotes still produce an arg
   165  		case '\\':
   166  			// Backslash escapes the next character outside quotes.
   167  			// Backslash-newline is line continuation (handles both LF and CRLF).
   168  			if i+1 < len(s) {
   169  				next := s[i+1]
   170  				if next == '\n' {
   171  					i += 2
   172  					continue
   173  				}
   174  				if next == '\r' && i+2 < len(s) && s[i+2] == '\n' {
   175  					i += 3
   176  					continue
   177  				}
   178  				// Backslash escapes the next character
   179  				arg.WriteByte(next)
   180  				hasArg = true
   181  				i += 2
   182  				continue
   183  			}
   184  			// Trailing backslash at end of input — consumed and discarded
   185  			i++
   186  			continue
   187  		default:
   188  			arg.WriteByte(c)
   189  		}
   190  		i++
   191  	}
   192  
   193  	// Don't forget the last argument
   194  	if arg.Len() > 0 || hasArg {
   195  		args = append(args, arg.String())
   196  	}
   197  
   198  	return args
   199  }
   200  
   201  func AddVersionFlag() {
   202  	flag.Var(versionFlag{}, "V", "print version and exit")
   203  }
   204  
   205  var buildID string // filled in by linker
   206  
   207  type versionFlag struct{}
   208  
   209  func (versionFlag) IsBoolFlag() bool { return true }
   210  func (versionFlag) Get() any         { return nil }
   211  func (versionFlag) String() string   { return "" }
   212  func (versionFlag) Set(s string) error {
   213  	name := os.Args[0]
   214  	name = name[strings.LastIndex(name, `/`)+1:]
   215  	name = name[strings.LastIndex(name, `\`)+1:]
   216  	name = strings.TrimSuffix(name, ".exe")
   217  
   218  	p := ""
   219  
   220  	// If the enabled experiments differ from the baseline,
   221  	// include that difference.
   222  	if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
   223  		p = " X:" + goexperiment
   224  	}
   225  
   226  	// The go command invokes -V=full to get a unique identifier
   227  	// for this tool. It is assumed that the release version is sufficient
   228  	// for releases, but during development we include the full
   229  	// build ID of the binary, so that if the compiler is changed and
   230  	// rebuilt, we notice and rebuild all packages.
   231  	if s == "full" {
   232  		if strings.Contains(buildcfg.Version, "devel") {
   233  			p += " buildID=" + buildID
   234  		}
   235  	}
   236  
   237  	fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
   238  	os.Exit(0)
   239  	return nil
   240  }
   241  
   242  // count is a flag.Value that is like a flag.Bool and a flag.Int.
   243  // If used as -name, it increments the count, but -name=x sets the count.
   244  // Used for verbose flag -v.
   245  type count int
   246  
   247  func (c *count) String() string {
   248  	return fmt.Sprint(int(*c))
   249  }
   250  
   251  func (c *count) Set(s string) error {
   252  	switch s {
   253  	case "true":
   254  		*c++
   255  	case "false":
   256  		*c = 0
   257  	default:
   258  		n, err := strconv.Atoi(s)
   259  		if err != nil {
   260  			return fmt.Errorf("invalid count %q", s)
   261  		}
   262  		*c = count(n)
   263  	}
   264  	return nil
   265  }
   266  
   267  func (c *count) Get() any {
   268  	return int(*c)
   269  }
   270  
   271  func (c *count) IsBoolFlag() bool {
   272  	return true
   273  }
   274  
   275  func (c *count) IsCountFlag() bool {
   276  	return true
   277  }
   278  
   279  type fn1 func(string)
   280  
   281  func (f fn1) Set(s string) error {
   282  	f(s)
   283  	return nil
   284  }
   285  
   286  func (f fn1) String() string { return "" }
   287  
   288  type debugField struct {
   289  	name         string
   290  	help         string
   291  	concurrentOk bool // true if this field/flag is compatible with concurrent compilation
   292  	val          any  // *int or *string
   293  }
   294  
   295  type DebugFlag struct {
   296  	tab          map[string]debugField
   297  	concurrentOk *bool    // this is non-nil only for compiler's DebugFlags, but only compiler has concurrent:ok fields
   298  	debugSSA     DebugSSA // this is non-nil only for compiler's DebugFlags.
   299  }
   300  
   301  // A DebugSSA function is called to set a -d ssa/... option.
   302  // If nil, those options are reported as invalid options.
   303  // If DebugSSA returns a non-empty string, that text is reported as a compiler error.
   304  // If phase is "help", it should print usage information and terminate the process.
   305  type DebugSSA func(phase, flag string, val int, valString string) string
   306  
   307  // NewDebugFlag constructs a DebugFlag for the fields of debug, which
   308  // must be a pointer to a struct.
   309  //
   310  // Each field of *debug is a different value, named for the lower-case of the field name.
   311  // Each field must be an int or string and must have a `help` struct tag.
   312  // There may be an "Any bool" field, which will be set if any debug flags are set.
   313  //
   314  // The returned flag takes a comma-separated list of settings.
   315  // Each setting is name=value; for ints, name is short for name=1.
   316  //
   317  // If debugSSA is non-nil, any debug flags of the form ssa/... will be
   318  // passed to debugSSA for processing.
   319  func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
   320  	flag := &DebugFlag{
   321  		tab:      make(map[string]debugField),
   322  		debugSSA: debugSSA,
   323  	}
   324  
   325  	v := reflect.ValueOf(debug).Elem()
   326  	t := v.Type()
   327  	for i := 0; i < t.NumField(); i++ {
   328  		f := t.Field(i)
   329  		ptr := v.Field(i).Addr().Interface()
   330  		if f.Name == "ConcurrentOk" {
   331  			switch ptr := ptr.(type) {
   332  			default:
   333  				panic("debug.ConcurrentOk must have type bool")
   334  			case *bool:
   335  				flag.concurrentOk = ptr
   336  			}
   337  			continue
   338  		}
   339  		name := strings.ToLower(f.Name)
   340  		help := f.Tag.Get("help")
   341  		if help == "" {
   342  			panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
   343  		}
   344  		concurrent := f.Tag.Get("concurrent")
   345  
   346  		switch ptr.(type) {
   347  		default:
   348  			panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
   349  		case *int, *string, **bisect.Matcher:
   350  			// ok
   351  		}
   352  		flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
   353  	}
   354  
   355  	return flag
   356  }
   357  
   358  func (f *DebugFlag) Set(debugstr string) error {
   359  	if debugstr == "" {
   360  		return nil
   361  	}
   362  	for name := range strings.SplitSeq(debugstr, ",") {
   363  		if name == "" {
   364  			continue
   365  		}
   366  		// display help about the debug option itself and quit
   367  		if name == "help" {
   368  			fmt.Print(debugHelpHeader)
   369  			maxLen, names := 0, []string{}
   370  			if f.debugSSA != nil {
   371  				maxLen = len("ssa/help")
   372  			}
   373  			for name := range f.tab {
   374  				if len(name) > maxLen {
   375  					maxLen = len(name)
   376  				}
   377  				names = append(names, name)
   378  			}
   379  			sort.Strings(names)
   380  			// Indent multi-line help messages.
   381  			nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
   382  			for _, name := range names {
   383  				help := f.tab[name].help
   384  				fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
   385  			}
   386  			if f.debugSSA != nil {
   387  				// ssa options have their own help
   388  				fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
   389  			}
   390  			os.Exit(0)
   391  		}
   392  
   393  		val, valstring, haveInt := 1, "", true
   394  		if i := strings.IndexAny(name, "=:"); i >= 0 {
   395  			var err error
   396  			name, valstring = name[:i], name[i+1:]
   397  			val, err = strconv.Atoi(valstring)
   398  			if err != nil {
   399  				val, haveInt = 1, false
   400  			}
   401  		}
   402  
   403  		if t, ok := f.tab[name]; ok {
   404  			switch vp := t.val.(type) {
   405  			case nil:
   406  				// Ignore
   407  			case *string:
   408  				*vp = valstring
   409  			case *int:
   410  				if !haveInt {
   411  					log.Fatalf("invalid debug value %v", name)
   412  				}
   413  				*vp = val
   414  			case **bisect.Matcher:
   415  				var err error
   416  				*vp, err = bisect.New(valstring)
   417  				if err != nil {
   418  					log.Fatalf("debug flag %v: %v", name, err)
   419  				}
   420  			default:
   421  				panic("bad debugtab type")
   422  			}
   423  			// assembler DebugFlags don't have a ConcurrentOk field to reset, so check against that.
   424  			if !t.concurrentOk && f.concurrentOk != nil {
   425  				*f.concurrentOk = false
   426  			}
   427  		} else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
   428  			// expect form ssa/phase/flag
   429  			// e.g. -d=ssa/generic_cse/time
   430  			// _ in phase name also matches space
   431  			phase := name[4:]
   432  			flag := "debug" // default flag is debug
   433  			if i := strings.Index(phase, "/"); i >= 0 {
   434  				flag = phase[i+1:]
   435  				phase = phase[:i]
   436  			}
   437  			err := f.debugSSA(phase, flag, val, valstring)
   438  			if err != "" {
   439  				log.Fatal(err)
   440  			}
   441  			// Setting this false for -d=ssa/... preserves old behavior
   442  			// of turning off concurrency for any debug flags.
   443  			// It's not known for sure if this is necessary, but it is safe.
   444  			*f.concurrentOk = false
   445  
   446  		} else {
   447  			return fmt.Errorf("unknown debug key %s\n", name)
   448  		}
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
   455  
   456  <key> is one of:
   457  
   458  `
   459  
   460  func (f *DebugFlag) String() string {
   461  	return ""
   462  }
   463  

View as plain text