github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/analysis/facts/typedness/typedness.go (about) 1 package typedness 2 3 import ( 4 "fmt" 5 "go/token" 6 "go/types" 7 "reflect" 8 9 "github.com/amarpal/go-tools/go/ir" 10 "github.com/amarpal/go-tools/go/ir/irutil" 11 "github.com/amarpal/go-tools/internal/passes/buildir" 12 13 "golang.org/x/exp/typeparams" 14 "golang.org/x/tools/go/analysis" 15 ) 16 17 // alwaysTypedFact denotes that a function's return value will never 18 // be untyped nil. The analysis errs on the side of false negatives. 19 type alwaysTypedFact struct { 20 Rets uint8 21 } 22 23 func (*alwaysTypedFact) AFact() {} 24 func (fact *alwaysTypedFact) String() string { 25 return fmt.Sprintf("always typed: %08b", fact.Rets) 26 } 27 28 type Result struct { 29 m map[*types.Func]uint8 30 } 31 32 var Analysis = &analysis.Analyzer{ 33 Name: "typedness", 34 Doc: "Annotates return values that are always typed values", 35 Run: run, 36 Requires: []*analysis.Analyzer{buildir.Analyzer}, 37 FactTypes: []analysis.Fact{(*alwaysTypedFact)(nil)}, 38 ResultType: reflect.TypeOf((*Result)(nil)), 39 } 40 41 // MustReturnTyped reports whether the ret's return value of fn must 42 // be a typed value, i.e. an interface value containing a concrete 43 // type or trivially a concrete type. The value of ret is zero-based. 44 // 45 // The analysis has false negatives: MustReturnTyped may incorrectly 46 // report false, but never incorrectly reports true. 47 func (r *Result) MustReturnTyped(fn *types.Func, ret int) bool { 48 if _, ok := fn.Type().(*types.Signature).Results().At(ret).Type().Underlying().(*types.Interface); !ok { 49 return true 50 } 51 return (r.m[fn] & (1 << ret)) != 0 52 } 53 54 func run(pass *analysis.Pass) (interface{}, error) { 55 seen := map[*ir.Function]struct{}{} 56 out := &Result{ 57 m: map[*types.Func]uint8{}, 58 } 59 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { 60 impl(pass, fn, seen) 61 } 62 63 for _, fact := range pass.AllObjectFacts() { 64 out.m[fact.Object.(*types.Func)] = fact.Fact.(*alwaysTypedFact).Rets 65 } 66 67 return out, nil 68 } 69 70 func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) (out uint8) { 71 if fn.Signature.Results().Len() > 8 { 72 return 0 73 } 74 if fn.Object() == nil { 75 // TODO(dh): support closures 76 return 0 77 } 78 if fact := new(alwaysTypedFact); pass.ImportObjectFact(fn.Object(), fact) { 79 return fact.Rets 80 } 81 if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg { 82 return 0 83 } 84 if fn.Blocks == nil { 85 return 0 86 } 87 if irutil.IsStub(fn) { 88 return 0 89 } 90 if _, ok := seenFns[fn]; ok { 91 // break recursion 92 return 0 93 } 94 95 seenFns[fn] = struct{}{} 96 defer func() { 97 for i := 0; i < fn.Signature.Results().Len(); i++ { 98 if _, ok := fn.Signature.Results().At(i).Type().Underlying().(*types.Interface); !ok { 99 // we don't need facts to know that non-interface 100 // types can't be untyped nil. zeroing out those bits 101 // may result in all bits being zero, in which case we 102 // don't have to save any fact. 103 out &= ^(1 << i) 104 } 105 } 106 if out > 0 { 107 pass.ExportObjectFact(fn.Object(), &alwaysTypedFact{out}) 108 } 109 }() 110 111 isUntypedNil := func(v ir.Value) bool { 112 k, ok := v.(*ir.Const) 113 if !ok { 114 return false 115 } 116 if _, ok := k.Type().Underlying().(*types.Interface); !ok { 117 return false 118 } 119 return k.Value == nil 120 } 121 122 var do func(v ir.Value, seen map[ir.Value]struct{}) bool 123 do = func(v ir.Value, seen map[ir.Value]struct{}) bool { 124 if _, ok := seen[v]; ok { 125 // break cycle 126 return false 127 } 128 seen[v] = struct{}{} 129 switch v := v.(type) { 130 case *ir.Const: 131 // can't be a typed nil, because then we'd be returning the 132 // result of MakeInterface. 133 return false 134 case *ir.ChangeInterface: 135 return do(v.X, seen) 136 case *ir.Extract: 137 call, ok := v.Tuple.(*ir.Call) 138 if !ok { 139 // We only care about extracts of function results. For 140 // everything else (e.g. channel receives and map 141 // lookups), we can either not deduce any information, or 142 // will see a MakeInterface. 143 return false 144 } 145 if callee := call.Call.StaticCallee(); callee != nil { 146 return impl(pass, callee, seenFns)&(1<<v.Index) != 0 147 } else { 148 // we don't know what function we're calling. no need 149 // to look at the signature, though. if it weren't an 150 // interface, we'd be seeing a MakeInterface 151 // instruction. 152 return false 153 } 154 case *ir.Call: 155 if callee := v.Call.StaticCallee(); callee != nil { 156 return impl(pass, callee, seenFns)&1 != 0 157 } else { 158 // we don't know what function we're calling. no need 159 // to look at the signature, though. if it weren't an 160 // interface, we'd be seeing a MakeInterface 161 // instruction. 162 return false 163 } 164 case *ir.Sigma: 165 iff, ok := v.From.Control().(*ir.If) 166 if !ok { 167 // give up 168 return false 169 } 170 171 binop, ok := iff.Cond.(*ir.BinOp) 172 if !ok { 173 // give up 174 return false 175 } 176 177 if (binop.X == v.X && isUntypedNil(binop.Y)) || (isUntypedNil(binop.X) && binop.Y == v.X) { 178 op := binop.Op 179 if v.From.Succs[0] != v.Block() { 180 // we're in the false branch, negate op 181 switch op { 182 case token.EQL: 183 op = token.NEQ 184 case token.NEQ: 185 op = token.EQL 186 default: 187 panic(fmt.Sprintf("internal error: unhandled token %v", op)) 188 } 189 } 190 191 switch op { 192 case token.EQL: 193 // returned value equals untyped nil 194 return false 195 case token.NEQ: 196 // returned value does not equal untyped nil 197 return true 198 default: 199 panic(fmt.Sprintf("internal error: unhandled token %v", op)) 200 } 201 } 202 203 // TODO(dh): handle comparison with typed nil 204 205 // give up 206 return false 207 case *ir.Phi: 208 for _, pv := range v.Edges { 209 if !do(pv, seen) { 210 return false 211 } 212 } 213 return true 214 case *ir.MakeInterface: 215 terms, err := typeparams.NormalTerms(v.X.Type()) 216 if len(terms) == 0 || err != nil { 217 // Type is a type parameter with no type terms (or we couldn't determine the terms). Such a type 218 // _can_ be nil when put in an interface value. 219 // 220 // There is no instruction that can create a guaranteed non-nil instance of a type parameter without 221 // type constraints, so we return false right away, without checking v.X's typedness. 222 return false 223 } 224 return true 225 case *ir.TypeAssert: 226 // type assertions fail for untyped nils. Either we have a 227 // single lhs and the type assertion succeeds or panics, 228 // or we have two lhs and we'll return Extract instead. 229 return true 230 case *ir.ChangeType: 231 // we'll only see interface->interface conversions, which 232 // don't tell us anything about the nilness. 233 return false 234 case *ir.MapLookup, *ir.Index, *ir.Recv, *ir.Parameter, *ir.Load, *ir.Field: 235 // All other instructions that tell us nothing about the 236 // typedness of interface values. 237 return false 238 default: 239 panic(fmt.Sprintf("internal error: unhandled type %T", v)) 240 } 241 } 242 243 ret := fn.Exit.Control().(*ir.Return) 244 for i, v := range ret.Results { 245 typ := fn.Signature.Results().At(i).Type() 246 if _, ok := typ.Underlying().(*types.Interface); ok && !typeparams.IsTypeParam(typ) { 247 if do(v, map[ir.Value]struct{}{}) { 248 out |= 1 << i 249 } 250 } 251 } 252 return out 253 }