目前,至少有两种相当临时的模式被转义分析识别为安全。当它看到不引入需要跟踪的新指针的自赋值语句时,它会跳过详细的分析。最初,只有一些简单的自切片赋值(如上所示)被处理:
x.buf = x.buf[a:b]
后来,添加了一些更多的模式,所以它们也被识别了:
x.ptr1 = x.ptr2
val.pointers[i] = val.pointers[j]
它们的问题在于它们非常脆弱,无法匹配与最简单情况不同的表达式,但仍然表示自赋值:
x.a.b.buf = x.a.b.buf[a:b]
可以通过“基本对象”的概念将所有自赋值模式进行泛化。我们上面看到的是对象字段赋值与对象本身的匹配。基本对象就是那个包含引用字段的对象。对于最简单的情况,基本对象只是“上面”的一个语法级别:
base(x.y) // => x
base(x.y.z) // => x.y
base(x[i]) // => x
base(x[i][j]) // => x[i]
对于表达式实际上返回相同对象的情况,我们可以跳过几个级别:
base(x[:]) => x
base(x[:][:]) => x
给定 base
函数,我们可以将自赋值表示为(伪Go):
// See samesafeexpr from cmd/compile/internal/gc/typecheck.go
return samesafeexpr(base(dst), base(src))
这涵盖了上述所有模式,再加上一些更多。作为奖励,它还解决了简单的 *p = *p
情况(参见 #5919 ):
func j(p *string) {
*p = *p
}
在泛化之前未被识别的更有趣的自赋值示例:
type node struct { next *node }
func createLoop(n *node) {
n.next = n // n was leaking param before
}
type foo struct {
pi *int
i int
}
func (f *foo) example() {
f.pi = &f.i // f was leaking param before
}
这个解决方案(如果正确或可以使其正确):
- 使自赋值检查变得更加临时、强大(希望更有用)
- 使实现变得更简单,减少代码重复
收集新的转义分析结果信息:
go build -a -gcflags=-m ./src/... 2>&1 | grep 'does not escape' > new_noescapes.txt
go build -a -gcflags=-m ./src/... 2>&1 | grep 'leaking param' > new_leakparam.txt
现在使用未修补的 go tool compile
:
go build -a -gcflags=-m ./src/... 2>&1 | grep 'does not escape' > old_noescapes.txt
go build -a -gcflags=-m ./src/... 2>&1 | grep 'leaking param' > old_leakparam.txt
现在可以进行一些比较。
新的非转义值/参数:
src/net/net.go:687:7: (*Buffers).consume v does not escape
src/net/net.go:674:7: (*Buffers).Read v does not escape
src/internal/poll/fd.go:48:14: consume v does not escape
src/compress/flate/inflate.go:325:10: (*decompressor).nextBlock &f.h1 does not escape
src/compress/flate/inflate.go:326:10: (*decompressor).nextBlock &f.h2 does not escape
src/cmd/compile/internal/gc/const.go:668:15: evconst &nl does not escape
src/container/ring/ring.go:121:7: (*Ring).Len r does not escape
src/cmd/compile/internal/types/type.go:727:13: (*Type).copy &nt does not escape
由于 ring.Ring.Len
接收器不再转义,我们可以尝试使用基准测试验证和测量它:
package foo
import (
"container/ring"
"testing"
)
func BenchmarkRingLen(b *testing.B) {
for i := 0; i < b.N; i++ {
var r ring.Ring
_ = r.Len()
}
}
旧的是具有泄漏的 r
的未修补转义分析。新的是非泄漏的 r
:
name old time/op new time/op delta
RingLen-8 35.6ns ± 6% 3.0ns ± 0% -91.46% (p=0.000 n=10+10)
name old alloc/op new alloc/op delta
RingLen-8 32.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10)
name old allocs/op new allocs/op delta
RingLen-8 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10)
这里是 ring.Ring.Len
方法实现,供参考:https://golang.org/src/container/ring/ring.go?s=2869:2893#L111
有关其他示例,请参阅建议的测试套件扩展:
package foo
var sink interface{}
func length(xs []byte) int { // ERROR "leaking param: xs"
sink = xs // ERROR "xs escapes to heap"
return len(xs)
}
func zero() int { return 0 }
type buffer struct {
arr [64]byte
buf1 []byte
buf2 []byte
bufPtr1 *[]byte
bufPtr2 *[]byte
bufList [][]byte
bufArr [5][]byte
bufPtrList []*[]byte
str1 string
str2 string
next *buffer
buffers []*buffer
}
func (b *buffer) getNext() *buffer { // ERROR "leaking param: b to result ~r0 level=1"
return b.next
}
// When referring to the "old" implementation, cases that are covered in
// escape2.go are implied.
// Most tests here are based on those tests, but with slight changes,
// like extra selector expression level.
func (b *buffer) noEsc() { // ERROR "\(\*buffer\).noEsc b does not escape"
// Like original slicing self-assignment test, but with additional
// slicing expressions inside the RHS.
b.buf1 = b.buf1[:][1:2] // ERROR "ignoring self-assignment to b.buf1$"
b.buf1 = b.buf1[1:][1:2:3] // ERROR "ignoring self-assignment to b.buf1$"
b.buf1 = b.buf2[:2][1:][1:2] // ERROR "ignoring self-assignment to b.buf1$"
b.buf1 = b.buf2[:][1:2][1:2:3] // ERROR "ignoring self-assignment to b.buf1$"
// The "left" (base) part is generalized and can be arbitrary,
// as long as it doesn't affect memory.
// Basically, these cases add "next" in the buf accessing chain.
b.next.buf1 = b.next.buf1[1:2] // ERROR "ignoring self-assignment to b.next.buf1$"
b.next.buf1 = b.next.buf1[1:2:3] // ERROR "ignoring self-assignment to b.next.buf1$"
b.next.buf1 = b.next.buf2[1:2] // ERROR "ignoring self-assignment to b.next.buf1$"
b.next.next.buf1 = b.next.next.buf2[1:2:3] // ERROR "ignoring self-assignment to b.next.next.buf1$"
// Indexing functionally is almost identical to field accessing.
// It's permitted to have different trailing indexes just as it's
// permitted to have different trailing selectors.
index1 := 10
index2 := index1
b.bufList[0] = b.bufList[1][1:2] // ERROR "ignoring self-assignment to b.bufList\[0\]$"
b.bufList[index1] = b.bufList[index2][1:2:3] // ERROR "ignoring self-assignment to b.bufList\[index1]$"
b.bufArr[0] = b.bufArr[1+1][1:2] // ERROR "ignoring self-assignment to b.bufArr\[0]$"
*b.bufPtrList[2] = (*b.bufPtrList[1])[1:2:3] // ERROR "ignoring self-assignment to \*b.bufPtrList\[2\]$"
// Same indexes should work as well.
b.bufList[0] = b.bufList[0][1:2] // ERROR "ignoring self-assignment to b.bufList\[0\]$"
b.bufList[index1] = b.bufList[index1][1:2:3] // ERROR "ignoring self-assignment to b.bufList\[index1]$"
b.bufArr[1+1] = b.bufArr[1+1][1:2] // ERROR "ignoring self-assignment to b.bufArr\[1 \+ 1\]$"
*b.bufPtrList[2] = (*b.bufPtrList[2])[1:2:3] // ERROR "ignoring self-assignment to \*b.bufPtrList\[2\]$"
// Works for chained indexing as well, but indexing in base objects must match.
b.buffers[0].bufList[0] = b.buffers[0].bufList[0][1:2] // ERROR "ignoring self-assignment to b.buffers\[0\].bufList\[0\]"
b.buffers[index1+1].bufList[index1] = b.buffers[index1+1].bufList[index1][1:2:3] // ERROR "ignoring self-assignment to b.buffers\[index1\ \+ 1].bufList\[index1\]"
b.buffers[1+1].bufArr[1+1] = b.buffers[1+1].bufArr[1+1][1:2] // ERROR "ignoring self-assignment to b.buffers\[1 \+ 1\].bufArr\[1 \+ 1\]"
*b.buffers[1].bufPtrList[2] = (*b.buffers[1].bufPtrList[2])[1:2:3] // ERROR "ignoring self-assignment to \*b.buffers\[1\].bufPtrList\[2\]"
}
func (b *buffer) esc() { // ERROR "leaking param content: b"
// None of the cases below should trigger self-assignment optimization.
// These slice expressions contain sub-exprs that may affect memory.
b.buf1 = b.buf1[:length(b.buf1)][1:2]
b.buf1 = b.buf1[1:length(b.buf1)][1:2:3]
b.buf1 = b.buf2[:][zero():length(b.buf2)][1:2]
b.buf1 = b.buf2[zero()+1:][:][1:2:3]
// Due to method call inside the chain, these should not be optimized.
// The baseObject(x) returns b.getNext() node for both sides,
// but samesafeexpr would not consider them as "same".
b.getNext().buf1 = b.getNext().buf1[1:2]
b.getNext().buf1 = b.getNext().buf1[1:2:3]
b.getNext().buf1 = b.getNext().buf2[1:2]
b.getNext().buf1 = b.getNext().buf2[1:2:3]
// Different base objects.
b.next.next.buf1 = b.next.buf1[1:2]
b.next.next.buf1 = b.next.buf1[1:2:3]
b.next.buf1 = b.next.next.buf2[1:2]
b.next.buf1 = b.next.next.buf2[1:2:3]
b.bufList[0] = b.bufArr[0][1:2]
// Different indexes are not permitted inside base objects.
index1 := 10
index2 := index1
b.buffers[0].bufList[0] = b.buffers[1].bufList[0][1:2]
b.buffers[index1].bufList[index1] = b.buffers[index2].bufList[index1][1:2:3]
b.buffers[1+1].bufArr[1+1] = b.buffers[1+0].bufArr[1+1][1:2]
*b.buffers[0].bufPtrList[2] = (*b.buffers[1].bufPtrList[2])[1:2:3]
b.buffers[index1+1].bufList[index1] = b.buffers[index1+2].bufList[index1][1:2:3]
}
func (b *buffer) sanity1() { // ERROR "leaking param content: b"
b.next.buf1 = b.next.buf2[:] // ERROR "ignoring self-assignment to b.next.buf1"
sink = b.next.buf1 // ERROR "b.next.buf1 escapes to heap"
sink = b.next.buf2 // ERROR "b.next.buf2 escapes to heap"
}
func (b *buffer) sanity2() { // ERROR "b does not escape"
b.bufList = b.bufList[:len(b.bufList)-1] // ERROR "ignoring self-assignment to b.bufList"
}
4条答案
按热度按时间a7qyws3x1#
https://golang.org/cl/136496提到了这个问题:
cmd/compile/internal/gc: generalize self-assignment
oiopk7p52#
/cc @randall77@dr2chase
wixjitnu3#
我稍微担心x是一个切片或者它的元素是切片的情况。
我认为切片解引用计数被视为间接访问,而数组解引用则不是。
在esc.go中搜索
IsArray
,我认为你会看到这种区别。向前迈出最安全的一步可能是在所有索引情况上都加上,看看只使用点号得到的结果,然后在一个单独的CL中将数组(而不是切片!)添加到基本测试中,以防有细微的错误。
ODOT是不同的,因为我认为在那里我们会在有显式解引用时重写为ODOTPTR。
vwhgwdsa4#
https://golang.org/cl/172421提到了这个问题:
test: add regress test cases for self-assignment