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 ------------------------------------------------------------
// 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.

View File

@ -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"

View File

@ -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)

View File

@ -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,
})
}

View File

@ -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++
}
}
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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. -----------

View File

@ -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
}

View File

@ -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))