github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa1023/sa1023.go (about) 1 package sa1023 2 3 import ( 4 "go/types" 5 6 "github.com/amarpal/go-tools/analysis/lint" 7 "github.com/amarpal/go-tools/analysis/report" 8 "github.com/amarpal/go-tools/go/ir" 9 "github.com/amarpal/go-tools/go/ir/irutil" 10 "github.com/amarpal/go-tools/go/types/typeutil" 11 "github.com/amarpal/go-tools/internal/passes/buildir" 12 13 "golang.org/x/tools/go/analysis" 14 ) 15 16 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 17 Analyzer: &analysis.Analyzer{ 18 Name: "SA1023", 19 Run: run, 20 Requires: []*analysis.Analyzer{buildir.Analyzer}, 21 }, 22 Doc: &lint.Documentation{ 23 Title: `Modifying the buffer in an \'io.Writer\' implementation`, 24 Text: `\'Write\' must not modify the slice data, even temporarily.`, 25 Since: "2017.1", 26 Severity: lint.SeverityError, 27 MergeIf: lint.MergeIfAny, 28 }, 29 }) 30 31 var Analyzer = SCAnalyzer.Analyzer 32 33 func run(pass *analysis.Pass) (interface{}, error) { 34 // TODO(dh): this might be a good candidate for taint analysis. 35 // Taint the argument as MUST_NOT_MODIFY, then propagate that 36 // through functions like bytes.Split 37 38 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 39 sig := fn.Signature 40 if fn.Name() != "Write" || sig.Recv() == nil || sig.Params().Len() != 1 || sig.Results().Len() != 2 { 41 continue 42 } 43 tArg, ok := sig.Params().At(0).Type().(*types.Slice) 44 if !ok { 45 continue 46 } 47 if basic, ok := tArg.Elem().(*types.Basic); !ok || basic.Kind() != types.Byte { 48 continue 49 } 50 if basic, ok := sig.Results().At(0).Type().(*types.Basic); !ok || basic.Kind() != types.Int { 51 continue 52 } 53 if named, ok := sig.Results().At(1).Type().(*types.Named); !ok || !typeutil.IsType(named, "error") { 54 continue 55 } 56 57 for _, block := range fn.Blocks { 58 for _, ins := range block.Instrs { 59 switch ins := ins.(type) { 60 case *ir.Store: 61 addr, ok := ins.Addr.(*ir.IndexAddr) 62 if !ok { 63 continue 64 } 65 if addr.X != fn.Params[1] { 66 continue 67 } 68 report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily") 69 case *ir.Call: 70 if !irutil.IsCallTo(ins.Common(), "append") { 71 continue 72 } 73 if ins.Common().Args[0] != fn.Params[1] { 74 continue 75 } 76 report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily") 77 } 78 } 79 } 80 } 81 return nil, nil 82 }