github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1006/sa1006.go (about) 1 package sa1006 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 8 "github.com/amarpal/go-tools/analysis/code" 9 "github.com/amarpal/go-tools/analysis/edit" 10 "github.com/amarpal/go-tools/analysis/lint" 11 "github.com/amarpal/go-tools/analysis/report" 12 "github.com/amarpal/go-tools/knowledge" 13 14 "golang.org/x/tools/go/analysis" 15 "golang.org/x/tools/go/analysis/passes/inspect" 16 ) 17 18 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 19 Analyzer: &analysis.Analyzer{ 20 Name: "SA1006", 21 Run: run, 22 Requires: []*analysis.Analyzer{inspect.Analyzer}, 23 }, 24 Doc: &lint.Documentation{ 25 Title: `\'Printf\' with dynamic first argument and no further arguments`, 26 Text: `Using \'fmt.Printf\' with a dynamic first argument can lead to unexpected 27 output. The first argument is a format string, where certain character 28 combinations have special meaning. If, for example, a user were to 29 enter a string such as 30 31 Interest rate: 5% 32 33 and you printed it with 34 35 fmt.Printf(s) 36 37 it would lead to the following output: 38 39 Interest rate: 5%!(NOVERB). 40 41 Similarly, forming the first parameter via string concatenation with 42 user input should be avoided for the same reason. When printing user 43 input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb 44 and pass the string as an argument.`, 45 Since: "2017.1", 46 Severity: lint.SeverityWarning, 47 MergeIf: lint.MergeIfAny, 48 }, 49 }) 50 51 var Analyzer = SCAnalyzer.Analyzer 52 53 func run(pass *analysis.Pass) (interface{}, error) { 54 fn := func(node ast.Node) { 55 call := node.(*ast.CallExpr) 56 name := code.CallName(pass, call) 57 var arg int 58 59 switch name { 60 case "fmt.Printf", "fmt.Sprintf", "log.Printf": 61 arg = knowledge.Arg("fmt.Printf.format") 62 case "fmt.Fprintf": 63 arg = knowledge.Arg("fmt.Fprintf.format") 64 default: 65 return 66 } 67 if len(call.Args) != arg+1 { 68 return 69 } 70 switch call.Args[arg].(type) { 71 case *ast.CallExpr, *ast.Ident: 72 default: 73 return 74 } 75 76 if _, ok := pass.TypesInfo.TypeOf(call.Args[arg]).(*types.Tuple); ok { 77 // the called function returns multiple values and got 78 // splatted into the call. for all we know, it is 79 // returning good arguments. 80 return 81 } 82 83 alt := name[:len(name)-1] 84 report.Report(pass, call, 85 "printf-style function with dynamic format string and no further arguments should use print-style function instead", 86 report.Fixes(edit.Fix(fmt.Sprintf("use %s instead of %s", alt, name), edit.ReplaceWithString(call.Fun, alt)))) 87 } 88 code.Preorder(pass, fn, (*ast.CallExpr)(nil)) 89 return nil, nil 90 }