mirror of
https://github.com/golang/go
synced 2024-11-26 14:36:52 -07:00
c20d959163
Adds: GOEXPERIMENT=loopvar (expected way of invoking) -d=loopvar={-1,0,1,2,11,12} (for per-package control and/or logging) -d=loopvarhash=... (for hash debugging) loopvar=11,12 are for testing, benchmarking, and debugging. If enabled,for loops of the form `for x,y := range thing`, if x and/or y are addressed or captured by a closure, are transformed by renaming x/y to a temporary and prepending an assignment to the body of the loop x := tmp_x. This changes the loop semantics by making each iteration's instance of x be distinct from the others (currently they are all aliased, and when this matters, it is almost always a bug). 3-range with captured iteration variables are also transformed, though it is a more complex transformation. "Optimized" to do a simpler transformation for 3-clause for where the increment is empty. (Prior optimization of address-taking under Return disabled, because it was incorrect; returns can have loops for children. Restored in a later CL.) Includes support for -d=loopvarhash=<binary string> intended for use with hash search and GOCOMPILEDEBUG=loopvarhash=<binary string> (use `gossahash -e loopvarhash command-that-fails`). Minor feature upgrades to hash-triggered features; clients can specify that file-position hashes use only the most-inline position, and/or that they use only the basenames of source files (not the full directory path). Most-inlined is the right choice for debugging loop-iteration change once the semantics are linked to the package across inlining; basename-only makes it tractable to write tests (which, otherwise, depend on the full pathname of the source file and thus vary). Updates #57969. Change-Id: I180a51a3f8d4173f6210c861f10de23de8a1b1db Reviewed-on: https://go-review.googlesource.com/c/go/+/411904 Reviewed-by: Matthew Dempsky <mdempsky@google.com> Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
192 lines
4.9 KiB
Go
192 lines
4.9 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, using compiler diagnostic flags, that the escape analysis is working.
|
|
// Compiles but does not run. Inlining is disabled.
|
|
// Registerization is disabled too (-N), which should
|
|
// have no effect on escape analysis.
|
|
|
|
package main
|
|
|
|
import "fmt"
|
|
|
|
func main() {
|
|
// Just run test over and over again. This main func is just for
|
|
// convenience; if test were the main func, we could also trigger
|
|
// the panic just by running the program over and over again
|
|
// (sometimes it takes 1 time, sometimes it takes ~4,000+).
|
|
for iter := 0; ; iter++ {
|
|
if iter%50 == 0 {
|
|
fmt.Println(iter) // ERROR "iter escapes to heap$" "... argument does not escape$"
|
|
}
|
|
test1(iter)
|
|
test2(iter)
|
|
test3(iter)
|
|
test4(iter)
|
|
test5(iter)
|
|
test6(iter)
|
|
}
|
|
}
|
|
|
|
func test1(iter int) {
|
|
|
|
const maxI = 500
|
|
m := make(map[int][]int) // ERROR "make\(map\[int\]\[\]int\) escapes to heap$"
|
|
|
|
// The panic seems to be triggered when m is modified inside a
|
|
// closure that is both recursively called and reassigned to in a
|
|
// loop.
|
|
|
|
// Cause of bug -- escape of closure failed to escape (shared) data structures
|
|
// of map. Assign to fn declared outside of loop triggers escape of closure.
|
|
// Heap -> stack pointer eventually causes badness when stack reallocation
|
|
// occurs.
|
|
|
|
var fn func() // ERROR "moved to heap: fn$"
|
|
i := 0 // ERROR "moved to heap: i$"
|
|
for ; i < maxI; i++ {
|
|
// var fn func() // this makes it work, because fn stays off heap
|
|
j := 0 // ERROR "moved to heap: j$"
|
|
fn = func() { // ERROR "func literal escapes to heap$"
|
|
m[i] = append(m[i], 0)
|
|
if j < 25 {
|
|
j++
|
|
fn()
|
|
}
|
|
}
|
|
fn()
|
|
}
|
|
|
|
if len(m) != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, len(m) = %d", iter, maxI, len(m))) // ERROR "iter escapes to heap$" "len\(m\) escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|
|
|
|
func test2(iter int) {
|
|
|
|
const maxI = 500
|
|
m := make(map[int][]int) // ERROR "make\(map\[int\]\[\]int\) does not escape$"
|
|
|
|
// var fn func()
|
|
for i := 0; i < maxI; i++ {
|
|
var fn func() // this makes it work, because fn stays off heap
|
|
j := 0
|
|
fn = func() { // ERROR "func literal does not escape$"
|
|
m[i] = append(m[i], 0)
|
|
if j < 25 {
|
|
j++
|
|
fn()
|
|
}
|
|
}
|
|
fn()
|
|
}
|
|
|
|
if len(m) != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, len(m) = %d", iter, maxI, len(m))) // ERROR "iter escapes to heap$" "len\(m\) escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|
|
|
|
func test3(iter int) {
|
|
|
|
const maxI = 500
|
|
var x int // ERROR "moved to heap: x$"
|
|
m := &x
|
|
|
|
var fn func() // ERROR "moved to heap: fn$"
|
|
for i := 0; i < maxI; i++ {
|
|
// var fn func() // this makes it work, because fn stays off heap
|
|
j := 0 // ERROR "moved to heap: j$"
|
|
fn = func() { // ERROR "func literal escapes to heap$"
|
|
if j < 100 {
|
|
j++
|
|
fn()
|
|
} else {
|
|
*m = *m + 1
|
|
}
|
|
}
|
|
fn()
|
|
}
|
|
|
|
if *m != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, *m = %d", iter, maxI, *m)) // ERROR "\*m escapes to heap$" "iter escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|
|
|
|
func test4(iter int) {
|
|
|
|
const maxI = 500
|
|
var x int
|
|
m := &x
|
|
|
|
// var fn func()
|
|
for i := 0; i < maxI; i++ {
|
|
var fn func() // this makes it work, because fn stays off heap
|
|
j := 0
|
|
fn = func() { // ERROR "func literal does not escape$"
|
|
if j < 100 {
|
|
j++
|
|
fn()
|
|
} else {
|
|
*m = *m + 1
|
|
}
|
|
}
|
|
fn()
|
|
}
|
|
|
|
if *m != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, *m = %d", iter, maxI, *m)) // ERROR "\*m escapes to heap$" "iter escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|
|
|
|
type str struct {
|
|
m *int
|
|
}
|
|
|
|
func recur1(j int, s *str) { // ERROR "s does not escape"
|
|
if j < 100 {
|
|
j++
|
|
recur1(j, s)
|
|
} else {
|
|
*s.m++
|
|
}
|
|
}
|
|
|
|
func test5(iter int) {
|
|
|
|
const maxI = 500
|
|
var x int // ERROR "moved to heap: x$"
|
|
m := &x
|
|
|
|
var fn *str
|
|
for i := 0; i < maxI; i++ {
|
|
// var fn *str // this makes it work, because fn stays off heap
|
|
fn = &str{m} // ERROR "&str{...} escapes to heap"
|
|
recur1(0, fn)
|
|
}
|
|
|
|
if *m != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, *m = %d", iter, maxI, *m)) // ERROR "\*m escapes to heap$" "iter escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|
|
|
|
func test6(iter int) {
|
|
|
|
const maxI = 500
|
|
var x int
|
|
m := &x
|
|
|
|
// var fn *str
|
|
for i := 0; i < maxI; i++ {
|
|
var fn *str // this makes it work, because fn stays off heap
|
|
fn = &str{m} // ERROR "&str{...} does not escape"
|
|
recur1(0, fn)
|
|
}
|
|
|
|
if *m != maxI {
|
|
panic(fmt.Sprintf("iter %d: maxI = %d, *m = %d", iter, maxI, *m)) // ERROR "\*m escapes to heap$" "iter escapes to heap$" "maxI escapes to heap$" "... argument does not escape$" "fmt.Sprintf\(.*\) escapes to heap"
|
|
}
|
|
}
|