Files
go/test/escape_closure.go
Jake Bailey 5af294bac7 cmd/compile: handle multiply-assigned func vars in escape analysis
Generalize FuncSingleAssignment to FuncAssignments, tracking all
non-zero assignments to func-typed variables. When multiple
callees are known, escape analysis merges their parameter escape
behaviors via teeHole.

compilebench results (this CL vs parent):

                         │  parent.txt  │             commit.txt             │
                         │    sec/op    │    sec/op     vs base              │
Template                   150.8m ± 12%   149.6m ± 12%       ~ (p=0.589 n=6)
Unicode                    120.2m ± 11%   117.2m ±  5%       ~ (p=0.699 n=6)
GoTypes                    890.6m ±  3%   884.8m ±  2%       ~ (p=0.310 n=6)
Compiler                   161.2m ± 13%   154.4m ±  4%       ~ (p=0.180 n=6)
SSA                         7.023 ±  1%    7.021 ±  1%       ~ (p=0.485 n=6)
Flate                      162.6m ±  6%   162.0m ±  6%       ~ (p=0.699 n=6)
GoParser                   176.9m ±  7%   178.0m ±  6%       ~ (p=0.699 n=6)
Reflect                    393.2m ±  4%   389.5m ±  5%       ~ (p=0.699 n=6)
Tar                        171.5m ± 14%   172.9m ±  5%       ~ (p=0.937 n=6)
XML                        197.1m ±  6%   197.7m ±  2%       ~ (p=0.818 n=6)
LinkCompiler               650.5m ±  4%   637.0m ±  2%       ~ (p=0.394 n=6)
ExternalLinkCompiler        2.196 ±  1%    2.206 ±  1%       ~ (p=0.240 n=6)
LinkWithoutDebugCompiler   421.2m ±  6%   423.4m ±  7%       ~ (p=0.699 n=6)
StdCmd                      29.21 ±  2%    29.27 ±  1%       ~ (p=0.937 n=6)
geomean                    525.6m         522.3m        -0.63%

                         │  parent.txt  │             commit.txt             │
                         │ user-sec/op  │ user-sec/op   vs base              │
Template                   728.1m ±  5%   686.9m ± 11%       ~ (p=0.180 n=6)
Unicode                    158.9m ± 12%   160.9m ±  8%       ~ (p=0.937 n=6)
GoTypes                     4.921 ±  4%    4.919 ±  2%       ~ (p=0.937 n=6)
Compiler                   502.4m ±  9%   505.3m ± 10%       ~ (p=0.818 n=6)
SSA                         37.77 ±  2%    37.06 ±  4%       ~ (p=0.699 n=6)
Flate                      751.3m ± 11%   759.7m ±  8%       ~ (p=0.937 n=6)
GoParser                   677.4m ±  5%   674.4m ±  8%       ~ (p=0.394 n=6)
Reflect                     1.888 ±  2%    1.901 ±  3%       ~ (p=0.589 n=6)
Tar                        772.1m ± 14%   789.7m ±  5%       ~ (p=0.937 n=6)
XML                        911.8m ±  9%   916.0m ± 12%       ~ (p=0.937 n=6)
LinkCompiler                1.077 ±  4%    1.059 ±  5%       ~ (p=0.310 n=6)
ExternalLinkCompiler        2.539 ±  2%    2.531 ±  3%       ~ (p=1.000 n=6)
LinkWithoutDebugCompiler   489.7m ±  4%   485.3m ±  5%       ~ (p=1.000 n=6)
geomean                     1.192          1.187        -0.36%

          │  parent.txt  │              commit.txt              │
          │  text-bytes  │  text-bytes   vs base                │
HelloSize   1.110Mi ± 0%   1.110Mi ± 0%       ~ (p=1.000 n=6) ¹
CmdGoSize   14.14Mi ± 0%   14.14Mi ± 0%       ~ (p=1.000 n=6) ¹
geomean     3.961Mi        3.961Mi       +0.00%
¹ all samples are equal

          │  parent.txt  │              commit.txt              │
          │  data-bytes  │  data-bytes   vs base                │
HelloSize   27.54Ki ± 0%   27.54Ki ± 0%       ~ (p=1.000 n=6) ¹
CmdGoSize   431.4Ki ± 0%   431.4Ki ± 0%       ~ (p=1.000 n=6) ¹
geomean     109.0Ki        109.0Ki       +0.00%
¹ all samples are equal

          │  parent.txt  │              commit.txt              │
          │  bss-bytes   │  bss-bytes    vs base                │
HelloSize   213.9Ki ± 0%   213.9Ki ± 0%       ~ (p=1.000 n=6) ¹
CmdGoSize   32.27Mi ± 0%   32.27Mi ± 0%       ~ (p=1.000 n=6) ¹
geomean     2.597Mi        2.597Mi       +0.00%
¹ all samples are equal

          │  parent.txt  │              commit.txt              │
          │  exe-bytes   │  exe-bytes    vs base                │
HelloSize   1.782Mi ± 0%   1.782Mi ± 0%       ~ (p=1.000 n=6) ¹
CmdGoSize   21.29Mi ± 0%   21.29Mi ± 0%       ~ (p=1.000 n=6) ¹
geomean     6.158Mi        6.158Mi       +0.00%
¹ all samples are equal

Fixes #73132

Change-Id: Ice3ed672db28dbfd4bdc788019111d5d3092c5bb
Reviewed-on: https://go-review.googlesource.com/c/go/+/771500
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
2026-05-20 09:48:01 -07:00

393 lines
11 KiB
Go

// errorcheck -0 -m -l
// Copyright 2015 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.
// Test escape analysis for closure arguments.
package escape
var sink interface{}
func ClosureCallArgs0() {
x := 0
func(p *int) { // ERROR "p does not escape" "func literal does not escape"
*p = 1
}(&x)
}
func ClosureCallArgs1() {
x := 0
for {
func(p *int) { // ERROR "p does not escape" "func literal does not escape"
*p = 1
}(&x)
}
}
func ClosureCallArgs2() {
for {
x := 0
func(p *int) { // ERROR "p does not escape" "func literal does not escape"
*p = 1
}(&x)
}
}
func ClosureCallArgs3() {
x := 0 // ERROR "moved to heap: x"
func(p *int) { // ERROR "leaking param: p" "func literal does not escape"
sink = p
}(&x)
}
func ClosureCallArgs4() {
x := 0
_ = func(p *int) *int { // ERROR "leaking param: p to result ~r0" "func literal does not escape"
return p
}(&x)
}
func ClosureCallArgs5() {
x := 0 // ERROR "moved to heap: x"
// TODO(mdempsky): We get "leaking param: p" here because the new escape analysis pass
// can tell that p flows directly to sink, but it's a little weird. Re-evaluate.
sink = func(p *int) *int { // ERROR "leaking param: p" "func literal does not escape"
return p
}(&x)
}
func ClosureCallArgs6() {
x := 0 // ERROR "moved to heap: x"
func(p *int) { // ERROR "moved to heap: p" "func literal does not escape"
sink = &p
}(&x)
}
func ClosureCallArgs7() {
var pp *int
for {
x := 0 // ERROR "moved to heap: x"
func(p *int) { // ERROR "leaking param: p" "func literal does not escape"
pp = p
}(&x)
}
_ = pp
}
func ClosureCallArgs8() {
x := 0
defer func(p *int) { // ERROR "p does not escape" "func literal does not escape"
*p = 1
}(&x)
}
func ClosureCallArgs9() {
// BAD: x should not leak
x := 0 // ERROR "moved to heap: x"
for {
defer func(p *int) { // ERROR "func literal escapes to heap" "p does not escape"
*p = 1
}(&x)
}
}
func ClosureCallArgs10() {
for {
x := 0 // ERROR "moved to heap: x"
defer func(p *int) { // ERROR "func literal escapes to heap" "p does not escape"
*p = 1
}(&x)
}
}
func ClosureCallArgs11() {
x := 0 // ERROR "moved to heap: x"
defer func(p *int) { // ERROR "leaking param: p" "func literal does not escape"
sink = p
}(&x)
}
func ClosureCallArgs12() {
x := 0
defer func(p *int) *int { // ERROR "leaking param: p to result ~r0" "func literal does not escape"
return p
}(&x)
}
func ClosureCallArgs13() {
x := 0 // ERROR "moved to heap: x"
defer func(p *int) { // ERROR "moved to heap: p" "func literal does not escape"
sink = &p
}(&x)
}
func ClosureCallArgs14() {
x := 0
p := &x
_ = func(p **int) *int { // ERROR "leaking param: p to result ~r0 level=1" "func literal does not escape"
return *p
}(&p)
}
func ClosureCallArgs15() {
x := 0 // ERROR "moved to heap: x"
p := &x
sink = func(p **int) *int { // ERROR "leaking param: p to result ~r0 level=1" "func literal does not escape"
return *p
}(&p)
}
func ClosureLeak1(s string) string { // ERROR "s does not escape"
t := s + "YYYY" // ERROR "escapes to heap"
return ClosureLeak1a(t) // ERROR "... argument does not escape"
}
// See #14409 -- returning part of captured var leaks it.
func ClosureLeak1a(a ...string) string { // ERROR "leaking param: a to result ~r0 level=1$"
return func() string { // ERROR "func literal does not escape"
return a[0]
}()
}
func ClosureLeak2(s string) string { // ERROR "s does not escape"
t := s + "YYYY" // ERROR "escapes to heap"
c := ClosureLeak2a(t) // ERROR "... argument does not escape"
return c
}
func ClosureLeak2a(a ...string) string { // ERROR "leaking param content: a"
return ClosureLeak2b(func() string { // ERROR "func literal does not escape"
return a[0]
})
}
func ClosureLeak2b(f func() string) string { // ERROR "f does not escape"
return f()
}
func ClosureIndirect() {
f := func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
g := f
g(new(int)) // ERROR "new\(int\) does not escape"
h := nopFunc
h(new(int)) // ERROR "new\(int\) does not escape"
}
func nopFunc(p *int) {} // ERROR "p does not escape"
func ClosureIndirect2() {
f := func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
g := f
g(new(int)) // ERROR "new\(int\) does not escape"
h := nopFunc2
h(new(int)) // ERROR "new\(int\) does not escape"
}
func nopFunc2(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0"
func ClosureIndirectDeclAssign() {
var f func(p *int)
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectDeclAssign2() {
var f func(p *int) *int
f = func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectDeclAssign3() {
var f func(p *int)
f = func(p *int) { // ERROR "leaking param: p" "func literal does not escape"
sink = p
}
f(new(int)) // ERROR "new\(int\) escapes to heap"
}
func ClosureIndirectDeclReassign() {
var f func(p *int)
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f = func(p *int) { // ERROR "leaking param: p" "func literal does not escape"
sink = p
}
f(new(int)) // ERROR "new\(int\) escapes to heap"
}
func ClosureIndirectDeclRecursive() {
var visit func(p *int)
visit = func(p *int) { // ERROR "p does not escape" "func literal does not escape"
visit(p)
}
visit(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectNilInit() {
var f func(p *int) = nil
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectTypedNilInit() {
var f func(p *int) = (func(*int))(nil)
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectNestedAssign() {
var f func(p *int)
func() { // ERROR "func literal does not escape"
f = func(p *int) {} // ERROR "p does not escape" "func literal escapes to heap"
}()
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectNestedAssignLeak() {
var f func(p *int)
func() { // ERROR "func literal does not escape"
f = func(p *int) { // ERROR "leaking param: p" "func literal escapes to heap"
sink = p
}
}()
f(new(int)) // ERROR "new\(int\) escapes to heap"
}
func ClosureIndirectTypeSwitch() {
foo := any(func(a *int) { sink = a }) // ERROR "func literal does not escape" "leaking param: a"
switch foo := foo.(type) {
case func(a *int):
foo(new(int)) // ERROR "new\(int\) escapes to heap"
}
}
func ClosureIndirectTypeSwitchReassign() {
foo := any(func(a *int) { sink = a }) // ERROR "func literal does not escape" "leaking param: a"
switch foo := foo.(type) {
case func(a *int):
foo = func(a *int) {} // ERROR "func literal does not escape" "a does not escape"
foo(new(int)) // ERROR "new\(int\) escapes to heap"
}
}
func ClosureIndirectNilReassign() {
var f func(p *int)
f = nil
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectMultiAssign(b bool) {
var f func(p *int)
if b {
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
} else {
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
}
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectMultiAssignNamed(b bool) {
var f func(*int)
if b {
f = nopFunc
} else {
f = nopFunc
}
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectMultiAssignResult(b bool) *int {
var f func(p *int) *int
if b {
f = func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
} else {
f = func(p *int) *int { return p } // ERROR "leaking param: p to result ~r0 level=0" "func literal does not escape"
}
return f(new(int)) // ERROR "new\(int\) escapes to heap"
}
func ClosureIndirectMultiAssignSafe(b bool) int {
var f func(p *int) int
if b {
f = func(p *int) int { return *p } // ERROR "p does not escape" "func literal does not escape"
} else {
f = func(p *int) int { return 42 } // ERROR "p does not escape" "func literal does not escape"
}
return f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectTripleAssign(x int) {
var f func(p *int)
switch x {
case 1:
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
case 2:
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
default:
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
}
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectReassignInit(b bool) {
f := func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
if b {
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
}
f(new(int)) // ERROR "new\(int\) does not escape"
}
func ClosureIndirectNestedMultiAssign(b bool) {
var f func(p *int)
f = func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
func() { // ERROR "func literal does not escape"
f = func(p *int) {} // ERROR "p does not escape" "func literal escapes to heap"
}()
f(new(int)) // ERROR "new\(int\) does not escape"
}
type myFloat struct{ v float64 }
func (f *myFloat) add(p *myFloat) *myFloat { // ERROR "leaking param: f to result ~r0 level=0" "p does not escape"
f.v += p.v
return f
}
func (f *myFloat) sub(p *myFloat) *myFloat { // ERROR "leaking param: f to result ~r0 level=0" "p does not escape"
f.v -= p.v
return f
}
func ClosureIndirectMethodExpr(b bool) {
var op func(*myFloat, *myFloat) *myFloat
if b {
op = (*myFloat).add
} else {
op = (*myFloat).sub
}
f := &myFloat{1.0} // ERROR "&myFloat{...} does not escape"
g := &myFloat{2.0} // ERROR "&myFloat{...} does not escape"
op(f, g)
}
func ClosureIndirectMethodExprMixed(b bool) {
var op func(*myFloat, *myFloat) *myFloat
if b {
op = (*myFloat).add
} else {
op = func(f, g *myFloat) *myFloat { // ERROR "f does not escape" "g does not escape" "func literal does not escape"
return nil
}
}
f := &myFloat{1.0} // ERROR "&myFloat{...} does not escape"
g := &myFloat{2.0} // ERROR "&myFloat{...} does not escape"
op(f, g)
}