src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sa1006.go (3136B)


      1 package sa1006
      2 
      3 import (
      4 	"fmt"
      5 	"go/ast"
      6 	"go/types"
      7 
      8 	"honnef.co/go/tools/analysis/code"
      9 	"honnef.co/go/tools/analysis/edit"
     10 	"honnef.co/go/tools/analysis/lint"
     11 	"honnef.co/go/tools/analysis/report"
     12 	"honnef.co/go/tools/pattern"
     13 
     14 	"golang.org/x/tools/go/analysis"
     15 )
     16 
     17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
     18 	Analyzer: &analysis.Analyzer{
     19 		Name:     "SA1006",
     20 		Run:      run,
     21 		Requires: code.RequiredAnalyzers,
     22 	},
     23 	Doc: &lint.RawDocumentation{
     24 		Title: `\'Printf\' with dynamic first argument and no further arguments`,
     25 		Text: `Using \'fmt.Printf\' with a dynamic first argument can lead to unexpected
     26 output. The first argument is a format string, where certain character
     27 combinations have special meaning. If, for example, a user were to
     28 enter a string such as
     29 
     30     Interest rate: 5%
     31 
     32 and you printed it with
     33 
     34     fmt.Printf(s)
     35 
     36 it would lead to the following output:
     37 
     38     Interest rate: 5%!(NOVERB).
     39 
     40 Similarly, forming the first parameter via string concatenation with
     41 user input should be avoided for the same reason. When printing user
     42 input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb
     43 and pass the string as an argument.`,
     44 		Since:    "2017.1",
     45 		Severity: lint.SeverityWarning,
     46 		MergeIf:  lint.MergeIfAny,
     47 	},
     48 })
     49 
     50 var Analyzer = SCAnalyzer.Analyzer
     51 
     52 var query1 = pattern.MustParse(`
     53 	(CallExpr
     54 		(Symbol
     55 			name@(Or
     56 				"fmt.Errorf"
     57 				"fmt.Printf"
     58 				"fmt.Sprintf"
     59 				"log.Fatalf"
     60 				"log.Panicf"
     61 				"log.Printf"
     62 				"(*log.Logger).Printf"
     63 				"(*testing.common).Logf"
     64 				"(*testing.common).Errorf"
     65 				"(*testing.common).Fatalf"
     66 				"(*testing.common).Skipf"
     67 				"(testing.TB).Logf"
     68 				"(testing.TB).Errorf"
     69 				"(testing.TB).Fatalf"
     70 				"(testing.TB).Skipf"))
     71 		format:[])
     72 `)
     73 
     74 var query2 = pattern.MustParse(`(CallExpr (Symbol "fmt.Fprintf") _:format:[])`)
     75 
     76 func run(pass *analysis.Pass) (any, error) {
     77 	for node, m := range code.Matches(pass, query1, query2) {
     78 		call := node.(*ast.CallExpr)
     79 		name, ok := m.State["name"].(string)
     80 		if !ok {
     81 			name = "fmt.Fprintf"
     82 		}
     83 
     84 		arg := m.State["format"].(ast.Expr)
     85 		switch arg.(type) {
     86 		case *ast.CallExpr, *ast.Ident:
     87 		default:
     88 			continue
     89 		}
     90 
     91 		if _, ok := pass.TypesInfo.TypeOf(arg).(*types.Tuple); ok {
     92 			// the called function returns multiple values and got
     93 			// splatted into the call. for all we know, it is
     94 			// returning good arguments.
     95 			continue
     96 		}
     97 
     98 		var alt string
     99 		if name == "fmt.Errorf" {
    100 			// The alternative to fmt.Errorf isn't fmt.Error but errors.New
    101 			alt = "errors.New"
    102 		} else {
    103 			// This can be either a function call like log.Printf or a method call with an
    104 			// arbitrarily complex selector, such as foo.bar[0].Printf. In either case,
    105 			// all we have to do is remove the final 'f' from the existing call.Fun
    106 			// expression.
    107 			alt = report.Render(pass, call.Fun)
    108 			alt = alt[:len(alt)-1]
    109 		}
    110 		report.Report(pass, call,
    111 			"printf-style function with dynamic format string and no further arguments should use print-style function instead",
    112 			report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead of %s", alt, name), edit.ReplaceWithString(call.Fun, alt))))
    113 	}
    114 	return nil, nil
    115 }