src

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

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 }