mirror of https://github.com/golang/go.git
go/ssa: emit field and index lvals on demand
Adds a new lazyAddress construct. This is the same as an *address except it emits a FieldAddr selection, Field selection, or IndexAddr on demand. This fixes issues with ordering on assignment statements. For example, x.f = e panics on x being nil in phase 2 of assignment statements. This change delays the introduction of the FieldAddr for x.f until it is used instead of as a side effect of (*builder).addr. The nil deref panic is from FieldAddr is now after side-effects of evaluating x and e but before the assignment to x.f. Fixes golang/go#55086 Change-Id: I0f215b209de5c5fd319aef3af677e071dbd168f8 Reviewed-on: https://go-review.googlesource.com/c/tools/+/442655 Reviewed-by: Alan Donovan <adonovan@google.com> Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
This commit is contained in:
parent
8166dca1ce
commit
1928cea0f0
|
|
@ -33,42 +33,42 @@ func Baz(f func()) {
|
|||
// t2 = *t1
|
||||
// *t2 = Baz$1
|
||||
// t3 = local A (a)
|
||||
// t4 = &t3.foo [#0]
|
||||
// t5 = *t1
|
||||
// t6 = *t5
|
||||
// *t4 = t6
|
||||
// t4 = *t1
|
||||
// t5 = *t4
|
||||
// t6 = &t3.foo [#0]
|
||||
// *t6 = t5
|
||||
// t7 = &t3.foo [#0]
|
||||
// t8 = *t7
|
||||
// t9 = t8()
|
||||
// t10 = &t3.do [#1] *Doer
|
||||
// t11 = &t3.foo [#0] *func()
|
||||
// t12 = *t11 func()
|
||||
// t13 = changetype Doer <- func() (t12) Doer
|
||||
// *t10 = t13
|
||||
// t10 = &t3.foo [#0] *func()
|
||||
// t11 = *t10 func()
|
||||
// t12 = &t3.do [#1] *Doer
|
||||
// t13 = changetype Doer <- func() (t11) Doer
|
||||
// *t12 = t13
|
||||
// t14 = &t3.do [#1] *Doer
|
||||
// t15 = *t14 Doer
|
||||
// t16 = t15() ()
|
||||
|
||||
// Flow chain showing that Baz$1 reaches t8():
|
||||
// Baz$1 -> t2 <-> PtrFunction(func()) <-> t5 -> t6 -> t4 <-> Field(testdata.A:foo) <-> t7 -> t8
|
||||
// Baz$1 -> t2 <-> PtrFunction(func()) <-> t4 -> t5 -> t6 <-> Field(testdata.A:foo) <-> t7 -> t8
|
||||
// Flow chain showing that Baz$1 reaches t15():
|
||||
// Field(testdata.A:foo) <-> t11 -> t12 -> t13 -> t10 <-> Field(testdata.A:do) <-> t14 -> t15
|
||||
// Field(testdata.A:foo) <-> t10 -> t11 -> t13 -> t12 <-> Field(testdata.A:do) <-> t14 -> t15
|
||||
|
||||
// WANT:
|
||||
// Local(f) -> Local(t0)
|
||||
// Local(t0) -> PtrFunction(func())
|
||||
// Function(Baz$1) -> Local(t2)
|
||||
// PtrFunction(func()) -> Local(t0), Local(t2), Local(t5)
|
||||
// PtrFunction(func()) -> Local(t0), Local(t2), Local(t4)
|
||||
// Local(t2) -> PtrFunction(func())
|
||||
// Local(t4) -> Field(testdata.A:foo)
|
||||
// Local(t5) -> Local(t6), PtrFunction(func())
|
||||
// Local(t6) -> Local(t4)
|
||||
// Local(t6) -> Field(testdata.A:foo)
|
||||
// Local(t4) -> Local(t5), PtrFunction(func())
|
||||
// Local(t5) -> Local(t6)
|
||||
// Local(t7) -> Field(testdata.A:foo), Local(t8)
|
||||
// Field(testdata.A:foo) -> Local(t11), Local(t4), Local(t7)
|
||||
// Local(t4) -> Field(testdata.A:foo)
|
||||
// Field(testdata.A:do) -> Local(t10), Local(t14)
|
||||
// Local(t10) -> Field(testdata.A:do)
|
||||
// Local(t11) -> Field(testdata.A:foo), Local(t12)
|
||||
// Local(t12) -> Local(t13)
|
||||
// Local(t13) -> Local(t10)
|
||||
// Field(testdata.A:foo) -> Local(t10), Local(t6), Local(t7)
|
||||
// Local(t6) -> Field(testdata.A:foo)
|
||||
// Field(testdata.A:do) -> Local(t12), Local(t14)
|
||||
// Local(t12) -> Field(testdata.A:do)
|
||||
// Local(t10) -> Field(testdata.A:foo), Local(t11)
|
||||
// Local(t11) -> Local(t13)
|
||||
// Local(t13) -> Local(t12)
|
||||
// Local(t14) -> Field(testdata.A:do), Local(t15)
|
||||
|
|
|
|||
|
|
@ -453,12 +453,16 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
|
|||
}
|
||||
wantAddr := true
|
||||
v := b.receiver(fn, e.X, wantAddr, escaping, sel)
|
||||
last := len(sel.index) - 1
|
||||
return &address{
|
||||
addr: emitFieldSelection(fn, v, sel.index[last], true, e.Sel),
|
||||
pos: e.Sel.Pos(),
|
||||
expr: e.Sel,
|
||||
index := sel.index[len(sel.index)-1]
|
||||
fld := typeparams.CoreType(deref(v.Type())).(*types.Struct).Field(index)
|
||||
|
||||
// Due to the two phases of resolving AssignStmt, a panic from x.f = p()
|
||||
// when x is nil is required to come after the side-effects of
|
||||
// evaluating x and p().
|
||||
emit := func(fn *Function) Value {
|
||||
return emitFieldSelection(fn, v, index, true, e.Sel)
|
||||
}
|
||||
return &lazyAddress{addr: emit, t: fld.Type(), pos: e.Sel.Pos(), expr: e.Sel}
|
||||
|
||||
case *ast.IndexExpr:
|
||||
var x Value
|
||||
|
|
@ -487,13 +491,19 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
|
|||
if isUntyped(index.Type()) {
|
||||
index = emitConv(fn, index, tInt)
|
||||
}
|
||||
v := &IndexAddr{
|
||||
X: x,
|
||||
Index: index,
|
||||
// Due to the two phases of resolving AssignStmt, a panic from x[i] = p()
|
||||
// when x is nil or i is out-of-bounds is required to come after the
|
||||
// side-effects of evaluating x, i and p().
|
||||
emit := func(fn *Function) Value {
|
||||
v := &IndexAddr{
|
||||
X: x,
|
||||
Index: index,
|
||||
}
|
||||
v.setPos(e.Lbrack)
|
||||
v.setType(et)
|
||||
return fn.emit(v)
|
||||
}
|
||||
v.setPos(e.Lbrack)
|
||||
v.setType(et)
|
||||
return &address{addr: fn.emit(v), pos: e.Lbrack, expr: e}
|
||||
return &lazyAddress{addr: emit, t: deref(et), pos: e.Lbrack, expr: e}
|
||||
|
||||
case *ast.StarExpr:
|
||||
return &address{addr: b.expr(fn, e.X), pos: e.Star, expr: e}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ var testdataTests = []string{
|
|||
"width32.go",
|
||||
|
||||
"fixedbugs/issue52342.go",
|
||||
"fixedbugs/issue55086.go",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
func a() (r string) {
|
||||
s := "initial"
|
||||
var p *struct{ i int }
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
|
||||
s, p.i = "set", 2 // s must be set before p.i panics
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func b() (r string) {
|
||||
s := "initial"
|
||||
fn := func() []int { panic("") }
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
|
||||
s, fn()[0] = "set", 2 // fn() panics before any assignment occurs
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func c() (r string) {
|
||||
s := "initial"
|
||||
var p map[int]int
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
|
||||
s, p[0] = "set", 2 //s must be set before p[0] index panics"
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func d() (r string) {
|
||||
s := "initial"
|
||||
var p map[int]int
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
fn := func() int { panic("") }
|
||||
|
||||
s, p[0] = "set", fn() // fn() panics before s is set
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func e() (r string) {
|
||||
s := "initial"
|
||||
p := map[int]int{}
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
fn := func() int { panic("") }
|
||||
|
||||
s, p[fn()] = "set", 0 // fn() panics before any assignment occurs
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func f() (r string) {
|
||||
s := "initial"
|
||||
p := []int{}
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
|
||||
s, p[1] = "set", 0 // p[1] panics after s is set
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func g() (r string) {
|
||||
s := "initial"
|
||||
p := map[any]any{}
|
||||
defer func() {
|
||||
recover()
|
||||
r = s
|
||||
}()
|
||||
var i any = func() {}
|
||||
s, p[i] = "set", 0 // p[i] panics after s is set
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func h() (r string) {
|
||||
fail := false
|
||||
defer func() {
|
||||
recover()
|
||||
if fail {
|
||||
r = "fail"
|
||||
} else {
|
||||
r = "success"
|
||||
}
|
||||
}()
|
||||
|
||||
type T struct{ f int }
|
||||
var p *struct{ *T }
|
||||
|
||||
// The implicit "p.T" operand should be evaluated in phase 1 (and panic),
|
||||
// before the "fail = true" assignment in phase 2.
|
||||
fail, p.f = true, 0
|
||||
return "unreachable"
|
||||
}
|
||||
|
||||
func main() {
|
||||
for _, test := range []struct {
|
||||
fn func() string
|
||||
want string
|
||||
desc string
|
||||
}{
|
||||
{a, "set", "s must be set before p.i panics"},
|
||||
{b, "initial", "p() panics before s is set"},
|
||||
{c, "set", "s must be set before p[0] index panics"},
|
||||
{d, "initial", "fn() panics before s is set"},
|
||||
{e, "initial", "fn() panics before s is set"},
|
||||
{f, "set", "p[1] panics after s is set"},
|
||||
{g, "set", "p[i] panics after s is set"},
|
||||
{h, "success", "p.T panics before fail is set"},
|
||||
} {
|
||||
if test.fn() != test.want {
|
||||
panic(test.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,42 @@ func (e *element) typ() types.Type {
|
|||
return e.t
|
||||
}
|
||||
|
||||
// A lazyAddress is an lvalue whose address is the result of an instruction.
|
||||
// These work like an *address except a new address.address() Value
|
||||
// is created on each load, store and address call.
|
||||
// A lazyAddress can be used to control when a side effect (nil pointer
|
||||
// dereference, index out of bounds) of using a location happens.
|
||||
type lazyAddress struct {
|
||||
addr func(fn *Function) Value // emit to fn the computation of the address
|
||||
t types.Type // type of the location
|
||||
pos token.Pos // source position
|
||||
expr ast.Expr // source syntax of the value (not address) [debug mode]
|
||||
}
|
||||
|
||||
func (l *lazyAddress) load(fn *Function) Value {
|
||||
load := emitLoad(fn, l.addr(fn))
|
||||
load.pos = l.pos
|
||||
return load
|
||||
}
|
||||
|
||||
func (l *lazyAddress) store(fn *Function, v Value) {
|
||||
store := emitStore(fn, l.addr(fn), v, l.pos)
|
||||
if l.expr != nil {
|
||||
// store.Val is v, converted for assignability.
|
||||
emitDebugRef(fn, l.expr, store.Val, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lazyAddress) address(fn *Function) Value {
|
||||
addr := l.addr(fn)
|
||||
if l.expr != nil {
|
||||
emitDebugRef(fn, l.expr, addr, true)
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func (l *lazyAddress) typ() types.Type { return l.t }
|
||||
|
||||
// A blank is a dummy variable whose name is "_".
|
||||
// It is not reified: loads are illegal and stores are ignored.
|
||||
type blank struct{}
|
||||
|
|
|
|||
Loading…
Reference in New Issue