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