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  }