mirror of
https://github.com/golang/go
synced 2024-11-05 17:06:13 -07:00
go.tools/ssa: record lvalue/rvalue distinction precisely in DebugRef.
A DebugRef associates a source expression E with an ssa.Value V, but until now did not record whether V was the value or the address of E. So, we would guess from the "pointerness" of the Value, leading to confusion in some cases, e.g. type N *N var n N n = &n // lvalue and rvalue are both pointers Now we explicitly record 'IsAddress bool' in DebugRef, and plumb this everywhere: through (*Function).ValueForExpr and (*Program).VarValue, all the way to forming the pointer analysis query. Also: - VarValue now treats each reference to a global distinctly, just like it does for other vars. So: var g int func f() { g = 1 // VarValue(g) == Const(1:int), !isAddress print(g) // VarValue(g) == Global(g), isAddress } - DebugRefs are not emitted for references to predeclared identifiers (nil, built-in). - DebugRefs no longer prevent lifting of an Alloc var into a register; now we update or discard the debug info. - TestValueForExpr: improve coverage of ssa.EnclosingFunction by putting expectations in methods and init funcs, not just normal funcs. - oracle: fix golden file broken by recent (*types.Var).IsField change. R=gri CC=golang-dev https://golang.org/cl/16610045
This commit is contained in:
parent
69f5b543df
commit
9f640c2abb
@ -302,25 +302,27 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
|
||||
// ---- VALUE ------------------------------------------------------------
|
||||
|
||||
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path
|
||||
// to the root of the AST is path. It may return a nil Value without
|
||||
// an error to indicate the pointer analysis is not appropriate.
|
||||
// to the root of the AST is path. isAddr reports whether the
|
||||
// ssa.Value is the address denoted by the ast.Ident, not its value.
|
||||
// ssaValueForIdent may return a nil Value without an error to
|
||||
// indicate the pointer analysis is not appropriate.
|
||||
//
|
||||
func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (ssa.Value, error) {
|
||||
func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
if obj, ok := obj.(*types.Var); ok {
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.Build()
|
||||
if v := prog.VarValue(obj, pkg, path); v != nil {
|
||||
if v, addr := prog.VarValue(obj, pkg, path); v != nil {
|
||||
// Don't run pointer analysis on a ref to a const expression.
|
||||
if _, ok := v.(*ssa.Const); ok {
|
||||
v = nil
|
||||
return
|
||||
}
|
||||
return v, nil
|
||||
return v, addr, nil
|
||||
}
|
||||
return nil, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name())
|
||||
}
|
||||
|
||||
// Don't run pointer analysis on const/func objects.
|
||||
return nil, nil
|
||||
return
|
||||
}
|
||||
|
||||
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
||||
@ -328,21 +330,21 @@ func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.
|
||||
// return a nil Value without an error to indicate the pointer
|
||||
// analysis is not appropriate.
|
||||
//
|
||||
func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (ssa.Value, error) {
|
||||
func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
||||
pkg := prog.Package(qinfo.Pkg)
|
||||
pkg.SetDebugMode(true)
|
||||
pkg.Build()
|
||||
|
||||
fn := ssa.EnclosingFunction(pkg, path)
|
||||
if fn == nil {
|
||||
return nil, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
if v := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
||||
return v, nil
|
||||
if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
|
||||
return v, addr, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
||||
return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn)
|
||||
}
|
||||
|
||||
func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
@ -384,22 +386,18 @@ func describeValue(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeValueRe
|
||||
if pointer.CanPoint(typ) {
|
||||
// Determine the ssa.Value for the expression.
|
||||
var value ssa.Value
|
||||
var isAddr bool
|
||||
if obj != nil {
|
||||
// def/ref of func/var/const object
|
||||
value, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
||||
value, isAddr, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path)
|
||||
} else {
|
||||
// any other expression
|
||||
if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant?
|
||||
value, ptaErr = ssaValueForExpr(o.prog, qpos.info, path)
|
||||
value, isAddr, ptaErr = ssaValueForExpr(o.prog, qpos.info, path)
|
||||
}
|
||||
}
|
||||
if value != nil {
|
||||
// TODO(adonovan): IsIdentical may be too strict;
|
||||
// perhaps we need is-assignable or even
|
||||
// has-same-underlying-representation?
|
||||
indirect := types.IsIdentical(types.NewPointer(typ), value.Type())
|
||||
|
||||
ptrs, ptaErr = describePointer(o, value, indirect)
|
||||
ptrs, ptaErr = describePointer(o, value, isAddr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,13 +476,6 @@ func (r *describeValueResult) display(printf printfFunc) {
|
||||
prefix = "method "
|
||||
}
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
// TODO(adonovan): go/types should make it simple to
|
||||
// ask: IsStructField(*Var)?
|
||||
if false {
|
||||
prefix = "struct field "
|
||||
}
|
||||
}
|
||||
|
||||
// Describe the expression.
|
||||
|
@ -42,7 +42,7 @@
|
||||
"referrers": {
|
||||
"pos": "testdata/src/main/referrers-json.go:20:10",
|
||||
"objpos": "testdata/src/main/referrers-json.go:10:2",
|
||||
"desc": "var f int",
|
||||
"desc": "field f int",
|
||||
"refs": [
|
||||
"testdata/src/main/referrers-json.go:20:10",
|
||||
"testdata/src/main/referrers-json.go:23:5"
|
||||
|
@ -495,7 +495,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
||||
e = unparen(e)
|
||||
v := b.expr0(fn, e)
|
||||
if fn.debugInfo() {
|
||||
emitDebugRef(fn, e, v)
|
||||
emitDebugRef(fn, e, v, false)
|
||||
}
|
||||
return v
|
||||
}
|
||||
@ -686,7 +686,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value {
|
||||
v := b.expr(fn, e.X)
|
||||
v = emitImplicitSelections(fn, v, indices[:last])
|
||||
v = emitFieldSelection(fn, v, indices[last], false, e.Sel.Pos())
|
||||
emitDebugRef(fn, e.Sel, v)
|
||||
emitDebugRef(fn, e.Sel, v, false)
|
||||
return v
|
||||
}
|
||||
|
||||
@ -1091,7 +1091,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
|
||||
if !isBlankIdent(id) {
|
||||
lhs := fn.addLocalForIdent(id)
|
||||
if fn.debugInfo() {
|
||||
emitDebugRef(fn, id, emitLoad(fn, lhs))
|
||||
emitDebugRef(fn, id, lhs, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1626,7 +1626,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
|
||||
case *ast.ExprStmt: // <-ch
|
||||
if debugInfo {
|
||||
v := emitExtract(fn, sel, r, vars[r].Type())
|
||||
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v)
|
||||
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
|
||||
}
|
||||
r++
|
||||
|
||||
@ -1637,7 +1637,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
|
||||
x := b.addr(fn, comm.Lhs[0], false) // non-escaping
|
||||
v := emitExtract(fn, sel, r, vars[r].Type())
|
||||
if debugInfo {
|
||||
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v)
|
||||
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
|
||||
}
|
||||
x.store(fn, v)
|
||||
|
||||
|
@ -37,7 +37,7 @@ func emitLoad(f *Function, addr Value) *UnOp {
|
||||
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
|
||||
// expression e with value v.
|
||||
//
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value) {
|
||||
func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) {
|
||||
if !f.debugInfo() {
|
||||
return // debugging not enabled
|
||||
}
|
||||
@ -50,13 +50,15 @@ func emitDebugRef(f *Function, e ast.Expr, v Value) {
|
||||
return
|
||||
}
|
||||
obj = f.Pkg.objectOf(id)
|
||||
if _, ok := obj.(*types.Const); ok {
|
||||
switch obj.(type) {
|
||||
case *types.Nil, *types.Const, *types.Builtin:
|
||||
return
|
||||
}
|
||||
}
|
||||
f.emit(&DebugRef{
|
||||
X: v,
|
||||
Expr: unparen(e),
|
||||
IsAddr: isAddr,
|
||||
object: obj,
|
||||
})
|
||||
}
|
||||
|
26
ssa/lift.go
26
ssa/lift.go
@ -257,7 +257,7 @@ func lift(fn *Function) {
|
||||
// Remove any fn.Locals that were lifted.
|
||||
j := 0
|
||||
for _, l := range fn.Locals {
|
||||
if l.index == -1 {
|
||||
if l.index < 0 {
|
||||
fn.Locals[j] = l
|
||||
j++
|
||||
}
|
||||
@ -333,7 +333,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
|
||||
for _, instr := range *alloc.Referrers() {
|
||||
// Bail out if we discover the alloc is not liftable;
|
||||
// the only operations permitted to use the alloc are
|
||||
// loads/stores into the cell.
|
||||
// loads/stores into the cell, and DebugRef.
|
||||
switch instr := instr.(type) {
|
||||
case *Store:
|
||||
if instr.Val == alloc {
|
||||
@ -350,6 +350,8 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
|
||||
if instr.X != alloc {
|
||||
panic("Alloc.Referrers is inconsistent")
|
||||
}
|
||||
case *DebugRef:
|
||||
// ok
|
||||
default:
|
||||
return false // some other instruction
|
||||
}
|
||||
@ -464,10 +466,9 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
|
||||
// Rename loads and stores of allocs.
|
||||
for i, instr := range u.Instrs {
|
||||
_ = i
|
||||
switch instr := instr.(type) {
|
||||
case *Store:
|
||||
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index != -1 { // store to Alloc cell
|
||||
if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
|
||||
// Delete the Store.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
@ -480,7 +481,7 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
}
|
||||
case *UnOp:
|
||||
if instr.Op == token.MUL {
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index != -1 { // load of Alloc cell
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
|
||||
newval := renamed(renaming, alloc)
|
||||
if debugLifting {
|
||||
fmt.Fprintln(os.Stderr, "Replace refs to load", instr.Name(), "=", instr, "with", newval.Name())
|
||||
@ -494,6 +495,21 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
case *DebugRef:
|
||||
if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
|
||||
if instr.IsAddr {
|
||||
instr.X = renamed(renaming, alloc)
|
||||
instr.IsAddr = false
|
||||
} else {
|
||||
// A source expression denotes the address
|
||||
// of an Alloc that was optimized away.
|
||||
instr.X = nil
|
||||
|
||||
// Delete the DebugRef.
|
||||
u.Instrs[i] = nil
|
||||
u.gaps++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,15 +42,14 @@ func (a *address) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, a.addr, v)
|
||||
store.pos = a.starPos
|
||||
if a.expr != nil {
|
||||
// store.Val is v converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val)
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, a.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *address) address(fn *Function) Value {
|
||||
if a.expr != nil {
|
||||
// NB: this kind of DebugRef yields the object's address.
|
||||
emitDebugRef(fn, a.expr, a.addr)
|
||||
emitDebugRef(fn, a.expr, a.addr, true)
|
||||
}
|
||||
return a.addr
|
||||
}
|
||||
|
@ -394,7 +394,11 @@ func (s *DebugRef) String() string {
|
||||
} else {
|
||||
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
|
||||
}
|
||||
return fmt.Sprintf("; %s is %s @ %d:%d", s.X.Name(), descr, p.Line, p.Column)
|
||||
var addr string
|
||||
if s.IsAddr {
|
||||
addr = "address of "
|
||||
}
|
||||
return fmt.Sprintf("; %s is %s%s @ %d:%d", s.X.Name(), addr, descr, p.Line, p.Column)
|
||||
}
|
||||
|
||||
func (p *Package) String() string {
|
||||
|
@ -141,30 +141,34 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||
// - e is a constant expression. (For efficiency, no debug
|
||||
// information is stored for constants. Use
|
||||
// importer.PackageInfo.ValueOf(e) instead.)
|
||||
// - e is a reference to nil or a built-in function.
|
||||
// - the value was optimised away.
|
||||
//
|
||||
// The types of e and the result are equal (modulo "untyped" bools
|
||||
// resulting from comparisons) and they have equal "pointerness".
|
||||
// If e is an addressable expression used an an lvalue context,
|
||||
// value is the address denoted by e, and isAddr is true.
|
||||
//
|
||||
// The types of e (or &e, if isAddr) and the result are equal
|
||||
// (modulo "untyped" bools resulting from comparisons).
|
||||
//
|
||||
// (Tip: to find the ssa.Value given a source position, use
|
||||
// importer.PathEnclosingInterval to locate the ast.Node, then
|
||||
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
||||
// the ssa.Value.)
|
||||
//
|
||||
func (f *Function) ValueForExpr(e ast.Expr) Value {
|
||||
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
||||
if f.debugInfo() { // (opt)
|
||||
e = unparen(e)
|
||||
for _, b := range f.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if ref, ok := instr.(*DebugRef); ok {
|
||||
if ref.Expr == e {
|
||||
return ref.X
|
||||
return ref.X, ref.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// --- Lookup functions for source-level named entities (types.Objects) ---
|
||||
@ -231,43 +235,45 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
|
||||
// and that ident must resolve to obj.
|
||||
//
|
||||
// pkg is the package enclosing the reference. (A reference to a var
|
||||
// may result in code, so we need to know where to find that code.)
|
||||
// always occurs within a function, so we need to know where to find it.)
|
||||
//
|
||||
// The Value of a defining (as opposed to referring) identifier is the
|
||||
// value assigned to it in its definition.
|
||||
// value assigned to it in its definition. Similarly, the Value of an
|
||||
// identifier that is the LHS of an assignment is the value assigned
|
||||
// to it in that statement. In all these examples, VarValue(x) returns
|
||||
// the value of x and isAddr==false.
|
||||
//
|
||||
// var x X
|
||||
// var x = X{}
|
||||
// x := X{}
|
||||
// x = X{}
|
||||
//
|
||||
// When an identifier appears in an lvalue context other than as the
|
||||
// LHS of an assignment, the resulting Value is the var's address, not
|
||||
// its value. This situation is reported by isAddr, the second
|
||||
// component of the result. In these examples, VarValue(x) returns
|
||||
// the address of x and isAddr==true.
|
||||
//
|
||||
// In many cases where the identifier appears in an lvalue context,
|
||||
// the resulting Value is the var's address, not its value.
|
||||
// For example, x in all these examples:
|
||||
// x.y = 0
|
||||
// x[0] = 0
|
||||
// _ = x[:]
|
||||
// x = X{}
|
||||
// _ = x[:] (where x is an array)
|
||||
// _ = &x
|
||||
// x.method() (iff method is on &x)
|
||||
// and all package-level vars. (This situation can be detected by
|
||||
// comparing the types of the Var and Value.)
|
||||
//
|
||||
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) Value {
|
||||
id := ref[0].(*ast.Ident)
|
||||
|
||||
// Package-level variable?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Global)
|
||||
}
|
||||
|
||||
// Must be a function-local variable.
|
||||
// (e.g. local, parameter, or field selection e.f)
|
||||
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
|
||||
// All references to a var are local to some function, possibly init.
|
||||
fn := EnclosingFunction(pkg, ref)
|
||||
if fn == nil {
|
||||
return nil // e.g. SSA not built
|
||||
return // e.g. def of struct field; SSA not built?
|
||||
}
|
||||
|
||||
id := ref[0].(*ast.Ident)
|
||||
|
||||
// Defining ident of a parameter?
|
||||
if id.Pos() == obj.Pos() {
|
||||
for _, param := range fn.Params {
|
||||
if param.Object() == obj {
|
||||
return param
|
||||
return param, false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -275,13 +281,18 @@ func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) Valu
|
||||
// Other ident?
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if ref, ok := instr.(*DebugRef); ok {
|
||||
if ref.Pos() == id.Pos() {
|
||||
return ref.X
|
||||
if dr, ok := instr.(*DebugRef); ok {
|
||||
if dr.Pos() == id.Pos() {
|
||||
return dr.X, dr.IsAddr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil // e.g. debug info not requested, or var optimized away
|
||||
// Defining ident of package-level var?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Global), true
|
||||
}
|
||||
|
||||
return // e.g. debug info not requested, or var optimized away
|
||||
}
|
||||
|
@ -84,6 +84,9 @@ func TestObjValueLookup(t *testing.T) {
|
||||
// Check invariants for var objects.
|
||||
// The result varies based on the specific Ident.
|
||||
for _, id := range ids {
|
||||
if id.Name == "_" {
|
||||
continue
|
||||
}
|
||||
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
||||
ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||
pos := imp.Fset.Position(id.Pos())
|
||||
@ -145,7 +148,7 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
|
||||
prefix := fmt.Sprintf("VarValue(%s @ L%d)",
|
||||
obj, prog.Fset.Position(ref[0].Pos()).Line)
|
||||
|
||||
v := prog.VarValue(obj, pkg, ref)
|
||||
v, gotAddr := prog.VarValue(obj, pkg, ref)
|
||||
|
||||
// Kind is the concrete type of the ssa Value.
|
||||
gotKind := "nil"
|
||||
@ -153,7 +156,7 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
|
||||
gotKind = fmt.Sprintf("%T", v)[len("*ssa."):]
|
||||
}
|
||||
|
||||
// fmt.Printf("%s = %v (kind %q; expect %q) addr=%t\n", prefix, v, gotKind, expKind, wantAddr) // debugging
|
||||
// fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
|
||||
|
||||
// Check the kinds match.
|
||||
// "nil" indicates expected failure (e.g. optimized away).
|
||||
@ -167,6 +170,11 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
|
||||
expType := obj.Type()
|
||||
if wantAddr {
|
||||
expType = types.NewPointer(expType)
|
||||
if !gotAddr {
|
||||
t.Errorf("%s: got value, want address", prefix)
|
||||
}
|
||||
} else if gotAddr {
|
||||
t.Errorf("%s: got address, want value", prefix)
|
||||
}
|
||||
if !types.IsIdentical(v.Type(), expType) {
|
||||
t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
|
||||
@ -195,10 +203,13 @@ func TestValueForExpr(t *testing.T) {
|
||||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
fn := mainPkg.Func("f")
|
||||
|
||||
if false {
|
||||
fn.DumpTo(os.Stderr) // debugging
|
||||
// debugging
|
||||
for _, mem := range mainPkg.Members {
|
||||
if fn, ok := mem.(*ssa.Function); ok {
|
||||
fn.DumpTo(os.Stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the actual AST node for each canonical position.
|
||||
@ -229,14 +240,30 @@ func TestValueForExpr(t *testing.T) {
|
||||
e = target.X
|
||||
}
|
||||
|
||||
v := fn.ValueForExpr(e) // (may be nil)
|
||||
path, _ := importer.PathEnclosingInterval(f, pos, pos)
|
||||
if path == nil {
|
||||
t.Errorf("%s: can't find AST path from root to comment: %s", position, text)
|
||||
continue
|
||||
}
|
||||
|
||||
fn := ssa.EnclosingFunction(mainPkg, path)
|
||||
if fn == nil {
|
||||
t.Errorf("%s: can't find enclosing function", position)
|
||||
continue
|
||||
}
|
||||
|
||||
v, gotAddr := fn.ValueForExpr(e) // (may be nil)
|
||||
got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
|
||||
if want := text; got != want {
|
||||
t.Errorf("%s: got value %q, want %q", position, got, want)
|
||||
}
|
||||
if v != nil {
|
||||
if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) {
|
||||
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), v.Type())
|
||||
T := v.Type()
|
||||
if gotAddr {
|
||||
T = T.Underlying().(*types.Pointer).Elem() // deref
|
||||
}
|
||||
if !types.IsIdentical(T, mainInfo.TypeOf(e)) {
|
||||
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1171,8 +1171,9 @@ type MapUpdate struct {
|
||||
type DebugRef struct {
|
||||
anInstruction
|
||||
X Value // the value whose position we're declaring
|
||||
Expr ast.Expr // the referring expression
|
||||
Expr ast.Expr // the referring expression (never *ast.ParenExpr)
|
||||
object types.Object // the identity of the source var/const/func
|
||||
IsAddr bool // Expr is addressable and X is the address it denotes
|
||||
}
|
||||
|
||||
// Embeddable mix-ins and helpers for common parts of other structs. -----------
|
||||
|
25
ssa/testdata/objlookup.go
vendored
25
ssa/testdata/objlookup.go
vendored
@ -37,6 +37,9 @@ type S struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
print(globalVar) // globalVar::UnOp
|
||||
globalVar = 1 // globalVar::Const
|
||||
|
||||
var v0 int = 1 // v0::Const (simple local value spec)
|
||||
if v0 > 0 { // v0::Const
|
||||
v0 = 2 // v0::Const
|
||||
@ -74,11 +77,11 @@ func main() {
|
||||
print(v5) // v5::Const
|
||||
print(v6) // v6::Const
|
||||
|
||||
var v7 S // v7::UnOp (load from Alloc)
|
||||
var v7 S // &v7::Alloc
|
||||
v7.x = 1 // &v7::Alloc x::Const
|
||||
print(v7.x) // v7::UnOp x::Field
|
||||
|
||||
var v8 [1]int // v8::UnOp (load from Alloc)
|
||||
var v8 [1]int // &v8::Alloc
|
||||
v8[0] = 0 // &v8::Alloc
|
||||
print(v8[:]) // &v8::Alloc
|
||||
_ = v8[0] // v8::UnOp (load from Alloc)
|
||||
@ -87,6 +90,10 @@ func main() {
|
||||
_ = v8ptr[0] // v8ptr::Alloc
|
||||
_ = *v8ptr // v8ptr::Alloc
|
||||
|
||||
v8a := make([]int, 1) // v8a::MakeSlice
|
||||
v8a[0] = 0 // v8a::MakeSlice
|
||||
print(v8a[:]) // v8a::MakeSlice
|
||||
|
||||
v9 := S{} // &v9::Alloc
|
||||
|
||||
v10 := &v9 // v10::Alloc &v9::Alloc
|
||||
@ -95,7 +102,7 @@ func main() {
|
||||
var v11 *J = nil // v11::Const
|
||||
v11.method() // v11::Const
|
||||
|
||||
var v12 J // v12::UnOp (load from Alloc)
|
||||
var v12 J // &v12::Alloc
|
||||
v12.method() // &v12::Alloc (implicitly address-taken)
|
||||
|
||||
// NB, in the following, 'method' resolves to the *types.Func
|
||||
@ -137,7 +144,17 @@ func main() {
|
||||
}
|
||||
|
||||
// .Op is an inter-package FieldVal-selection.
|
||||
var err os.PathError // err::UnOp
|
||||
var err os.PathError // &err::Alloc
|
||||
_ = err.Op // err::UnOp Op::Field
|
||||
_ = &err.Op // &err::Alloc &Op::FieldAddr
|
||||
|
||||
// Exercise corner-cases of lvalues vs rvalues.
|
||||
// (Guessing IsAddr from the 'pointerness' won't cut it here.)
|
||||
type N *N
|
||||
var n N // n::Const
|
||||
n1 := n // n1::Const n::Const
|
||||
n2 := &n1 // n2::Alloc &n1::Alloc
|
||||
n3 := *n2 // n3::UnOp n2::Alloc
|
||||
n4 := **n3 // n4::UnOp n3::UnOp
|
||||
_ = n4 // n4::UnOp
|
||||
}
|
||||
|
41
ssa/testdata/valueforexpr.go
vendored
41
ssa/testdata/valueforexpr.go
vendored
@ -7,7 +7,9 @@ package main
|
||||
// annotation, when passed to Function.ValueForExpr(e), returns a
|
||||
// non-nil Value of the same type as e and of kind 'kind'.
|
||||
|
||||
func f(param int) {
|
||||
func f(spilled, unspilled int) {
|
||||
_ = /*@UnOp*/ (spilled)
|
||||
_ = /*@Parameter*/ (unspilled)
|
||||
_ = /*@<nil>*/ (1 + 2) // (constant)
|
||||
i := 0
|
||||
/*@Call*/ (print( /*@BinOp*/ (i + 1)))
|
||||
@ -33,11 +35,13 @@ func f(param int) {
|
||||
_ = map1
|
||||
_ = /*@MakeMap*/ (map[string]string{"": ""})
|
||||
_ = /*@MakeSlice*/ (make([]int, 0))
|
||||
_ = /*@MakeClosure*/ (func() { print(param) })
|
||||
_ = /*@MakeClosure*/ (func() { print(spilled) })
|
||||
sl := /*@Slice*/ ([]int{})
|
||||
_ = /*@Alloc*/ (&struct{}{})
|
||||
_ = /*@Slice*/ (sl[:0])
|
||||
_ = /*@Alloc*/ (new(int))
|
||||
_ = /*@<nil>*/ (new(int)) // optimized away
|
||||
tmp := /*@Alloc*/ (new(int))
|
||||
_ = tmp
|
||||
var iface interface{}
|
||||
_ = /*@TypeAssert*/ (iface.(int))
|
||||
_ = /*@UnOp*/ (sl[0])
|
||||
@ -45,4 +49,35 @@ func f(param int) {
|
||||
_ = /*@Index*/ ([2]int{}[0])
|
||||
var p *int
|
||||
_ = /*@UnOp*/ (*p)
|
||||
|
||||
_ = /*@UnOp*/ (global)
|
||||
/*@UnOp*/ (global)[""] = ""
|
||||
/*@Global*/ (global) = map[string]string{}
|
||||
|
||||
var local t
|
||||
/*UnOp*/ (local.x) = 1
|
||||
|
||||
// Exercise corner-cases of lvalues vs rvalues.
|
||||
type N *N
|
||||
var n N
|
||||
/*@UnOp*/ (n) = /*@UnOp*/ (n)
|
||||
/*@ChangeType*/ (n) = /*@Alloc*/ (&n)
|
||||
/*@UnOp*/ (n) = /*@UnOp*/ (*n)
|
||||
/*@UnOp*/ (n) = /*@UnOp*/ (**n)
|
||||
}
|
||||
|
||||
type t struct{ x int }
|
||||
|
||||
// Ensure we can locate methods of named types.
|
||||
func (t) f(param int) {
|
||||
_ = /*@Parameter*/ (param)
|
||||
}
|
||||
|
||||
// Ensure we can locate init functions.
|
||||
func init() {
|
||||
m := /*@MakeMap*/ (make(map[string]string))
|
||||
_ = m
|
||||
}
|
||||
|
||||
// Ensure we can locate variables in initializer expressions.
|
||||
var global = /*@MakeMap*/ (make(map[string]string))
|
||||
|
Loading…
Reference in New Issue
Block a user