golang.org/x/tools@v0.21.0/internal/refactor/inline/callee.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package inline
     6  
     7  // This file defines the analysis of the callee function.
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/gob"
    12  	"fmt"
    13  	"go/ast"
    14  	"go/parser"
    15  	"go/token"
    16  	"go/types"
    17  	"strings"
    18  
    19  	"golang.org/x/tools/go/ast/astutil"
    20  	"golang.org/x/tools/go/types/typeutil"
    21  	"golang.org/x/tools/internal/typeparams"
    22  )
    23  
    24  // A Callee holds information about an inlinable function. Gob-serializable.
    25  type Callee struct {
    26  	impl gobCallee
    27  }
    28  
    29  func (callee *Callee) String() string { return callee.impl.Name }
    30  
    31  type gobCallee struct {
    32  	Content []byte // file content, compacted to a single func decl
    33  
    34  	// results of type analysis (does not reach go/types data structures)
    35  	PkgPath          string                 // package path of declaring package
    36  	Name             string                 // user-friendly name for error messages
    37  	Unexported       []string               // names of free objects that are unexported
    38  	FreeRefs         []freeRef              // locations of references to free objects
    39  	FreeObjs         []object               // descriptions of free objects
    40  	ValidForCallStmt bool                   // function body is "return expr" where expr is f() or <-ch
    41  	NumResults       int                    // number of results (according to type, not ast.FieldList)
    42  	Params           []*paramInfo           // information about parameters (incl. receiver)
    43  	Results          []*paramInfo           // information about result variables
    44  	Effects          []int                  // order in which parameters are evaluated (see calleefx)
    45  	HasDefer         bool                   // uses defer
    46  	HasBareReturn    bool                   // uses bare return in non-void function
    47  	Returns          [][]returnOperandFlags // metadata about result expressions for each return
    48  	Labels           []string               // names of all control labels
    49  	Falcon           falconResult           // falcon constraint system
    50  }
    51  
    52  // returnOperandFlags records metadata about a single result expression in a return
    53  // statement.
    54  type returnOperandFlags int
    55  
    56  const (
    57  	nonTrivialResult returnOperandFlags = 1 << iota // return operand has non-trivial conversion to result type
    58  	untypedNilResult                                // return operand is nil literal
    59  )
    60  
    61  // A freeRef records a reference to a free object. Gob-serializable.
    62  // (This means free relative to the FuncDecl as a whole, i.e. excluding parameters.)
    63  type freeRef struct {
    64  	Offset int // byte offset of the reference relative to the FuncDecl
    65  	Object int // index into Callee.freeObjs
    66  }
    67  
    68  // An object abstracts a free types.Object referenced by the callee. Gob-serializable.
    69  type object struct {
    70  	Name    string // Object.Name()
    71  	Kind    string // one of {var,func,const,type,pkgname,nil,builtin}
    72  	PkgPath string // path of object's package (or imported package if kind="pkgname")
    73  	PkgName string // name of object's package (or imported package if kind="pkgname")
    74  	// TODO(rfindley): should we also track LocalPkgName here? Do we want to
    75  	// preserve the local package name?
    76  	ValidPos bool            // Object.Pos().IsValid()
    77  	Shadow   map[string]bool // names shadowed at one of the object's refs
    78  }
    79  
    80  // AnalyzeCallee analyzes a function that is a candidate for inlining
    81  // and returns a Callee that describes it. The Callee object, which is
    82  // serializable, can be passed to one or more subsequent calls to
    83  // Inline, each with a different Caller.
    84  //
    85  // This design allows separate analysis of callers and callees in the
    86  // golang.org/x/tools/go/analysis framework: the inlining information
    87  // about a callee can be recorded as a "fact".
    88  //
    89  // The content should be the actual input to the compiler, not the
    90  // apparent source file according to any //line directives that
    91  // may be present within it.
    92  func AnalyzeCallee(logf func(string, ...any), fset *token.FileSet, pkg *types.Package, info *types.Info, decl *ast.FuncDecl, content []byte) (*Callee, error) {
    93  	checkInfoFields(info)
    94  
    95  	// The client is expected to have determined that the callee
    96  	// is a function with a declaration (not a built-in or var).
    97  	fn := info.Defs[decl.Name].(*types.Func)
    98  	sig := fn.Type().(*types.Signature)
    99  
   100  	logf("analyzeCallee %v @ %v", fn, fset.PositionFor(decl.Pos(), false))
   101  
   102  	// Create user-friendly name ("pkg.Func" or "(pkg.T).Method")
   103  	var name string
   104  	if sig.Recv() == nil {
   105  		name = fmt.Sprintf("%s.%s", fn.Pkg().Name(), fn.Name())
   106  	} else {
   107  		name = fmt.Sprintf("(%s).%s", types.TypeString(sig.Recv().Type(), (*types.Package).Name), fn.Name())
   108  	}
   109  
   110  	if decl.Body == nil {
   111  		return nil, fmt.Errorf("cannot inline function %s as it has no body", name)
   112  	}
   113  
   114  	// TODO(adonovan): support inlining of instantiated generic
   115  	// functions by replacing each occurrence of a type parameter
   116  	// T by its instantiating type argument (e.g. int). We'll need
   117  	// to wrap the instantiating type in parens when it's not an
   118  	// ident or qualified ident to prevent "if x == struct{}"
   119  	// parsing ambiguity, or "T(x)" where T = "*int" or "func()"
   120  	// from misparsing.
   121  	if funcHasTypeParams(decl) {
   122  		return nil, fmt.Errorf("cannot inline generic function %s: type parameters are not yet supported", name)
   123  	}
   124  
   125  	// Record the location of all free references in the FuncDecl.
   126  	// (Parameters are not free by this definition.)
   127  	var (
   128  		freeObjIndex = make(map[types.Object]int)
   129  		freeObjs     []object
   130  		freeRefs     []freeRef // free refs that may need renaming
   131  		unexported   []string  // free refs to unexported objects, for later error checks
   132  	)
   133  	var f func(n ast.Node) bool
   134  	visit := func(n ast.Node) { ast.Inspect(n, f) }
   135  	var stack []ast.Node
   136  	stack = append(stack, decl.Type) // for scope of function itself
   137  	f = func(n ast.Node) bool {
   138  		if n != nil {
   139  			stack = append(stack, n) // push
   140  		} else {
   141  			stack = stack[:len(stack)-1] // pop
   142  		}
   143  		switch n := n.(type) {
   144  		case *ast.SelectorExpr:
   145  			// Check selections of free fields/methods.
   146  			if sel, ok := info.Selections[n]; ok &&
   147  				!within(sel.Obj().Pos(), decl) &&
   148  				!n.Sel.IsExported() {
   149  				sym := fmt.Sprintf("(%s).%s", info.TypeOf(n.X), n.Sel.Name)
   150  				unexported = append(unexported, sym)
   151  			}
   152  
   153  			// Don't recur into SelectorExpr.Sel.
   154  			visit(n.X)
   155  			return false
   156  
   157  		case *ast.CompositeLit:
   158  			// Check for struct literals that refer to unexported fields,
   159  			// whether keyed or unkeyed. (Logic assumes well-typedness.)
   160  			litType := typeparams.Deref(info.TypeOf(n))
   161  			if s, ok := typeparams.CoreType(litType).(*types.Struct); ok {
   162  				if n.Type != nil {
   163  					visit(n.Type)
   164  				}
   165  				for i, elt := range n.Elts {
   166  					var field *types.Var
   167  					var value ast.Expr
   168  					if kv, ok := elt.(*ast.KeyValueExpr); ok {
   169  						field = info.Uses[kv.Key.(*ast.Ident)].(*types.Var)
   170  						value = kv.Value
   171  					} else {
   172  						field = s.Field(i)
   173  						value = elt
   174  					}
   175  					if !within(field.Pos(), decl) && !field.Exported() {
   176  						sym := fmt.Sprintf("(%s).%s", litType, field.Name())
   177  						unexported = append(unexported, sym)
   178  					}
   179  
   180  					// Don't recur into KeyValueExpr.Key.
   181  					visit(value)
   182  				}
   183  				return false
   184  			}
   185  
   186  		case *ast.Ident:
   187  			if obj, ok := info.Uses[n]; ok {
   188  				// Methods and fields are handled by SelectorExpr and CompositeLit.
   189  				if isField(obj) || isMethod(obj) {
   190  					panic(obj)
   191  				}
   192  				// Inv: id is a lexical reference.
   193  
   194  				// A reference to an unexported package-level declaration
   195  				// cannot be inlined into another package.
   196  				if !n.IsExported() &&
   197  					obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() {
   198  					unexported = append(unexported, n.Name)
   199  				}
   200  
   201  				// Record free reference (incl. self-reference).
   202  				if obj == fn || !within(obj.Pos(), decl) {
   203  					objidx, ok := freeObjIndex[obj]
   204  					if !ok {
   205  						objidx = len(freeObjIndex)
   206  						var pkgpath, pkgname string
   207  						if pn, ok := obj.(*types.PkgName); ok {
   208  							pkgpath = pn.Imported().Path()
   209  							pkgname = pn.Imported().Name()
   210  						} else if obj.Pkg() != nil {
   211  							pkgpath = obj.Pkg().Path()
   212  							pkgname = obj.Pkg().Name()
   213  						}
   214  						freeObjs = append(freeObjs, object{
   215  							Name:     obj.Name(),
   216  							Kind:     objectKind(obj),
   217  							PkgName:  pkgname,
   218  							PkgPath:  pkgpath,
   219  							ValidPos: obj.Pos().IsValid(),
   220  						})
   221  						freeObjIndex[obj] = objidx
   222  					}
   223  
   224  					freeObjs[objidx].Shadow = addShadows(freeObjs[objidx].Shadow, info, obj.Name(), stack)
   225  
   226  					freeRefs = append(freeRefs, freeRef{
   227  						Offset: int(n.Pos() - decl.Pos()),
   228  						Object: objidx,
   229  					})
   230  				}
   231  			}
   232  		}
   233  		return true
   234  	}
   235  	visit(decl)
   236  
   237  	// Analyze callee body for "return expr" form,
   238  	// where expr is f() or <-ch. These forms are
   239  	// safe to inline as a standalone statement.
   240  	validForCallStmt := false
   241  	if len(decl.Body.List) != 1 {
   242  		// not just a return statement
   243  	} else if ret, ok := decl.Body.List[0].(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
   244  		validForCallStmt = func() bool {
   245  			switch expr := astutil.Unparen(ret.Results[0]).(type) {
   246  			case *ast.CallExpr: // f(x)
   247  				callee := typeutil.Callee(info, expr)
   248  				if callee == nil {
   249  					return false // conversion T(x)
   250  				}
   251  
   252  				// The only non-void built-in functions that may be
   253  				// called as a statement are copy and recover
   254  				// (though arguably a call to recover should never
   255  				// be inlined as that changes its behavior).
   256  				if builtin, ok := callee.(*types.Builtin); ok {
   257  					return builtin.Name() == "copy" ||
   258  						builtin.Name() == "recover"
   259  				}
   260  
   261  				return true // ordinary call f()
   262  
   263  			case *ast.UnaryExpr: // <-x
   264  				return expr.Op == token.ARROW // channel receive <-ch
   265  			}
   266  
   267  			// No other expressions are valid statements.
   268  			return false
   269  		}()
   270  	}
   271  
   272  	// Record information about control flow in the callee
   273  	// (but not any nested functions).
   274  	var (
   275  		hasDefer      = false
   276  		hasBareReturn = false
   277  		returnInfo    [][]returnOperandFlags
   278  		labels        []string
   279  	)
   280  	ast.Inspect(decl.Body, func(n ast.Node) bool {
   281  		switch n := n.(type) {
   282  		case *ast.FuncLit:
   283  			return false // prune traversal
   284  		case *ast.DeferStmt:
   285  			hasDefer = true
   286  		case *ast.LabeledStmt:
   287  			labels = append(labels, n.Label.Name)
   288  		case *ast.ReturnStmt:
   289  
   290  			// Are implicit assignment conversions
   291  			// to result variables all trivial?
   292  			var resultInfo []returnOperandFlags
   293  			if len(n.Results) > 0 {
   294  				argInfo := func(i int) (ast.Expr, types.Type) {
   295  					expr := n.Results[i]
   296  					return expr, info.TypeOf(expr)
   297  				}
   298  				if len(n.Results) == 1 && sig.Results().Len() > 1 {
   299  					// Spread return: return f() where f.Results > 1.
   300  					tuple := info.TypeOf(n.Results[0]).(*types.Tuple)
   301  					argInfo = func(i int) (ast.Expr, types.Type) {
   302  						return nil, tuple.At(i).Type()
   303  					}
   304  				}
   305  				for i := 0; i < sig.Results().Len(); i++ {
   306  					expr, typ := argInfo(i)
   307  					var flags returnOperandFlags
   308  					if typ == types.Typ[types.UntypedNil] { // untyped nil is preserved by go/types
   309  						flags |= untypedNilResult
   310  					}
   311  					if !trivialConversion(info.Types[expr].Value, typ, sig.Results().At(i).Type()) {
   312  						flags |= nonTrivialResult
   313  					}
   314  					resultInfo = append(resultInfo, flags)
   315  				}
   316  			} else if sig.Results().Len() > 0 {
   317  				hasBareReturn = true
   318  			}
   319  			returnInfo = append(returnInfo, resultInfo)
   320  		}
   321  		return true
   322  	})
   323  
   324  	// Reject attempts to inline cgo-generated functions.
   325  	for _, obj := range freeObjs {
   326  		// There are others (iconst fconst sconst fpvar macro)
   327  		// but this is probably sufficient.
   328  		if strings.HasPrefix(obj.Name, "_Cfunc_") ||
   329  			strings.HasPrefix(obj.Name, "_Ctype_") ||
   330  			strings.HasPrefix(obj.Name, "_Cvar_") {
   331  			return nil, fmt.Errorf("cannot inline cgo-generated functions")
   332  		}
   333  	}
   334  
   335  	// Compact content to just the FuncDecl.
   336  	//
   337  	// As a space optimization, we don't retain the complete
   338  	// callee file content; all we need is "package _; func f() { ... }".
   339  	// This reduces the size of analysis facts.
   340  	//
   341  	// Offsets in the callee information are "relocatable"
   342  	// since they are all relative to the FuncDecl.
   343  
   344  	content = append([]byte("package _\n"),
   345  		content[offsetOf(fset, decl.Pos()):offsetOf(fset, decl.End())]...)
   346  	// Sanity check: re-parse the compacted content.
   347  	if _, _, err := parseCompact(content); err != nil {
   348  		return nil, err
   349  	}
   350  
   351  	params, results, effects, falcon := analyzeParams(logf, fset, info, decl)
   352  	return &Callee{gobCallee{
   353  		Content:          content,
   354  		PkgPath:          pkg.Path(),
   355  		Name:             name,
   356  		Unexported:       unexported,
   357  		FreeObjs:         freeObjs,
   358  		FreeRefs:         freeRefs,
   359  		ValidForCallStmt: validForCallStmt,
   360  		NumResults:       sig.Results().Len(),
   361  		Params:           params,
   362  		Results:          results,
   363  		Effects:          effects,
   364  		HasDefer:         hasDefer,
   365  		HasBareReturn:    hasBareReturn,
   366  		Returns:          returnInfo,
   367  		Labels:           labels,
   368  		Falcon:           falcon,
   369  	}}, nil
   370  }
   371  
   372  // parseCompact parses a Go source file of the form "package _\n func f() { ... }"
   373  // and returns the sole function declaration.
   374  func parseCompact(content []byte) (*token.FileSet, *ast.FuncDecl, error) {
   375  	fset := token.NewFileSet()
   376  	const mode = parser.ParseComments | parser.SkipObjectResolution | parser.AllErrors
   377  	f, err := parser.ParseFile(fset, "callee.go", content, mode)
   378  	if err != nil {
   379  		return nil, nil, fmt.Errorf("internal error: cannot compact file: %v", err)
   380  	}
   381  	return fset, f.Decls[0].(*ast.FuncDecl), nil
   382  }
   383  
   384  // A paramInfo records information about a callee receiver, parameter, or result variable.
   385  type paramInfo struct {
   386  	Name       string          // parameter name (may be blank, or even "")
   387  	Index      int             // index within signature
   388  	IsResult   bool            // false for receiver or parameter, true for result variable
   389  	Assigned   bool            // parameter appears on left side of an assignment statement
   390  	Escapes    bool            // parameter has its address taken
   391  	Refs       []int           // FuncDecl-relative byte offset of parameter ref within body
   392  	Shadow     map[string]bool // names shadowed at one of the above refs
   393  	FalconType string          // name of this parameter's type (if basic) in the falcon system
   394  }
   395  
   396  // analyzeParams computes information about parameters of function fn,
   397  // including a simple "address taken" escape analysis.
   398  //
   399  // It returns two new arrays, one of the receiver and parameters, and
   400  // the other of the result variables of function fn.
   401  //
   402  // The input must be well-typed.
   403  func analyzeParams(logf func(string, ...any), fset *token.FileSet, info *types.Info, decl *ast.FuncDecl) (params, results []*paramInfo, effects []int, _ falconResult) {
   404  	fnobj, ok := info.Defs[decl.Name]
   405  	if !ok {
   406  		panic(fmt.Sprintf("%s: no func object for %q",
   407  			fset.PositionFor(decl.Name.Pos(), false), decl.Name)) // ill-typed?
   408  	}
   409  
   410  	paramInfos := make(map[*types.Var]*paramInfo)
   411  	{
   412  		sig := fnobj.Type().(*types.Signature)
   413  		newParamInfo := func(param *types.Var, isResult bool) *paramInfo {
   414  			info := &paramInfo{
   415  				Name:     param.Name(),
   416  				IsResult: isResult,
   417  				Index:    len(paramInfos),
   418  			}
   419  			paramInfos[param] = info
   420  			return info
   421  		}
   422  		if sig.Recv() != nil {
   423  			params = append(params, newParamInfo(sig.Recv(), false))
   424  		}
   425  		for i := 0; i < sig.Params().Len(); i++ {
   426  			params = append(params, newParamInfo(sig.Params().At(i), false))
   427  		}
   428  		for i := 0; i < sig.Results().Len(); i++ {
   429  			results = append(results, newParamInfo(sig.Results().At(i), true))
   430  		}
   431  	}
   432  
   433  	// Search function body for operations &x, x.f(), and x = y
   434  	// where x is a parameter, and record it.
   435  	escape(info, decl, func(v *types.Var, escapes bool) {
   436  		if info := paramInfos[v]; info != nil {
   437  			if escapes {
   438  				info.Escapes = true
   439  			} else {
   440  				info.Assigned = true
   441  			}
   442  		}
   443  	})
   444  
   445  	// Record locations of all references to parameters.
   446  	// And record the set of intervening definitions for each parameter.
   447  	//
   448  	// TODO(adonovan): combine this traversal with the one that computes
   449  	// FreeRefs. The tricky part is that calleefx needs this one first.
   450  	var stack []ast.Node
   451  	stack = append(stack, decl.Type) // for scope of function itself
   452  	ast.Inspect(decl.Body, func(n ast.Node) bool {
   453  		if n != nil {
   454  			stack = append(stack, n) // push
   455  		} else {
   456  			stack = stack[:len(stack)-1] // pop
   457  		}
   458  
   459  		if id, ok := n.(*ast.Ident); ok {
   460  			if v, ok := info.Uses[id].(*types.Var); ok {
   461  				if pinfo, ok := paramInfos[v]; ok {
   462  					// Record location of ref to parameter/result
   463  					// and any intervening (shadowing) names.
   464  					offset := int(n.Pos() - decl.Pos())
   465  					pinfo.Refs = append(pinfo.Refs, offset)
   466  					pinfo.Shadow = addShadows(pinfo.Shadow, info, pinfo.Name, stack)
   467  				}
   468  			}
   469  		}
   470  		return true
   471  	})
   472  
   473  	// Compute subset and order of parameters that are strictly evaluated.
   474  	// (Depends on Refs computed above.)
   475  	effects = calleefx(info, decl.Body, paramInfos)
   476  	logf("effects list = %v", effects)
   477  
   478  	falcon := falcon(logf, fset, paramInfos, info, decl)
   479  
   480  	return params, results, effects, falcon
   481  }
   482  
   483  // -- callee helpers --
   484  
   485  // addShadows returns the shadows set augmented by the set of names
   486  // locally shadowed at the location of the reference in the callee
   487  // (identified by the stack). The name of the reference itself is
   488  // excluded.
   489  //
   490  // These shadowed names may not be used in a replacement expression
   491  // for the reference.
   492  func addShadows(shadows map[string]bool, info *types.Info, exclude string, stack []ast.Node) map[string]bool {
   493  	for _, n := range stack {
   494  		if scope := scopeFor(info, n); scope != nil {
   495  			for _, name := range scope.Names() {
   496  				if name != exclude {
   497  					if shadows == nil {
   498  						shadows = make(map[string]bool)
   499  					}
   500  					shadows[name] = true
   501  				}
   502  			}
   503  		}
   504  	}
   505  	return shadows
   506  }
   507  
   508  func isField(obj types.Object) bool {
   509  	if v, ok := obj.(*types.Var); ok && v.IsField() {
   510  		return true
   511  	}
   512  	return false
   513  }
   514  
   515  func isMethod(obj types.Object) bool {
   516  	if f, ok := obj.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil {
   517  		return true
   518  	}
   519  	return false
   520  }
   521  
   522  // -- serialization --
   523  
   524  var (
   525  	_ gob.GobEncoder = (*Callee)(nil)
   526  	_ gob.GobDecoder = (*Callee)(nil)
   527  )
   528  
   529  func (callee *Callee) GobEncode() ([]byte, error) {
   530  	var out bytes.Buffer
   531  	if err := gob.NewEncoder(&out).Encode(callee.impl); err != nil {
   532  		return nil, err
   533  	}
   534  	return out.Bytes(), nil
   535  }
   536  
   537  func (callee *Callee) GobDecode(data []byte) error {
   538  	return gob.NewDecoder(bytes.NewReader(data)).Decode(&callee.impl)
   539  }