sa4031.go (4148B)
1 package sa4031 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "go/types" 8 "sort" 9 10 "honnef.co/go/tools/analysis/code" 11 "honnef.co/go/tools/analysis/lint" 12 "honnef.co/go/tools/analysis/report" 13 "honnef.co/go/tools/go/ir" 14 "honnef.co/go/tools/internal/passes/buildir" 15 "honnef.co/go/tools/pattern" 16 "honnef.co/go/tools/staticcheck/sa4022" 17 18 "golang.org/x/tools/go/analysis" 19 "golang.org/x/tools/go/analysis/passes/inspect" 20 ) 21 22 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 23 Analyzer: &analysis.Analyzer{ 24 Name: "SA4031", 25 Run: run, 26 Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer}, 27 }, 28 Doc: &lint.RawDocumentation{ 29 Title: `Checking never-nil value against nil`, 30 Since: "2022.1", 31 Severity: lint.SeverityWarning, 32 MergeIf: lint.MergeIfAny, 33 }, 34 }) 35 36 var Analyzer = SCAnalyzer.Analyzer 37 38 var allocationNilCheckQ = pattern.MustParse(`(IfStmt _ cond@(BinaryExpr lhs op@(Or "==" "!=") (Builtin "nil")) _ _)`) 39 40 func run(pass *analysis.Pass) (any, error) { 41 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg 42 43 var path []ast.Node 44 fn := func(node ast.Node, stack []ast.Node) { 45 m, ok := code.Match(pass, allocationNilCheckQ, node) 46 if !ok { 47 return 48 } 49 cond := m.State["cond"].(ast.Node) 50 if _, ok := code.Match(pass, sa4022.CheckAddressIsNilQ, cond); ok { 51 // Don't duplicate diagnostics reported by SA4022 52 return 53 } 54 lhs := m.State["lhs"].(ast.Expr) 55 path = path[:0] 56 for i := len(stack) - 1; i >= 0; i-- { 57 path = append(path, stack[i]) 58 } 59 irfn := ir.EnclosingFunction(irpkg, path) 60 if irfn == nil { 61 // For example for functions named "_", because we don't generate IR for them. 62 return 63 } 64 v, isAddr := irfn.ValueForExpr(lhs) 65 if isAddr { 66 return 67 } 68 69 seen := map[ir.Value]struct{}{} 70 var values []ir.Value 71 var neverNil func(v ir.Value, track bool) bool 72 neverNil = func(v ir.Value, track bool) bool { 73 if _, ok := seen[v]; ok { 74 return true 75 } 76 seen[v] = struct{}{} 77 switch v := v.(type) { 78 case *ir.MakeClosure, *ir.Function: 79 if track { 80 values = append(values, v) 81 } 82 return true 83 case *ir.MakeChan, *ir.MakeMap, *ir.MakeSlice, *ir.Alloc: 84 if track { 85 values = append(values, v) 86 } 87 return true 88 case *ir.Slice: 89 if track { 90 values = append(values, v) 91 } 92 return neverNil(v.X, false) 93 case *ir.FieldAddr: 94 if track { 95 values = append(values, v) 96 } 97 return neverNil(v.X, false) 98 case *ir.Phi: 99 for _, e := range v.Edges { 100 if !neverNil(e, true) { 101 return false 102 } 103 } 104 return true 105 default: 106 return false 107 } 108 } 109 110 if !neverNil(v, true) { 111 return 112 } 113 114 var qualifier string 115 if op := m.State["op"].(token.Token); op == token.EQL { 116 qualifier = "never" 117 } else { 118 qualifier = "always" 119 } 120 fallback := fmt.Sprintf("this nil check is %s true", qualifier) 121 122 sort.Slice(values, func(i, j int) bool { return values[i].Pos() < values[j].Pos() }) 123 124 if ident, ok := m.State["lhs"].(*ast.Ident); ok { 125 if _, ok := pass.TypesInfo.ObjectOf(ident).(*types.Var); ok { 126 var opts []report.Option 127 if v.Parent() == irfn { 128 if len(values) == 1 { 129 opts = append(opts, report.Related(values[0], fmt.Sprintf("this is the value of %s", ident.Name))) 130 } else { 131 for _, vv := range values { 132 opts = append(opts, report.Related(vv, fmt.Sprintf("this is one of the value of %s", ident.Name))) 133 } 134 } 135 } 136 137 switch v.(type) { 138 case *ir.MakeClosure, *ir.Function: 139 report.Report(pass, cond, "the checked variable contains a function and is never nil; did you mean to call it?", opts...) 140 default: 141 report.Report(pass, cond, fallback, opts...) 142 } 143 } else { 144 if _, ok := v.(*ir.Function); ok { 145 report.Report(pass, cond, "functions are never nil; did you mean to call it?") 146 } else { 147 report.Report(pass, cond, fallback) 148 } 149 } 150 } else { 151 if _, ok := v.(*ir.Function); ok { 152 report.Report(pass, cond, "functions are never nil; did you mean to call it?") 153 } else { 154 report.Report(pass, cond, fallback) 155 } 156 } 157 } 158 code.PreorderStack(pass, fn, (*ast.IfStmt)(nil)) 159 return nil, nil 160 }