From 9f640c2abb1cb665e9227930030021043dd2e30c Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 24 Oct 2013 18:31:50 -0400 Subject: [PATCH] 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 --- oracle/describe.go | 47 +++++------- .../testdata/src/main/referrers-json.golden | 2 +- ssa/builder.go | 10 +-- ssa/emit.go | 6 +- ssa/lift.go | 26 +++++-- ssa/lvalue.go | 7 +- ssa/print.go | 6 +- ssa/source.go | 71 +++++++++++-------- ssa/source_test.go | 43 ++++++++--- ssa/ssa.go | 3 +- ssa/testdata/objlookup.go | 25 +++++-- ssa/testdata/valueforexpr.go | 41 ++++++++++- 12 files changed, 195 insertions(+), 92 deletions(-) diff --git a/oracle/describe.go b/oracle/describe.go index 23da217509..3426dcea8e 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -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. diff --git a/oracle/testdata/src/main/referrers-json.golden b/oracle/testdata/src/main/referrers-json.golden index d31c19f829..ad7ec1d370 100644 --- a/oracle/testdata/src/main/referrers-json.golden +++ b/oracle/testdata/src/main/referrers-json.golden @@ -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" diff --git a/ssa/builder.go b/ssa/builder.go index a11246afad..8675fb611b 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -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) diff --git a/ssa/emit.go b/ssa/emit.go index 682f06af34..983115606a 100644 --- a/ssa/emit.go +++ b/ssa/emit.go @@ -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, }) } diff --git a/ssa/lift.go b/ssa/lift.go index 2fe456470c..18f28878c0 100644 --- a/ssa/lift.go +++ b/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++ + } + } } } diff --git a/ssa/lvalue.go b/ssa/lvalue.go index 3ed7642a8d..2e8eb9d022 100644 --- a/ssa/lvalue.go +++ b/ssa/lvalue.go @@ -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 } diff --git a/ssa/print.go b/ssa/print.go index 49b5d5d312..f0926c2b1f 100644 --- a/ssa/print.go +++ b/ssa/print.go @@ -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 { diff --git a/ssa/source.go b/ssa/source.go index fd6907220a..726d80ece4 100644 --- a/ssa/source.go +++ b/ssa/source.go @@ -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 } diff --git a/ssa/source_test.go b/ssa/source_test.go index 3c123c2096..268463aeff 100644 --- a/ssa/source_test.go +++ b/ssa/source_test.go @@ -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) } } } diff --git a/ssa/ssa.go b/ssa/ssa.go index 539128bbd8..e3624d4b75 100644 --- a/ssa/ssa.go +++ b/ssa/ssa.go @@ -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. ----------- diff --git a/ssa/testdata/objlookup.go b/ssa/testdata/objlookup.go index 249e994716..f747a2aa43 100644 --- a/ssa/testdata/objlookup.go +++ b/ssa/testdata/objlookup.go @@ -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 } diff --git a/ssa/testdata/valueforexpr.go b/ssa/testdata/valueforexpr.go index 140447efae..2f719b96c2 100644 --- a/ssa/testdata/valueforexpr.go +++ b/ssa/testdata/valueforexpr.go @@ -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) _ = /*@*/ (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)) + _ = /*@*/ (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))