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 := ¶mInfo{ 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 }