Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go

     1  // Copyright 2010 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 printf
     6  
     7  import (
     8  	_ "embed"
     9  	"fmt"
    10  	"go/ast"
    11  	"go/constant"
    12  	"go/token"
    13  	"go/types"
    14  	"reflect"
    15  	"regexp"
    16  	"sort"
    17  	"strings"
    18  
    19  	"golang.org/x/tools/go/analysis"
    20  	"golang.org/x/tools/go/analysis/passes/inspect"
    21  	"golang.org/x/tools/go/ast/edge"
    22  	"golang.org/x/tools/go/ast/inspector"
    23  	"golang.org/x/tools/go/types/typeutil"
    24  	"golang.org/x/tools/internal/analysis/analyzerutil"
    25  	"golang.org/x/tools/internal/astutil"
    26  	"golang.org/x/tools/internal/fmtstr"
    27  	"golang.org/x/tools/internal/typeparams"
    28  	"golang.org/x/tools/internal/typesinternal"
    29  	"golang.org/x/tools/internal/versions"
    30  	"golang.org/x/tools/refactor/satisfy"
    31  )
    32  
    33  func init() {
    34  	Analyzer.Flags.Var(isPrint, "funcs", "comma-separated list of print function names to check")
    35  }
    36  
    37  //go:embed doc.go
    38  var doc string
    39  
    40  var Analyzer = &analysis.Analyzer{
    41  	Name:       "printf",
    42  	Doc:        analyzerutil.MustExtractDoc(doc, "printf"),
    43  	URL:        "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
    44  	Requires:   []*analysis.Analyzer{inspect.Analyzer},
    45  	Run:        run,
    46  	ResultType: reflect.TypeFor[*Result](),
    47  	FactTypes:  []analysis.Fact{new(isWrapper)},
    48  }
    49  
    50  // Kind is a kind of fmt function behavior.
    51  type Kind int
    52  
    53  const (
    54  	KindNone   Kind = iota // not a fmt wrapper function
    55  	KindPrint              // function behaves like fmt.Print
    56  	KindPrintf             // function behaves like fmt.Printf
    57  	KindErrorf             // function behaves like fmt.Errorf
    58  )
    59  
    60  func (kind Kind) String() string {
    61  	switch kind {
    62  	case KindPrint:
    63  		return "print"
    64  	case KindPrintf:
    65  		return "printf"
    66  	case KindErrorf:
    67  		return "errorf"
    68  	}
    69  	return "(none)"
    70  }
    71  
    72  // Result is the printf analyzer's result type. Clients may query the result
    73  // to learn whether a function behaves like fmt.Print or fmt.Printf.
    74  type Result struct {
    75  	funcs map[types.Object]Kind
    76  }
    77  
    78  // Kind reports whether fn behaves like fmt.Print or fmt.Printf.
    79  func (r *Result) Kind(fn *types.Func) Kind {
    80  	_, ok := isPrint[fn.FullName()]
    81  	if !ok {
    82  		// Next look up just "printf", for use with -printf.funcs.
    83  		_, ok = isPrint[strings.ToLower(fn.Name())]
    84  	}
    85  	if ok {
    86  		if strings.HasSuffix(fn.Name(), "f") {
    87  			return KindPrintf
    88  		} else {
    89  			return KindPrint
    90  		}
    91  	}
    92  
    93  	return r.funcs[fn]
    94  }
    95  
    96  // isWrapper is a fact indicating that a function is a print or printf wrapper.
    97  type isWrapper struct{ Kind Kind }
    98  
    99  func (f *isWrapper) AFact() {}
   100  
   101  func (f *isWrapper) String() string {
   102  	switch f.Kind {
   103  	case KindPrintf:
   104  		return "printfWrapper"
   105  	case KindPrint:
   106  		return "printWrapper"
   107  	case KindErrorf:
   108  		return "errorfWrapper"
   109  	default:
   110  		return "unknownWrapper"
   111  	}
   112  }
   113  
   114  func run(pass *analysis.Pass) (any, error) {
   115  	res := &Result{
   116  		funcs: make(map[types.Object]Kind),
   117  	}
   118  	findPrintLike(pass, res)
   119  	checkCalls(pass, res)
   120  	return res, nil
   121  }
   122  
   123  // A wrapper is a candidate print/printf wrapper function.
   124  //
   125  // We represent functions generally as types.Object, not *Func, so
   126  // that we can analyze anonymous functions such as
   127  //
   128  //	printf := func(format string, args ...any) {...},
   129  //
   130  // representing them by the *types.Var symbol for the local variable
   131  // 'printf'.
   132  type wrapper struct {
   133  	obj     types.Object     // *Func or *Var
   134  	curBody inspector.Cursor // for *ast.BlockStmt
   135  	format  *types.Var       // optional "format string" parameter in the Func{Decl,Lit}
   136  	args    *types.Var       // "args ...any" parameter in the Func{Decl,Lit}
   137  	callers []printfCaller
   138  }
   139  
   140  // printfCaller is a candidate print{,f} forwarding call from candidate wrapper w.
   141  type printfCaller struct {
   142  	w    *wrapper
   143  	call *ast.CallExpr // forwarding call (nil for implicit interface method -> impl calls)
   144  }
   145  
   146  // formatArgsParams returns the "format string" and "args ...any"
   147  // parameters of a potential print or printf wrapper function.
   148  // (The format is nil in the print-like case.)
   149  func formatArgsParams(sig *types.Signature) (format, args *types.Var) {
   150  	if !sig.Variadic() {
   151  		return nil, nil // not variadic
   152  	}
   153  
   154  	params := sig.Params()
   155  	nparams := params.Len() // variadic => nonzero
   156  
   157  	// Is second last param 'format string'?
   158  	if nparams >= 2 {
   159  		if p := params.At(nparams - 2); p.Type() == types.Typ[types.String] {
   160  			format = p
   161  		}
   162  	}
   163  
   164  	// Check final parameter is "args ...any".
   165  	// (variadic => slice)
   166  	args = params.At(nparams - 1)
   167  	iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
   168  	if !ok || !iface.Empty() {
   169  		return nil, nil
   170  	}
   171  
   172  	return format, args
   173  }
   174  
   175  // findPrintLike scans the entire package to find print or printf-like functions.
   176  // When it returns, all such functions have been identified.
   177  func findPrintLike(pass *analysis.Pass, res *Result) {
   178  	var (
   179  		inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   180  		info    = pass.TypesInfo
   181  	)
   182  
   183  	// Pass 1: gather candidate wrapper functions (and populate wrappers).
   184  	var (
   185  		wrappers []*wrapper
   186  		byObj    = make(map[types.Object]*wrapper)
   187  	)
   188  	for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil), (*ast.InterfaceType)(nil)) {
   189  
   190  		// addWrapper records that a func (or var representing
   191  		// a FuncLit) is a potential print{,f} wrapper.
   192  		// curBody is its *ast.BlockStmt, if any.
   193  		addWrapper := func(obj types.Object, sig *types.Signature, curBody inspector.Cursor) *wrapper {
   194  			format, args := formatArgsParams(sig)
   195  			if args != nil {
   196  				// obj (the symbol for a function/method, or variable
   197  				// assigned to an anonymous function) is a potential
   198  				// print or printf wrapper.
   199  				//
   200  				// Later processing will analyze the graph of potential
   201  				// wrappers and their function bodies to pick out the
   202  				// ones that are true wrappers.
   203  				w := &wrapper{
   204  					obj:     obj,
   205  					curBody: curBody,
   206  					format:  format, // non-nil => printf
   207  					args:    args,
   208  				}
   209  				byObj[w.obj] = w
   210  				wrappers = append(wrappers, w)
   211  				return w
   212  			}
   213  			return nil
   214  		}
   215  
   216  		switch f := cur.Node().(type) {
   217  		case *ast.FuncDecl:
   218  			// named function or method:
   219  			//
   220  			//    func wrapf(format string, args ...any) {...}
   221  			if f.Body != nil {
   222  				fn := info.Defs[f.Name].(*types.Func)
   223  				addWrapper(fn, fn.Signature(), cur.ChildAt(edge.FuncDecl_Body, -1))
   224  			}
   225  
   226  		case *ast.FuncLit:
   227  			// anonymous function directly assigned to a variable:
   228  			//
   229  			//    var wrapf = func(format string, args ...any) {...}
   230  			//    wrapf    := func(format string, args ...any) {...}
   231  			//    wrapf     = func(format string, args ...any) {...}
   232  			//
   233  			// The LHS may also be a struct field x.wrapf or
   234  			// an imported var pkg.Wrapf.
   235  			//
   236  			var lhs ast.Expr
   237  			switch ek, idx := cur.ParentEdge(); ek {
   238  			case edge.ValueSpec_Values:
   239  				curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx)
   240  				lhs = curName.Node().(*ast.Ident)
   241  			case edge.AssignStmt_Rhs:
   242  				curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
   243  				lhs = curLhs.Node().(ast.Expr)
   244  			}
   245  
   246  			var v *types.Var
   247  			switch lhs := lhs.(type) {
   248  			case *ast.Ident:
   249  				// variable: wrapf = func(...)
   250  				v, _ = info.ObjectOf(lhs).(*types.Var)
   251  			case *ast.SelectorExpr:
   252  				if sel, ok := info.Selections[lhs]; ok {
   253  					// struct field: x.wrapf = func(...)
   254  					v = sel.Obj().(*types.Var)
   255  				} else {
   256  					// imported var: pkg.Wrapf = func(...)
   257  					v = info.Uses[lhs.Sel].(*types.Var)
   258  				}
   259  			}
   260  			if v != nil {
   261  				sig := info.TypeOf(f).(*types.Signature)
   262  				curBody := cur.ChildAt(edge.FuncLit_Body, -1)
   263  				addWrapper(v, sig, curBody)
   264  			}
   265  
   266  		case *ast.InterfaceType:
   267  			// Induction through interface methods is gated as
   268  			// if it were a go1.26 language feature, to avoid
   269  			// surprises when go test's vet suite gets stricter.
   270  			if analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_26) {
   271  				for imeth := range info.TypeOf(f).(*types.Interface).Methods() {
   272  					addWrapper(imeth, imeth.Signature(), inspector.Cursor{})
   273  				}
   274  			}
   275  		}
   276  	}
   277  
   278  	// impls maps abstract methods to implementations.
   279  	//
   280  	// Interface methods are modelled as if they have a body
   281  	// that calls each implementing method.
   282  	//
   283  	// In the code below, impls maps Logger.Logf to
   284  	// [myLogger.Logf], and if myLogger.Logf is discovered to be
   285  	// printf-like, then so will be Logger.Logf.
   286  	//
   287  	//   type Logger interface {
   288  	// 	Logf(format string, args ...any)
   289  	//   }
   290  	//   type myLogger struct{ ... }
   291  	//   func (myLogger) Logf(format string, args ...any) {...}
   292  	//   var _ Logger = myLogger{}
   293  	impls := methodImplementations(pass)
   294  
   295  	// doCall records a call from one wrapper to another.
   296  	doCall := func(w *wrapper, callee types.Object, call *ast.CallExpr) {
   297  		// Call from one wrapper candidate to another?
   298  		// Record the edge so that if callee is found to be
   299  		// a true wrapper, w will be too.
   300  		if w2, ok := byObj[callee]; ok {
   301  			w2.callers = append(w2.callers, printfCaller{w, call})
   302  		}
   303  
   304  		// Is the candidate a true wrapper, because it calls
   305  		// a known print{,f}-like function from the allowlist
   306  		// or an imported fact, or another wrapper found
   307  		// to be a true wrapper?
   308  		// If so, convert all w's callers to kind.
   309  		kind := callKind(pass, callee, res)
   310  		if kind != KindNone {
   311  			propagate(pass, w, call, kind, res)
   312  		}
   313  	}
   314  
   315  	// Pass 2: scan the body of each wrapper function
   316  	// for calls to other printf-like functions.
   317  	for _, w := range wrappers {
   318  
   319  		// An interface method has no body, but acts
   320  		// like an implicit call to each implementing method.
   321  		if w.curBody.Inspector() == nil {
   322  			for impl := range impls[w.obj.(*types.Func)] {
   323  				doCall(w, impl, nil)
   324  			}
   325  			continue // (no body)
   326  		}
   327  
   328  		// Process all calls in the wrapper function's body.
   329  	scan:
   330  		for cur := range w.curBody.Preorder(
   331  			(*ast.AssignStmt)(nil),
   332  			(*ast.UnaryExpr)(nil),
   333  			(*ast.CallExpr)(nil),
   334  		) {
   335  			switch n := cur.Node().(type) {
   336  
   337  			// Reject tricky cases where the parameters
   338  			// are potentially mutated by AssignStmt or UnaryExpr.
   339  			// (This logic checks for mutation only before the call.)
   340  			// TODO: Relax these checks; issue 26555.
   341  
   342  			case *ast.AssignStmt:
   343  				// If the wrapper updates format or args
   344  				// it is not a simple wrapper.
   345  				for _, lhs := range n.Lhs {
   346  					if w.format != nil && match(info, lhs, w.format) ||
   347  						match(info, lhs, w.args) {
   348  						break scan
   349  					}
   350  				}
   351  
   352  			case *ast.UnaryExpr:
   353  				// If the wrapper computes &format or &args,
   354  				// it is not a simple wrapper.
   355  				if n.Op == token.AND &&
   356  					(w.format != nil && match(info, n.X, w.format) ||
   357  						match(info, n.X, w.args)) {
   358  					break scan
   359  				}
   360  
   361  			case *ast.CallExpr:
   362  				if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) {
   363  					if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
   364  						doCall(w, callee, n)
   365  					}
   366  				}
   367  			}
   368  		}
   369  	}
   370  }
   371  
   372  // methodImplementations returns the mapping from interface methods
   373  // declared in this package to their corresponding implementing
   374  // methods (which may also be interface methods), according to the set
   375  // of assignments to interface types that appear within this package.
   376  func methodImplementations(pass *analysis.Pass) map[*types.Func]map[*types.Func]bool {
   377  	impls := make(map[*types.Func]map[*types.Func]bool)
   378  
   379  	// To find interface/implementation relations,
   380  	// we use the 'satisfy' pass, but proposal #70638
   381  	// provides a better way.
   382  	//
   383  	// This pass over the syntax could be factored out as
   384  	// a separate analysis pass if it is needed by other
   385  	// analyzers.
   386  	var f satisfy.Finder
   387  	f.Find(pass.TypesInfo, pass.Files)
   388  	for assign := range f.Result {
   389  		// Have: LHS = RHS, where LHS is an interface type.
   390  		for imeth := range assign.LHS.Underlying().(*types.Interface).Methods() {
   391  			// Limit to interface methods of current package.
   392  			if imeth.Pkg() != pass.Pkg {
   393  				continue
   394  			}
   395  
   396  			if _, args := formatArgsParams(imeth.Signature()); args == nil {
   397  				continue // not print{,f}-like
   398  			}
   399  
   400  			// Add implementing method to the set.
   401  			impl, _, _ := types.LookupFieldOrMethod(assign.RHS, false, pass.Pkg, imeth.Name()) // can't fail
   402  			set, ok := impls[imeth]
   403  			if !ok {
   404  				set = make(map[*types.Func]bool)
   405  				impls[imeth] = set
   406  			}
   407  			set[impl.(*types.Func)] = true
   408  		}
   409  	}
   410  	return impls
   411  }
   412  
   413  func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
   414  	id, ok := arg.(*ast.Ident)
   415  	return ok && info.ObjectOf(id) == param
   416  }
   417  
   418  // propagate propagates changes in wrapper (non-None) kind information backwards
   419  // through through the wrapper.callers graph of well-formed forwarding calls.
   420  func propagate(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) {
   421  	// Check correct call forwarding.
   422  	//
   423  	// Interface methods (call==nil) forward
   424  	// correctly by construction.
   425  	if call != nil && !checkForward(pass, w, call, kind) {
   426  		return
   427  	}
   428  
   429  	// If the candidate's print{,f} status becomes known,
   430  	// propagate it back to all its so-far known callers.
   431  	if res.funcs[w.obj] != kind {
   432  		res.funcs[w.obj] = kind
   433  
   434  		// Export a fact.
   435  		// (This is a no-op for local symbols.)
   436  		// We can't export facts on a symbol of another package,
   437  		// but we can treat the symbol as a wrapper within
   438  		// the current analysis unit.
   439  		if w.obj.Pkg() == pass.Pkg {
   440  			// Facts are associated with origins.
   441  			pass.ExportObjectFact(origin(w.obj), &isWrapper{Kind: kind})
   442  		}
   443  
   444  		// Propagate kind back to known callers.
   445  		for _, caller := range w.callers {
   446  			propagate(pass, caller.w, caller.call, kind, res)
   447  		}
   448  	}
   449  }
   450  
   451  // checkForward checks whether a call from wrapper w is a well-formed
   452  // forwarding call of the specified (non-None) kind.
   453  //
   454  // If not, it reports a diagnostic that the user wrote
   455  // fmt.Printf(format, args) instead of fmt.Printf(format, args...).
   456  func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind) bool {
   457  	// Printf/Errorf calls must delegate the format string.
   458  	switch kind {
   459  	case KindPrintf, KindErrorf:
   460  		if len(call.Args) < 2 || !match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) {
   461  			return false
   462  		}
   463  	}
   464  
   465  	// The args... delegation must be variadic.
   466  	// (That args is actually delegated was
   467  	// established before the root call to doCall.)
   468  	if !call.Ellipsis.IsValid() {
   469  		typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature)
   470  		if !ok {
   471  			return false
   472  		}
   473  		if len(call.Args) > typ.Params().Len() {
   474  			// If we're passing more arguments than what the
   475  			// print/printf function can take, adding an ellipsis
   476  			// would break the program. For example:
   477  			//
   478  			//   func foo(arg1 string, arg2 ...interface{}) {
   479  			//       fmt.Printf("%s %v", arg1, arg2)
   480  			//   }
   481  			return false
   482  		}
   483  		pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", kind)
   484  		return false
   485  	}
   486  
   487  	return true
   488  }
   489  
   490  func origin(obj types.Object) types.Object {
   491  	switch obj := obj.(type) {
   492  	case *types.Func:
   493  		return obj.Origin()
   494  	case *types.Var:
   495  		return obj.Origin()
   496  	}
   497  	return obj
   498  }
   499  
   500  // isPrint records the print functions.
   501  // If a key ends in 'f' then it is assumed to be a formatted print.
   502  //
   503  // Keys are either values returned by (*types.Func).FullName,
   504  // or case-insensitive identifiers such as "errorf".
   505  //
   506  // The -funcs flag adds to this set.
   507  //
   508  // The set below includes facts for many important standard library
   509  // functions, even though the analysis is capable of deducing that, for
   510  // example, fmt.Printf forwards to fmt.Fprintf. We avoid relying on the
   511  // driver applying analyzers to standard packages because "go vet" does
   512  // not do so with gccgo, and nor do some other build systems.
   513  var isPrint = stringSet{
   514  	"fmt.Appendf":  true,
   515  	"fmt.Append":   true,
   516  	"fmt.Appendln": true,
   517  	"fmt.Errorf":   true,
   518  	"fmt.Fprint":   true,
   519  	"fmt.Fprintf":  true,
   520  	"fmt.Fprintln": true,
   521  	"fmt.Print":    true,
   522  	"fmt.Printf":   true,
   523  	"fmt.Println":  true,
   524  	"fmt.Sprint":   true,
   525  	"fmt.Sprintf":  true,
   526  	"fmt.Sprintln": true,
   527  
   528  	"runtime/trace.Logf": true,
   529  
   530  	"log.Print":             true,
   531  	"log.Printf":            true,
   532  	"log.Println":           true,
   533  	"log.Fatal":             true,
   534  	"log.Fatalf":            true,
   535  	"log.Fatalln":           true,
   536  	"log.Panic":             true,
   537  	"log.Panicf":            true,
   538  	"log.Panicln":           true,
   539  	"(*log.Logger).Fatal":   true,
   540  	"(*log.Logger).Fatalf":  true,
   541  	"(*log.Logger).Fatalln": true,
   542  	"(*log.Logger).Panic":   true,
   543  	"(*log.Logger).Panicf":  true,
   544  	"(*log.Logger).Panicln": true,
   545  	"(*log.Logger).Print":   true,
   546  	"(*log.Logger).Printf":  true,
   547  	"(*log.Logger).Println": true,
   548  
   549  	"(*testing.common).Error":  true,
   550  	"(*testing.common).Errorf": true,
   551  	"(*testing.common).Fatal":  true,
   552  	"(*testing.common).Fatalf": true,
   553  	"(*testing.common).Log":    true,
   554  	"(*testing.common).Logf":   true,
   555  	"(*testing.common).Skip":   true,
   556  	"(*testing.common).Skipf":  true,
   557  	"(testing.TB).Error":       true,
   558  	"(testing.TB).Errorf":      true,
   559  	"(testing.TB).Fatal":       true,
   560  	"(testing.TB).Fatalf":      true,
   561  	"(testing.TB).Log":         true,
   562  	"(testing.TB).Logf":        true,
   563  	"(testing.TB).Skip":        true,
   564  	"(testing.TB).Skipf":       true,
   565  }
   566  
   567  // formatStringIndex returns the index of the format string (the last
   568  // non-variadic parameter) within the given printf-like call
   569  // expression, or -1 if unknown.
   570  func formatStringIndex(pass *analysis.Pass, call *ast.CallExpr) int {
   571  	typ := pass.TypesInfo.Types[call.Fun].Type
   572  	if typ == nil {
   573  		return -1 // missing type
   574  	}
   575  	sig, ok := typ.(*types.Signature)
   576  	if !ok {
   577  		return -1 // ill-typed
   578  	}
   579  	if !sig.Variadic() {
   580  		// Skip checking non-variadic functions.
   581  		return -1
   582  	}
   583  	idx := sig.Params().Len() - 2
   584  	if idx < 0 {
   585  		// Skip checking variadic functions without
   586  		// fixed arguments.
   587  		return -1
   588  	}
   589  	return idx
   590  }
   591  
   592  // stringConstantExpr returns expression's string constant value.
   593  //
   594  // ("", false) is returned if expression isn't a string
   595  // constant.
   596  func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
   597  	lit := pass.TypesInfo.Types[expr].Value
   598  	if lit != nil && lit.Kind() == constant.String {
   599  		return constant.StringVal(lit), true
   600  	}
   601  	return "", false
   602  }
   603  
   604  // checkCalls triggers the print-specific checks for calls that invoke a print
   605  // function.
   606  func checkCalls(pass *analysis.Pass, res *Result) {
   607  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
   608  	nodeFilter := []ast.Node{
   609  		(*ast.File)(nil),
   610  		(*ast.CallExpr)(nil),
   611  	}
   612  
   613  	var fileVersion string // for selectively suppressing checks; "" if unknown.
   614  	inspect.Preorder(nodeFilter, func(n ast.Node) {
   615  		switch n := n.(type) {
   616  		case *ast.File:
   617  			fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
   618  
   619  		case *ast.CallExpr:
   620  			if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
   621  				kind := callKind(pass, callee, res)
   622  				switch kind {
   623  				case KindPrintf, KindErrorf:
   624  					checkPrintf(pass, fileVersion, kind, n, fullname(callee))
   625  				case KindPrint:
   626  					checkPrint(pass, n, fullname(callee))
   627  				}
   628  			}
   629  		}
   630  	})
   631  }
   632  
   633  func fullname(obj types.Object) string {
   634  	if fn, ok := obj.(*types.Func); ok {
   635  		return fn.FullName()
   636  	}
   637  	return obj.Name()
   638  }
   639  
   640  // callKind returns the symbol of the called function
   641  // and its print/printf kind, if any.
   642  // (The symbol may be a var for an anonymous function.)
   643  // The result is memoized in res.funcs.
   644  func callKind(pass *analysis.Pass, obj types.Object, res *Result) Kind {
   645  	kind, ok := res.funcs[obj]
   646  	if !ok {
   647  		// cache miss
   648  		_, ok := isPrint[fullname(obj)]
   649  		if !ok {
   650  			// Next look up just "printf", for use with -printf.funcs.
   651  			_, ok = isPrint[strings.ToLower(obj.Name())]
   652  		}
   653  		if ok {
   654  			// well-known printf functions
   655  			if fullname(obj) == "fmt.Errorf" {
   656  				kind = KindErrorf
   657  			} else if strings.HasSuffix(obj.Name(), "f") {
   658  				kind = KindPrintf
   659  			} else {
   660  				kind = KindPrint
   661  			}
   662  		} else {
   663  			// imported wrappers
   664  			// Facts are associated with generic declarations, not instantiations.
   665  			obj = origin(obj)
   666  			var fact isWrapper
   667  			if pass.ImportObjectFact(obj, &fact) {
   668  				kind = fact.Kind
   669  			}
   670  		}
   671  		res.funcs[obj] = kind // cache
   672  	}
   673  	return kind
   674  }
   675  
   676  // isFormatter reports whether t could satisfy fmt.Formatter.
   677  // The only interface method to look for is "Format(State, rune)".
   678  func isFormatter(typ types.Type) bool {
   679  	// If the type is an interface, the value it holds might satisfy fmt.Formatter.
   680  	if _, ok := typ.Underlying().(*types.Interface); ok {
   681  		// Don't assume type parameters could be formatters. With the greater
   682  		// expressiveness of constraint interface syntax we expect more type safety
   683  		// when using type parameters.
   684  		if !typeparams.IsTypeParam(typ) {
   685  			return true
   686  		}
   687  	}
   688  	obj, _, _ := types.LookupFieldOrMethod(typ, false, nil, "Format")
   689  	fn, ok := obj.(*types.Func)
   690  	if !ok {
   691  		return false
   692  	}
   693  	sig := fn.Type().(*types.Signature)
   694  	return sig.Params().Len() == 2 &&
   695  		sig.Results().Len() == 0 &&
   696  		typesinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
   697  		types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
   698  }
   699  
   700  // checkPrintf checks a call to a formatted print routine such as Printf.
   701  func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, name string) {
   702  	idx := formatStringIndex(pass, call)
   703  	if idx < 0 || idx >= len(call.Args) {
   704  		return
   705  	}
   706  	formatArg := call.Args[idx]
   707  	format, ok := stringConstantExpr(pass, formatArg)
   708  	if !ok {
   709  		// Format string argument is non-constant.
   710  
   711  		// It is a common mistake to call fmt.Printf(msg) with a
   712  		// non-constant format string and no arguments:
   713  		// if msg contains "%", misformatting occurs.
   714  		// Report the problem and suggest a fix: fmt.Printf("%s", msg).
   715  		//
   716  		// However, as described in golang/go#71485, this analysis can produce a
   717  		// significant number of diagnostics in existing code, and the bugs it
   718  		// finds are sometimes unlikely or inconsequential, and may not be worth
   719  		// fixing for some users. Gating on language version allows us to avoid
   720  		// breaking existing tests and CI scripts.
   721  		if idx == len(call.Args)-1 &&
   722  			fileVersion != "" && // fail open
   723  			versions.AtLeast(fileVersion, versions.Go1_24) {
   724  
   725  			pass.Report(analysis.Diagnostic{
   726  				Pos: formatArg.Pos(),
   727  				End: formatArg.End(),
   728  				Message: fmt.Sprintf("non-constant format string in call to %s",
   729  					name),
   730  				SuggestedFixes: []analysis.SuggestedFix{{
   731  					Message: `Insert "%s" format string`,
   732  					TextEdits: []analysis.TextEdit{{
   733  						Pos:     formatArg.Pos(),
   734  						End:     formatArg.Pos(),
   735  						NewText: []byte(`"%s", `),
   736  					}},
   737  				}},
   738  			})
   739  		}
   740  		return
   741  	}
   742  
   743  	firstArg := idx + 1 // Arguments are immediately after format string.
   744  	if !strings.Contains(format, "%") {
   745  		if len(call.Args) > firstArg {
   746  			pass.ReportRangef(call.Args[firstArg], "%s call has arguments but no formatting directives", name)
   747  		}
   748  		return
   749  	}
   750  
   751  	// Pass the string constant value so
   752  	// fmt.Sprintf("%"+("s"), "hi", 3) can be reported as
   753  	// "fmt.Sprintf call needs 1 arg but has 2 args".
   754  	operations, err := fmtstr.Parse(format, idx)
   755  	if err != nil {
   756  		// All error messages are in predicate form ("call has a problem")
   757  		// so that they may be affixed into a subject ("log.Printf ").
   758  		pass.ReportRangef(formatArg, "%s %s", name, err)
   759  		return
   760  	}
   761  
   762  	// index of the highest used index.
   763  	maxArgIndex := firstArg - 1
   764  	anyIndex := false
   765  	// Check formats against args.
   766  	for _, op := range operations {
   767  		if op.Prec.Index != -1 ||
   768  			op.Width.Index != -1 ||
   769  			op.Verb.Index != -1 {
   770  			anyIndex = true
   771  		}
   772  		rng := opRange(formatArg, op)
   773  		if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
   774  			// One error per format is enough.
   775  			return
   776  		}
   777  		if op.Verb.Verb == 'w' {
   778  			switch kind {
   779  			case KindNone, KindPrint, KindPrintf:
   780  				pass.ReportRangef(rng, "%s does not support error-wrapping directive %%w", name)
   781  				return
   782  			}
   783  		}
   784  	}
   785  	// Dotdotdot is hard.
   786  	if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
   787  		return
   788  	}
   789  	// If any formats are indexed, extra arguments are ignored.
   790  	if anyIndex {
   791  		return
   792  	}
   793  	// There should be no leftover arguments.
   794  	if maxArgIndex+1 < len(call.Args) {
   795  		expect := maxArgIndex + 1 - firstArg
   796  		numArgs := len(call.Args) - firstArg
   797  		pass.ReportRangef(call, "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
   798  	}
   799  }
   800  
   801  // opRange returns the source range for the specified printf operation,
   802  // such as the position of the %v substring of "...%v...".
   803  func opRange(formatArg ast.Expr, op *fmtstr.Operation) analysis.Range {
   804  	if lit, ok := formatArg.(*ast.BasicLit); ok {
   805  		rng, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
   806  		if err == nil {
   807  			return rng // position of "%v"
   808  		}
   809  	}
   810  	return formatArg // entire format string
   811  }
   812  
   813  // printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
   814  type printfArgType int
   815  
   816  const (
   817  	argBool printfArgType = 1 << iota
   818  	argByte
   819  	argInt
   820  	argRune
   821  	argString
   822  	argFloat
   823  	argComplex
   824  	argPointer
   825  	argError
   826  	anyType printfArgType = ^0
   827  )
   828  
   829  type printVerb struct {
   830  	verb  rune   // User may provide verb through Formatter; could be a rune.
   831  	flags string // known flags are all ASCII
   832  	typ   printfArgType
   833  }
   834  
   835  // Common flag sets for printf verbs.
   836  const (
   837  	noFlag       = ""
   838  	numFlag      = " -+.0"
   839  	sharpNumFlag = " -+.0#"
   840  	allFlags     = " -+.0#"
   841  )
   842  
   843  // printVerbs identifies which flags are known to printf for each verb.
   844  var printVerbs = []printVerb{
   845  	// '-' is a width modifier, always valid.
   846  	// '.' is a precision for float, max width for strings.
   847  	// '+' is required sign for numbers, Go format for %v.
   848  	// '#' is alternate format for several verbs.
   849  	// ' ' is spacer for numbers
   850  	{'%', noFlag, 0},
   851  	{'b', sharpNumFlag, argInt | argFloat | argComplex | argPointer},
   852  	{'c', "-", argRune | argInt},
   853  	{'d', numFlag, argInt | argPointer},
   854  	{'e', sharpNumFlag, argFloat | argComplex},
   855  	{'E', sharpNumFlag, argFloat | argComplex},
   856  	{'f', sharpNumFlag, argFloat | argComplex},
   857  	{'F', sharpNumFlag, argFloat | argComplex},
   858  	{'g', sharpNumFlag, argFloat | argComplex},
   859  	{'G', sharpNumFlag, argFloat | argComplex},
   860  	{'o', sharpNumFlag, argInt | argPointer},
   861  	{'O', sharpNumFlag, argInt | argPointer},
   862  	{'p', "-#", argPointer},
   863  	{'q', " -+.0#", argRune | argInt | argString}, // note: when analyzing go1.26 code, argInt => argByte
   864  	{'s', " -+.0", argString},
   865  	{'t', "-", argBool},
   866  	{'T', "-", anyType},
   867  	{'U', "-#", argRune | argInt},
   868  	{'v', allFlags, anyType},
   869  	{'w', allFlags, argError},
   870  	{'x', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
   871  	{'X', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
   872  }
   873  
   874  // okPrintfArg compares the operation to the arguments actually present,
   875  // reporting any discrepancies it can discern, maxArgIndex was the index of the highest used index.
   876  // If the final argument is ellipsissed, there's little it can do for that.
   877  func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
   878  	verb := operation.Verb.Verb
   879  	var v printVerb
   880  	found := false
   881  	// Linear scan is fast enough for a small list.
   882  	for _, v = range printVerbs {
   883  		if v.verb == verb {
   884  			found = true
   885  			break
   886  		}
   887  	}
   888  
   889  	// When analyzing go1.26 code, rune and byte are the only %q integers (#72850).
   890  	if verb == 'q' &&
   891  		fileVersion != "" && // fail open
   892  		versions.AtLeast(fileVersion, versions.Go1_26) {
   893  		v.typ = argRune | argByte | argString
   894  	}
   895  
   896  	// Could verb's arg implement fmt.Formatter?
   897  	// Skip check for the %w verb, which requires an error.
   898  	formatter := false
   899  	if v.typ != argError && operation.Verb.ArgIndex < len(call.Args) {
   900  		if tv, ok := pass.TypesInfo.Types[call.Args[operation.Verb.ArgIndex]]; ok {
   901  			formatter = isFormatter(tv.Type)
   902  		}
   903  	}
   904  
   905  	if !formatter {
   906  		if !found {
   907  			pass.ReportRangef(rng, "%s format %s has unknown verb %c", name, operation.Text, verb)
   908  			return false
   909  		}
   910  		for _, flag := range operation.Flags {
   911  			// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
   912  			// See issues 23598 and 23605.
   913  			if flag == '0' {
   914  				continue
   915  			}
   916  			if !strings.ContainsRune(v.flags, rune(flag)) {
   917  				pass.ReportRangef(rng, "%s format %s has unrecognized flag %c", name, operation.Text, flag)
   918  				return false
   919  			}
   920  		}
   921  	}
   922  
   923  	var argIndexes []int
   924  	// First check for *.
   925  	if operation.Width.Dynamic != -1 {
   926  		argIndexes = append(argIndexes, operation.Width.Dynamic)
   927  	}
   928  	if operation.Prec.Dynamic != -1 {
   929  		argIndexes = append(argIndexes, operation.Prec.Dynamic)
   930  	}
   931  	// If len(argIndexes)>0, we have something like %.*s and all
   932  	// indexes in argIndexes must be an integer.
   933  	for _, argIndex := range argIndexes {
   934  		if !argCanBeChecked(pass, call, rng, argIndex, firstArg, operation, name) {
   935  			return
   936  		}
   937  		arg := call.Args[argIndex]
   938  		if reason, ok := matchArgType(pass, argInt, arg); !ok {
   939  			details := ""
   940  			if reason != "" {
   941  				details = " (" + reason + ")"
   942  			}
   943  			pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, astutil.Format(pass.Fset, arg), details)
   944  			return false
   945  		}
   946  	}
   947  
   948  	// Collect to update maxArgNum in one loop.
   949  	if operation.Verb.ArgIndex != -1 && verb != '%' {
   950  		argIndexes = append(argIndexes, operation.Verb.ArgIndex)
   951  	}
   952  	for _, index := range argIndexes {
   953  		*maxArgIndex = max(*maxArgIndex, index)
   954  	}
   955  
   956  	// Special case for '%', go will print "fmt.Printf("%10.2%%dhello", 4)"
   957  	// as "%4hello", discard any runes between the two '%'s, and treat the verb '%'
   958  	// as an ordinary rune, so early return to skip the type check.
   959  	if verb == '%' || formatter {
   960  		return true
   961  	}
   962  
   963  	// Now check verb's type.
   964  	verbArgIndex := operation.Verb.ArgIndex
   965  	if !argCanBeChecked(pass, call, rng, verbArgIndex, firstArg, operation, name) {
   966  		return false
   967  	}
   968  	arg := call.Args[verbArgIndex]
   969  	if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
   970  		pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg))
   971  		return false
   972  	}
   973  	if reason, ok := matchArgType(pass, v.typ, arg); !ok {
   974  		typeString := ""
   975  		if typ := pass.TypesInfo.Types[arg].Type; typ != nil {
   976  			typeString = typ.String()
   977  		}
   978  		details := ""
   979  		if reason != "" {
   980  			details = " (" + reason + ")"
   981  		}
   982  		pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, astutil.Format(pass.Fset, arg), typeString, details)
   983  		return false
   984  	}
   985  	// Detect recursive formatting via value's String/Error methods.
   986  	// The '#' flag suppresses the methods, except with %x, %X, and %q.
   987  	if v.typ&argString != 0 && v.verb != 'T' && (!strings.Contains(operation.Flags, "#") || strings.ContainsRune("qxX", v.verb)) {
   988  		if methodName, ok := recursiveStringer(pass, arg); ok {
   989  			pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, astutil.Format(pass.Fset, arg), methodName)
   990  			return false
   991  		}
   992  	}
   993  	return true
   994  }
   995  
   996  // recursiveStringer reports whether the argument e is a potential
   997  // recursive call to stringer or is an error, such as t and &t in these examples:
   998  //
   999  //	func (t *T) String() string { printf("%s",  t) }
  1000  //	func (t  T) Error() string { printf("%s",  t) }
  1001  //	func (t  T) String() string { printf("%s", &t) }
  1002  func recursiveStringer(pass *analysis.Pass, e ast.Expr) (string, bool) {
  1003  	typ := pass.TypesInfo.Types[e].Type
  1004  
  1005  	// It's unlikely to be a recursive stringer if it has a Format method.
  1006  	if isFormatter(typ) {
  1007  		return "", false
  1008  	}
  1009  
  1010  	// Does e allow e.String() or e.Error()?
  1011  	strObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "String")
  1012  	strMethod, strOk := strObj.(*types.Func)
  1013  	errObj, _, _ := types.LookupFieldOrMethod(typ, false, pass.Pkg, "Error")
  1014  	errMethod, errOk := errObj.(*types.Func)
  1015  	if !strOk && !errOk {
  1016  		return "", false
  1017  	}
  1018  
  1019  	// inScope returns true if e is in the scope of f.
  1020  	inScope := func(e ast.Expr, f *types.Func) bool {
  1021  		return f.Scope() != nil && f.Scope().Contains(e.Pos())
  1022  	}
  1023  
  1024  	// Is the expression e within the body of that String or Error method?
  1025  	var method *types.Func
  1026  	if strOk && strMethod.Pkg() == pass.Pkg && inScope(e, strMethod) {
  1027  		method = strMethod
  1028  	} else if errOk && errMethod.Pkg() == pass.Pkg && inScope(e, errMethod) {
  1029  		method = errMethod
  1030  	} else {
  1031  		return "", false
  1032  	}
  1033  
  1034  	sig := method.Type().(*types.Signature)
  1035  	if !isStringer(sig) {
  1036  		return "", false
  1037  	}
  1038  
  1039  	// Is it the receiver r, or &r?
  1040  	if u, ok := e.(*ast.UnaryExpr); ok && u.Op == token.AND {
  1041  		e = u.X // strip off & from &r
  1042  	}
  1043  	if id, ok := e.(*ast.Ident); ok {
  1044  		if pass.TypesInfo.Uses[id] == sig.Recv() {
  1045  			return method.FullName(), true
  1046  		}
  1047  	}
  1048  	return "", false
  1049  }
  1050  
  1051  // isStringer reports whether the method signature matches the String() definition in fmt.Stringer.
  1052  func isStringer(sig *types.Signature) bool {
  1053  	return sig.Params().Len() == 0 &&
  1054  		sig.Results().Len() == 1 &&
  1055  		sig.Results().At(0).Type() == types.Typ[types.String]
  1056  }
  1057  
  1058  // isFunctionValue reports whether the expression is a function as opposed to a function call.
  1059  // It is almost always a mistake to print a function value.
  1060  func isFunctionValue(pass *analysis.Pass, e ast.Expr) bool {
  1061  	if typ := pass.TypesInfo.Types[e].Type; typ != nil {
  1062  		// Don't call Underlying: a named func type with a String method is ok.
  1063  		// TODO(adonovan): it would be more precise to check isStringer.
  1064  		_, ok := typ.(*types.Signature)
  1065  		return ok
  1066  	}
  1067  	return false
  1068  }
  1069  
  1070  // argCanBeChecked reports whether the specified argument is statically present;
  1071  // it may be beyond the list of arguments or in a terminal slice... argument, which
  1072  // means we can't see it.
  1073  func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, argIndex, firstArg int, operation *fmtstr.Operation, name string) bool {
  1074  	if argIndex <= 0 {
  1075  		// Shouldn't happen, so catch it with prejudice.
  1076  		panic("negative argIndex")
  1077  	}
  1078  	if argIndex < len(call.Args)-1 {
  1079  		return true // Always OK.
  1080  	}
  1081  	if call.Ellipsis.IsValid() {
  1082  		return false // We just can't tell; there could be many more arguments.
  1083  	}
  1084  	if argIndex < len(call.Args) {
  1085  		return true
  1086  	}
  1087  	// There are bad indexes in the format or there are fewer arguments than the format needs.
  1088  	// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
  1089  	arg := argIndex - firstArg + 1 // People think of arguments as 1-indexed.
  1090  	pass.ReportRangef(rng, "%s format %s reads arg #%d, but call has %v", name, operation.Text, arg, count(len(call.Args)-firstArg, "arg"))
  1091  	return false
  1092  }
  1093  
  1094  // printFormatRE is the regexp we match and report as a possible format string
  1095  // in the first argument to unformatted prints like fmt.Print.
  1096  // We exclude the space flag, so that printing a string like "x % y" is not reported as a format.
  1097  var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE)
  1098  
  1099  const (
  1100  	flagsRE    = `[+\-#]*`
  1101  	indexOptRE = `(\[[0-9]+\])?`
  1102  	numOptRE   = `([0-9]+|` + indexOptRE + `\*)?`
  1103  	verbRE     = `[bcdefgopqstvxEFGTUX]`
  1104  )
  1105  
  1106  // checkPrint checks a call to an unformatted print routine such as Println.
  1107  func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
  1108  	firstArg := 0
  1109  	typ := pass.TypesInfo.Types[call.Fun].Type
  1110  	if typ == nil {
  1111  		// Skip checking functions with unknown type.
  1112  		return
  1113  	}
  1114  	if sig, ok := typ.Underlying().(*types.Signature); ok {
  1115  		if !sig.Variadic() {
  1116  			// Skip checking non-variadic functions.
  1117  			return
  1118  		}
  1119  		params := sig.Params()
  1120  		firstArg = params.Len() - 1
  1121  
  1122  		typ := params.At(firstArg).Type()
  1123  		typ = typ.(*types.Slice).Elem()
  1124  		it, ok := types.Unalias(typ).(*types.Interface)
  1125  		if !ok || !it.Empty() {
  1126  			// Skip variadic functions accepting non-interface{} args.
  1127  			return
  1128  		}
  1129  	}
  1130  	args := call.Args
  1131  	if len(args) <= firstArg {
  1132  		// Skip calls without variadic args.
  1133  		return
  1134  	}
  1135  	args = args[firstArg:]
  1136  
  1137  	if firstArg == 0 {
  1138  		if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
  1139  			if x, ok := sel.X.(*ast.Ident); ok {
  1140  				if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
  1141  					pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, astutil.Format(pass.Fset, call.Args[0]))
  1142  				}
  1143  			}
  1144  		}
  1145  	}
  1146  
  1147  	arg := args[0]
  1148  	if s, ok := stringConstantExpr(pass, arg); ok {
  1149  		// Ignore trailing % character
  1150  		// The % in "abc 0.0%" couldn't be a formatting directive.
  1151  		s = strings.TrimSuffix(s, "%")
  1152  		if strings.Contains(s, "%") {
  1153  			for _, m := range printFormatRE.FindAllString(s, -1) {
  1154  				// Allow %XX where XX are hex digits,
  1155  				// as this is common in URLs.
  1156  				if len(m) >= 3 && isHex(m[1]) && isHex(m[2]) {
  1157  					continue
  1158  				}
  1159  				pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", name, m)
  1160  				break // report only the first one
  1161  			}
  1162  		}
  1163  	}
  1164  	if strings.HasSuffix(name, "ln") {
  1165  		// The last item, if a string, should not have a newline.
  1166  		arg = args[len(args)-1]
  1167  		if s, ok := stringConstantExpr(pass, arg); ok {
  1168  			if strings.HasSuffix(s, "\n") {
  1169  				pass.ReportRangef(call, "%s arg list ends with redundant newline", name)
  1170  			}
  1171  		}
  1172  	}
  1173  	for _, arg := range args {
  1174  		if isFunctionValue(pass, arg) {
  1175  			pass.ReportRangef(call, "%s arg %s is a func value, not called", name, astutil.Format(pass.Fset, arg))
  1176  		}
  1177  		if methodName, ok := recursiveStringer(pass, arg); ok {
  1178  			pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, astutil.Format(pass.Fset, arg), methodName)
  1179  		}
  1180  	}
  1181  }
  1182  
  1183  // count(n, what) returns "1 what" or "N whats"
  1184  // (assuming the plural of what is whats).
  1185  func count(n int, what string) string {
  1186  	if n == 1 {
  1187  		return "1 " + what
  1188  	}
  1189  	return fmt.Sprintf("%d %ss", n, what)
  1190  }
  1191  
  1192  // stringSet is a set-of-nonempty-strings-valued flag.
  1193  // Note: elements without a '.' get lower-cased.
  1194  type stringSet map[string]bool
  1195  
  1196  func (ss stringSet) String() string {
  1197  	var list []string
  1198  	for name := range ss {
  1199  		list = append(list, name)
  1200  	}
  1201  	sort.Strings(list)
  1202  	return strings.Join(list, ",")
  1203  }
  1204  
  1205  func (ss stringSet) Set(flag string) error {
  1206  	for name := range strings.SplitSeq(flag, ",") {
  1207  		if len(name) == 0 {
  1208  			return fmt.Errorf("empty string")
  1209  		}
  1210  		if !strings.Contains(name, ".") {
  1211  			name = strings.ToLower(name)
  1212  		}
  1213  		ss[name] = true
  1214  	}
  1215  	return nil
  1216  }
  1217  
  1218  // isHex reports whether b is a hex digit.
  1219  func isHex(b byte) bool {
  1220  	return '0' <= b && b <= '9' ||
  1221  		'A' <= b && b <= 'F' ||
  1222  		'a' <= b && b <= 'f'
  1223  }
  1224  

View as plain text