1
0
mirror of https://github.com/golang/go synced 2024-11-05 16:56:16 -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:
Alan Donovan 2013-10-24 18:31:50 -04:00
parent 69f5b543df
commit 9f640c2abb
12 changed files with 195 additions and 92 deletions

View File

@ -302,25 +302,27 @@ func findInterestingNode(pkginfo *importer.PackageInfo, path []ast.Node) ([]ast.
// ---- VALUE ------------------------------------------------------------ // ---- VALUE ------------------------------------------------------------
// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path // 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 // to the root of the AST is path. isAddr reports whether the
// an error to indicate the pointer analysis is not appropriate. // 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 { if obj, ok := obj.(*types.Var); ok {
pkg := prog.Package(qinfo.Pkg) pkg := prog.Package(qinfo.Pkg)
pkg.Build() 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. // Don't run pointer analysis on a ref to a const expression.
if _, ok := v.(*ssa.Const); ok { 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. // Don't run pointer analysis on const/func objects.
return nil, nil return
} }
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident // 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 // return a nil Value without an error to indicate the pointer
// analysis is not appropriate. // 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 := prog.Package(qinfo.Pkg)
pkg.SetDebugMode(true) pkg.SetDebugMode(true)
pkg.Build() pkg.Build()
fn := ssa.EnclosingFunction(pkg, path) fn := ssa.EnclosingFunction(pkg, path)
if fn == nil { 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 { if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil {
return 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) { 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) { if pointer.CanPoint(typ) {
// Determine the ssa.Value for the expression. // Determine the ssa.Value for the expression.
var value ssa.Value var value ssa.Value
var isAddr bool
if obj != nil { if obj != nil {
// def/ref of func/var/const object // 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 { } else {
// any other expression // any other expression
if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant? 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 { if value != nil {
// TODO(adonovan): IsIdentical may be too strict; ptrs, ptaErr = describePointer(o, value, isAddr)
// 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)
} }
} }
@ -478,13 +476,6 @@ func (r *describeValueResult) display(printf printfFunc) {
prefix = "method " 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. // Describe the expression.

View File

@ -42,7 +42,7 @@
"referrers": { "referrers": {
"pos": "testdata/src/main/referrers-json.go:20:10", "pos": "testdata/src/main/referrers-json.go:20:10",
"objpos": "testdata/src/main/referrers-json.go:10:2", "objpos": "testdata/src/main/referrers-json.go:10:2",
"desc": "var f int", "desc": "field f int",
"refs": [ "refs": [
"testdata/src/main/referrers-json.go:20:10", "testdata/src/main/referrers-json.go:20:10",
"testdata/src/main/referrers-json.go:23:5" "testdata/src/main/referrers-json.go:23:5"

View File

@ -495,7 +495,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
e = unparen(e) e = unparen(e)
v := b.expr0(fn, e) v := b.expr0(fn, e)
if fn.debugInfo() { if fn.debugInfo() {
emitDebugRef(fn, e, v) emitDebugRef(fn, e, v, false)
} }
return v return v
} }
@ -686,7 +686,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr) Value {
v := b.expr(fn, e.X) v := b.expr(fn, e.X)
v = emitImplicitSelections(fn, v, indices[:last]) v = emitImplicitSelections(fn, v, indices[:last])
v = emitFieldSelection(fn, v, indices[last], false, e.Sel.Pos()) v = emitFieldSelection(fn, v, indices[last], false, e.Sel.Pos())
emitDebugRef(fn, e.Sel, v) emitDebugRef(fn, e.Sel, v, false)
return v return v
} }
@ -1091,7 +1091,7 @@ func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) {
if !isBlankIdent(id) { if !isBlankIdent(id) {
lhs := fn.addLocalForIdent(id) lhs := fn.addLocalForIdent(id)
if fn.debugInfo() { 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 case *ast.ExprStmt: // <-ch
if debugInfo { if debugInfo {
v := emitExtract(fn, sel, r, vars[r].Type()) 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++ 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 x := b.addr(fn, comm.Lhs[0], false) // non-escaping
v := emitExtract(fn, sel, r, vars[r].Type()) v := emitExtract(fn, sel, r, vars[r].Type())
if debugInfo { if debugInfo {
emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v) emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false)
} }
x.store(fn, v) x.store(fn, v)

View File

@ -37,7 +37,7 @@ func emitLoad(f *Function, addr Value) *UnOp {
// emitDebugRef emits to f a DebugRef pseudo-instruction associating // emitDebugRef emits to f a DebugRef pseudo-instruction associating
// expression e with value v. // 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() { if !f.debugInfo() {
return // debugging not enabled return // debugging not enabled
} }
@ -50,13 +50,15 @@ func emitDebugRef(f *Function, e ast.Expr, v Value) {
return return
} }
obj = f.Pkg.objectOf(id) obj = f.Pkg.objectOf(id)
if _, ok := obj.(*types.Const); ok { switch obj.(type) {
case *types.Nil, *types.Const, *types.Builtin:
return return
} }
} }
f.emit(&DebugRef{ f.emit(&DebugRef{
X: v, X: v,
Expr: unparen(e), Expr: unparen(e),
IsAddr: isAddr,
object: obj, object: obj,
}) })
} }

View File

@ -257,7 +257,7 @@ func lift(fn *Function) {
// Remove any fn.Locals that were lifted. // Remove any fn.Locals that were lifted.
j := 0 j := 0
for _, l := range fn.Locals { for _, l := range fn.Locals {
if l.index == -1 { if l.index < 0 {
fn.Locals[j] = l fn.Locals[j] = l
j++ j++
} }
@ -333,7 +333,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
for _, instr := range *alloc.Referrers() { for _, instr := range *alloc.Referrers() {
// Bail out if we discover the alloc is not liftable; // Bail out if we discover the alloc is not liftable;
// the only operations permitted to use the alloc are // 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) { switch instr := instr.(type) {
case *Store: case *Store:
if instr.Val == alloc { if instr.Val == alloc {
@ -350,6 +350,8 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
if instr.X != alloc { if instr.X != alloc {
panic("Alloc.Referrers is inconsistent") panic("Alloc.Referrers is inconsistent")
} }
case *DebugRef:
// ok
default: default:
return false // some other instruction return false // some other instruction
} }
@ -464,10 +466,9 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
// Rename loads and stores of allocs. // Rename loads and stores of allocs.
for i, instr := range u.Instrs { for i, instr := range u.Instrs {
_ = i
switch instr := instr.(type) { switch instr := instr.(type) {
case *Store: 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. // Delete the Store.
u.Instrs[i] = nil u.Instrs[i] = nil
u.gaps++ u.gaps++
@ -480,7 +481,7 @@ func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
} }
case *UnOp: case *UnOp:
if instr.Op == token.MUL { 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) newval := renamed(renaming, alloc)
if debugLifting { if debugLifting {
fmt.Fprintln(os.Stderr, "Replace refs to load", instr.Name(), "=", instr, "with", newval.Name()) 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++ 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++
}
}
} }
} }

View File

@ -42,15 +42,14 @@ func (a *address) store(fn *Function, v Value) {
store := emitStore(fn, a.addr, v) store := emitStore(fn, a.addr, v)
store.pos = a.starPos store.pos = a.starPos
if a.expr != nil { if a.expr != nil {
// store.Val is v converted for assignability. // store.Val is v, converted for assignability.
emitDebugRef(fn, a.expr, store.Val) emitDebugRef(fn, a.expr, store.Val, false)
} }
} }
func (a *address) address(fn *Function) Value { func (a *address) address(fn *Function) Value {
if a.expr != nil { if a.expr != nil {
// NB: this kind of DebugRef yields the object's address. emitDebugRef(fn, a.expr, a.addr, true)
emitDebugRef(fn, a.expr, a.addr)
} }
return a.addr return a.addr
} }

View File

@ -394,7 +394,11 @@ func (s *DebugRef) String() string {
} else { } else {
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr" 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 { func (p *Package) String() string {

View File

@ -141,30 +141,34 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// - e is a constant expression. (For efficiency, no debug // - e is a constant expression. (For efficiency, no debug
// information is stored for constants. Use // information is stored for constants. Use
// importer.PackageInfo.ValueOf(e) instead.) // importer.PackageInfo.ValueOf(e) instead.)
// - e is a reference to nil or a built-in function.
// - the value was optimised away. // - the value was optimised away.
// //
// The types of e and the result are equal (modulo "untyped" bools // If e is an addressable expression used an an lvalue context,
// resulting from comparisons) and they have equal "pointerness". // 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 // (Tip: to find the ssa.Value given a source position, use
// importer.PathEnclosingInterval to locate the ast.Node, then // importer.PathEnclosingInterval to locate the ast.Node, then
// EnclosingFunction to locate the Function, then ValueForExpr to find // EnclosingFunction to locate the Function, then ValueForExpr to find
// the ssa.Value.) // 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) if f.debugInfo() { // (opt)
e = unparen(e) e = unparen(e)
for _, b := range f.Blocks { for _, b := range f.Blocks {
for _, instr := range b.Instrs { for _, instr := range b.Instrs {
if ref, ok := instr.(*DebugRef); ok { if ref, ok := instr.(*DebugRef); ok {
if ref.Expr == e { if ref.Expr == e {
return ref.X return ref.X, ref.IsAddr
} }
} }
} }
} }
} }
return nil return
} }
// --- Lookup functions for source-level named entities (types.Objects) --- // --- 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. // and that ident must resolve to obj.
// //
// pkg is the package enclosing the reference. (A reference to a var // 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 // 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.y = 0
// x[0] = 0 // x[0] = 0
// _ = x[:] // _ = x[:] (where x is an array)
// x = X{}
// _ = &x // _ = &x
// x.method() (iff method is on &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 { func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
id := ref[0].(*ast.Ident) // All references to a var are local to some function, possibly init.
// 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)
fn := EnclosingFunction(pkg, ref) fn := EnclosingFunction(pkg, ref)
if fn == nil { 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? // Defining ident of a parameter?
if id.Pos() == obj.Pos() { if id.Pos() == obj.Pos() {
for _, param := range fn.Params { for _, param := range fn.Params {
if param.Object() == obj { 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? // Other ident?
for _, b := range fn.Blocks { for _, b := range fn.Blocks {
for _, instr := range b.Instrs { for _, instr := range b.Instrs {
if ref, ok := instr.(*DebugRef); ok { if dr, ok := instr.(*DebugRef); ok {
if ref.Pos() == id.Pos() { if dr.Pos() == id.Pos() {
return ref.X 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
} }

View File

@ -84,6 +84,9 @@ func TestObjValueLookup(t *testing.T) {
// Check invariants for var objects. // Check invariants for var objects.
// The result varies based on the specific Ident. // The result varies based on the specific Ident.
for _, id := range ids { for _, id := range ids {
if id.Name == "_" {
continue
}
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok { if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos()) ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := imp.Fset.Position(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)", prefix := fmt.Sprintf("VarValue(%s @ L%d)",
obj, prog.Fset.Position(ref[0].Pos()).Line) 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. // Kind is the concrete type of the ssa Value.
gotKind := "nil" 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."):] 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. // Check the kinds match.
// "nil" indicates expected failure (e.g. optimized away). // "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() expType := obj.Type()
if wantAddr { if wantAddr {
expType = types.NewPointer(expType) 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) { if !types.IsIdentical(v.Type(), expType) {
t.Errorf("%s.Type() == %s, want %s", prefix, 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.SetDebugMode(true)
mainPkg.Build() mainPkg.Build()
fn := mainPkg.Func("f")
if false { 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. // Find the actual AST node for each canonical position.
@ -229,14 +240,30 @@ func TestValueForExpr(t *testing.T) {
e = target.X 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.") got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
if want := text; got != want { if want := text; got != want {
t.Errorf("%s: got value %q, want %q", position, got, want) t.Errorf("%s: got value %q, want %q", position, got, want)
} }
if v != nil { if v != nil {
if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) { T := v.Type()
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), 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)
} }
} }
} }

View File

@ -1171,8 +1171,9 @@ type MapUpdate struct {
type DebugRef struct { type DebugRef struct {
anInstruction anInstruction
X Value // the value whose position we're declaring 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 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. ----------- // Embeddable mix-ins and helpers for common parts of other structs. -----------

View File

@ -37,6 +37,9 @@ type S struct {
} }
func main() { func main() {
print(globalVar) // globalVar::UnOp
globalVar = 1 // globalVar::Const
var v0 int = 1 // v0::Const (simple local value spec) var v0 int = 1 // v0::Const (simple local value spec)
if v0 > 0 { // v0::Const if v0 > 0 { // v0::Const
v0 = 2 // v0::Const v0 = 2 // v0::Const
@ -74,11 +77,11 @@ func main() {
print(v5) // v5::Const print(v5) // v5::Const
print(v6) // v6::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 v7.x = 1 // &v7::Alloc x::Const
print(v7.x) // v7::UnOp x::Field 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 v8[0] = 0 // &v8::Alloc
print(v8[:]) // &v8::Alloc print(v8[:]) // &v8::Alloc
_ = v8[0] // v8::UnOp (load from Alloc) _ = v8[0] // v8::UnOp (load from Alloc)
@ -87,6 +90,10 @@ func main() {
_ = v8ptr[0] // v8ptr::Alloc _ = v8ptr[0] // v8ptr::Alloc
_ = *v8ptr // v8ptr::Alloc _ = *v8ptr // v8ptr::Alloc
v8a := make([]int, 1) // v8a::MakeSlice
v8a[0] = 0 // v8a::MakeSlice
print(v8a[:]) // v8a::MakeSlice
v9 := S{} // &v9::Alloc v9 := S{} // &v9::Alloc
v10 := &v9 // v10::Alloc &v9::Alloc v10 := &v9 // v10::Alloc &v9::Alloc
@ -95,7 +102,7 @@ func main() {
var v11 *J = nil // v11::Const var v11 *J = nil // v11::Const
v11.method() // 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) v12.method() // &v12::Alloc (implicitly address-taken)
// NB, in the following, 'method' resolves to the *types.Func // NB, in the following, 'method' resolves to the *types.Func
@ -137,7 +144,17 @@ func main() {
} }
// .Op is an inter-package FieldVal-selection. // .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::UnOp Op::Field
_ = &err.Op // &err::Alloc &Op::FieldAddr _ = &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
} }

View File

@ -7,7 +7,9 @@ package main
// annotation, when passed to Function.ValueForExpr(e), returns a // annotation, when passed to Function.ValueForExpr(e), returns a
// non-nil Value of the same type as e and of kind 'kind'. // 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) _ = /*@<nil>*/ (1 + 2) // (constant)
i := 0 i := 0
/*@Call*/ (print( /*@BinOp*/ (i + 1))) /*@Call*/ (print( /*@BinOp*/ (i + 1)))
@ -33,11 +35,13 @@ func f(param int) {
_ = map1 _ = map1
_ = /*@MakeMap*/ (map[string]string{"": ""}) _ = /*@MakeMap*/ (map[string]string{"": ""})
_ = /*@MakeSlice*/ (make([]int, 0)) _ = /*@MakeSlice*/ (make([]int, 0))
_ = /*@MakeClosure*/ (func() { print(param) }) _ = /*@MakeClosure*/ (func() { print(spilled) })
sl := /*@Slice*/ ([]int{}) sl := /*@Slice*/ ([]int{})
_ = /*@Alloc*/ (&struct{}{}) _ = /*@Alloc*/ (&struct{}{})
_ = /*@Slice*/ (sl[:0]) _ = /*@Slice*/ (sl[:0])
_ = /*@Alloc*/ (new(int)) _ = /*@<nil>*/ (new(int)) // optimized away
tmp := /*@Alloc*/ (new(int))
_ = tmp
var iface interface{} var iface interface{}
_ = /*@TypeAssert*/ (iface.(int)) _ = /*@TypeAssert*/ (iface.(int))
_ = /*@UnOp*/ (sl[0]) _ = /*@UnOp*/ (sl[0])
@ -45,4 +49,35 @@ func f(param int) {
_ = /*@Index*/ ([2]int{}[0]) _ = /*@Index*/ ([2]int{}[0])
var p *int var p *int
_ = /*@UnOp*/ (*p) _ = /*@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))