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:
parent
69f5b543df
commit
9f640c2abb
@ -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.
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
26
ssa/lift.go
26
ssa/lift.go
@ -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++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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. -----------
|
||||||
|
25
ssa/testdata/objlookup.go
vendored
25
ssa/testdata/objlookup.go
vendored
@ -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
|
||||||
}
|
}
|
||||||
|
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
|
// 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))
|
||||||
|
Loading…
Reference in New Issue
Block a user