blob: a3f5e555e9f2478c54b016aba0b8b4f347e65369 [file] [log] [blame]
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package inline
import (
"fmt"
"go/ast"
"go/token"
"go/types"
)
// escape implements a simple "address-taken" escape analysis. It
// calls f for each local variable that appears on the left side of an
// assignment (escapes=false) or has its address taken (escapes=true).
// The initialization of a variable by its declaration does not count
// as an assignment.
func escape(info *types.Info, root ast.Node, f func(v *types.Var, escapes bool)) {
// lvalue is called for each address-taken expression or LHS of assignment.
// Supported forms are: x, (x), x[i], x.f, *x, T{}.
var lvalue func(e ast.Expr, escapes bool)
lvalue = func(e ast.Expr, escapes bool) {
switch e := e.(type) {
case *ast.Ident:
if v, ok := info.Uses[e].(*types.Var); ok {
if !isPkgLevel(v) {
f(v, escapes)
}
}
case *ast.ParenExpr:
lvalue(e.X, escapes)
case *ast.IndexExpr:
// TODO(adonovan): support generics without assuming e.X has a core type.
// Consider:
//
// func Index[T interface{ [3]int | []int }](t T, i int) *int {
// return &t[i]
// }
//
// We must traverse the normal terms and check
// whether any of them is an array.
if _, ok := info.TypeOf(e.X).Underlying().(*types.Array); ok {
lvalue(e.X, escapes) // &a[i] on array
}
case *ast.SelectorExpr:
if _, ok := info.TypeOf(e.X).Underlying().(*types.Struct); ok {
lvalue(e.X, escapes) // &s.f on struct
}
case *ast.StarExpr:
// *ptr indirects an existing pointer
case *ast.CompositeLit:
// &T{...} creates a new variable
default:
panic(fmt.Sprintf("&x on %T", e)) // unreachable in well-typed code
}
}
// Search function body for operations &x, x.f(), x++, and x = y
// where x is a parameter. Each of these treats x as an address.
ast.Inspect(root, func(n ast.Node) bool {
switch n := n.(type) {
case *ast.UnaryExpr:
if n.Op == token.AND {
lvalue(n.X, true) // &x
}
case *ast.CallExpr:
// implicit &x in method call x.f(),
// where x has type T and method is (*T).f
if sel, ok := n.Fun.(*ast.SelectorExpr); ok {
if seln, ok := info.Selections[sel]; ok &&
seln.Kind() == types.MethodVal &&
isPointer(seln.Obj().Type().Underlying().(*types.Signature).Recv().Type()) {
tArg, indirect := effectiveReceiver(seln)
if !indirect && !isPointer(tArg) {
lvalue(sel.X, true) // &x.f
}
}
}
case *ast.AssignStmt:
for _, lhs := range n.Lhs {
if id, ok := lhs.(*ast.Ident); ok &&
info.Defs[id] != nil &&
n.Tok == token.DEFINE {
// declaration: doesn't count
} else {
lvalue(lhs, false)
}
}
case *ast.IncDecStmt:
lvalue(n.X, false)
}
return true
})
}