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