sa4023.go (7032B)
1 package sa4023 2 3 import ( 4 "fmt" 5 "go/token" 6 "go/types" 7 8 "honnef.co/go/tools/analysis/code" 9 "honnef.co/go/tools/analysis/facts/nilness" 10 "honnef.co/go/tools/analysis/lint" 11 "honnef.co/go/tools/analysis/report" 12 "honnef.co/go/tools/go/ir" 13 "honnef.co/go/tools/go/ir/irutil" 14 "honnef.co/go/tools/go/types/typeutil" 15 "honnef.co/go/tools/internal/passes/buildir" 16 17 "golang.org/x/exp/typeparams" 18 "golang.org/x/tools/go/analysis" 19 ) 20 21 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 22 Analyzer: &analysis.Analyzer{ 23 Name: "SA4023", 24 Run: run, 25 Requires: []*analysis.Analyzer{buildir.Analyzer, nilness.Analysis}, 26 }, 27 Doc: &lint.RawDocumentation{ 28 Title: `Impossible comparison of interface value with untyped nil`, 29 Text: `Under the covers, interfaces are implemented as two elements, a 30 type T and a value V. V is a concrete value such as an int, 31 struct or pointer, never an interface itself, and has type T. For 32 instance, if we store the int value 3 in an interface, the 33 resulting interface value has, schematically, (T=int, V=3). The 34 value V is also known as the interface's dynamic value, since a 35 given interface variable might hold different values V (and 36 corresponding types T) during the execution of the program. 37 38 An interface value is nil only if the V and T are both 39 unset, (T=nil, V is not set), In particular, a nil interface will 40 always hold a nil type. If we store a nil pointer of type *int 41 inside an interface value, the inner type will be *int regardless 42 of the value of the pointer: (T=*int, V=nil). Such an interface 43 value will therefore be non-nil even when the pointer value V 44 inside is nil. 45 46 This situation can be confusing, and arises when a nil value is 47 stored inside an interface value such as an error return: 48 49 func returnsError() error { 50 var p *MyError = nil 51 if bad() { 52 p = ErrBad 53 } 54 return p // Will always return a non-nil error. 55 } 56 57 If all goes well, the function returns a nil p, so the return 58 value is an error interface value holding (T=*MyError, V=nil). 59 This means that if the caller compares the returned error to nil, 60 it will always look as if there was an error even if nothing bad 61 happened. To return a proper nil error to the caller, the 62 function must return an explicit nil: 63 64 func returnsError() error { 65 if bad() { 66 return ErrBad 67 } 68 return nil 69 } 70 71 It's a good idea for functions that return errors always to use 72 the error type in their signature (as we did above) rather than a 73 concrete type such as \'*MyError\', to help guarantee the error is 74 created correctly. As an example, \'os.Open\' returns an error even 75 though, if not nil, it's always of concrete type *os.PathError. 76 77 Similar situations to those described here can arise whenever 78 interfaces are used. Just keep in mind that if any concrete value 79 has been stored in the interface, the interface will not be nil. 80 For more information, see The Laws of 81 Reflection at https://golang.org/doc/articles/laws_of_reflection.html. 82 83 This text has been copied from 84 https://golang.org/doc/faq#nil_error, licensed under the Creative 85 Commons Attribution 3.0 License.`, 86 Since: "2020.2", 87 Severity: lint.SeverityWarning, 88 MergeIf: lint.MergeIfAny, // TODO should this be MergeIfAll? 89 }, 90 }) 91 92 var Analyzer = SCAnalyzer.Analyzer 93 94 func run(pass *analysis.Pass) (any, error) { 95 // The comparison 'fn() == nil' can never be true if fn() returns 96 // an interface value and only returns typed nils. This is usually 97 // a mistake in the function itself, but all we can say for 98 // certain is that the comparison is pointless. 99 // 100 // Flag results if no untyped nils are being returned, but either 101 // known typed nils, or typed unknown nilness are being returned. 102 103 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR) 104 nilnessRes := pass.ResultOf[nilness.Analysis].(*nilness.Result) 105 for _, fn := range irpkg.SrcFuncs { 106 for _, b := range fn.Blocks { 107 for _, instr := range b.Instrs { 108 binop, ok := instr.(*ir.BinOp) 109 if !ok || !(binop.Op == token.EQL || binop.Op == token.NEQ) { 110 continue 111 } 112 if !types.IsInterface(binop.X.Type()) || typeparams.IsTypeParam(binop.X.Type()) { 113 // TODO support swapped X and Y 114 continue 115 } 116 117 k, ok := binop.Y.(*ir.Const) 118 if !ok || !k.IsNil() { 119 // if binop.X is an interface, then binop.Y can only be a 120 // Const if its untyped. A typed nil constant would first 121 // be passed to MakeInterface. 122 continue 123 } 124 125 var idx int 126 var obj *types.Func 127 switch x := irutil.Flatten(binop.X).(type) { 128 case *ir.Call: 129 callee := x.Call.StaticCallee() 130 if callee == nil { 131 continue 132 } 133 obj, _ = callee.Object().(*types.Func) 134 idx = 0 135 case *ir.Extract: 136 call, ok := irutil.Flatten(x.Tuple).(*ir.Call) 137 if !ok { 138 continue 139 } 140 callee := call.Call.StaticCallee() 141 if callee == nil { 142 continue 143 } 144 obj, _ = callee.Object().(*types.Func) 145 idx = x.Index 146 case *ir.MakeInterface: 147 var qualifier string 148 switch binop.Op { 149 case token.EQL: 150 qualifier = "never" 151 case token.NEQ: 152 qualifier = "always" 153 default: 154 panic("unreachable") 155 } 156 157 terms, err := typeparams.NormalTerms(x.X.Type()) 158 if len(terms) == 0 || err != nil { 159 // Type is a type parameter with no type terms (or we 160 // couldn't determine the terms). Such a type _can_ be 161 // nil when put in an interface value. 162 continue 163 } 164 165 if report.HasRange(x.X) { 166 report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), 167 report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type")) 168 } else { 169 // we can't generate related information for this, so 170 // make the diagnostic itself slightly more useful 171 report.Report(pass, binop, 172 fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", 173 qualifier)) 174 } 175 continue 176 } 177 if obj == nil { 178 continue 179 } 180 181 nillity := nilnessRes.Nilness(obj, idx) 182 if nillity.Outer == nilness.NeverNil && 183 !code.IsInTest(pass, binop) && 184 !irutil.IsTrivial(irpkg.Pkg.Prog.FuncValue(obj)) { 185 // Don't flag these comparisons in tests. Tests may be 186 // explicitly enforcing the invariant that a value isn't 187 // nil. 188 189 var qualifier string 190 switch binop.Op { 191 case token.EQL: 192 qualifier = "never" 193 case token.NEQ: 194 qualifier = "always" 195 default: 196 panic("unreachable") 197 } 198 report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), 199 // TODO support swapped X and Y 200 report.Related(binop.X, 201 fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", 202 report.Ordinal(idx+1))), 203 report.Related(obj, 204 fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj)))) 205 } 206 } 207 } 208 } 209 210 return nil, nil 211 }