Initial commit: Go 1.23 release state
This commit is contained in:
517
src/runtime/defer_test.go
Normal file
517
src/runtime/defer_test.go
Normal file
@@ -0,0 +1,517 @@
|
||||
// Copyright 2019 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 runtime_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Make sure open-coded defer exit code is not lost, even when there is an
|
||||
// unconditional panic (hence no return from the function)
|
||||
func TestUnconditionalPanic(t *testing.T) {
|
||||
defer func() {
|
||||
if recover() != "testUnconditional" {
|
||||
t.Fatal("expected unconditional panic")
|
||||
}
|
||||
}()
|
||||
panic("testUnconditional")
|
||||
}
|
||||
|
||||
var glob int = 3
|
||||
|
||||
// Test an open-coded defer and non-open-coded defer - make sure both defers run
|
||||
// and call recover()
|
||||
func TestOpenAndNonOpenDefers(t *testing.T) {
|
||||
for {
|
||||
// Non-open defer because in a loop
|
||||
defer func(n int) {
|
||||
if recover() != "testNonOpenDefer" {
|
||||
t.Fatal("expected testNonOpen panic")
|
||||
}
|
||||
}(3)
|
||||
if glob > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
testOpen(t, 47)
|
||||
panic("testNonOpenDefer")
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func testOpen(t *testing.T, arg int) {
|
||||
defer func(n int) {
|
||||
if recover() != "testOpenDefer" {
|
||||
t.Fatal("expected testOpen panic")
|
||||
}
|
||||
}(4)
|
||||
if arg > 2 {
|
||||
panic("testOpenDefer")
|
||||
}
|
||||
}
|
||||
|
||||
// Test a non-open-coded defer and an open-coded defer - make sure both defers run
|
||||
// and call recover()
|
||||
func TestNonOpenAndOpenDefers(t *testing.T) {
|
||||
testOpen(t, 47)
|
||||
for {
|
||||
// Non-open defer because in a loop
|
||||
defer func(n int) {
|
||||
if recover() != "testNonOpenDefer" {
|
||||
t.Fatal("expected testNonOpen panic")
|
||||
}
|
||||
}(3)
|
||||
if glob > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
panic("testNonOpenDefer")
|
||||
}
|
||||
|
||||
var list []int
|
||||
|
||||
// Make sure that conditional open-coded defers are activated correctly and run in
|
||||
// the correct order.
|
||||
func TestConditionalDefers(t *testing.T) {
|
||||
list = make([]int, 0, 10)
|
||||
|
||||
defer func() {
|
||||
if recover() != "testConditional" {
|
||||
t.Fatal("expected panic")
|
||||
}
|
||||
want := []int{4, 2, 1}
|
||||
if !reflect.DeepEqual(want, list) {
|
||||
t.Fatalf("wanted %v, got %v", want, list)
|
||||
}
|
||||
|
||||
}()
|
||||
testConditionalDefers(8)
|
||||
}
|
||||
|
||||
func testConditionalDefers(n int) {
|
||||
doappend := func(i int) {
|
||||
list = append(list, i)
|
||||
}
|
||||
|
||||
defer doappend(1)
|
||||
if n > 5 {
|
||||
defer doappend(2)
|
||||
if n > 8 {
|
||||
defer doappend(3)
|
||||
} else {
|
||||
defer doappend(4)
|
||||
}
|
||||
}
|
||||
panic("testConditional")
|
||||
}
|
||||
|
||||
// Test that there is no compile-time or run-time error if an open-coded defer
|
||||
// call is removed by constant propagation and dead-code elimination.
|
||||
func TestDisappearingDefer(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "invalidOS":
|
||||
defer func() {
|
||||
t.Fatal("Defer shouldn't run")
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// This tests an extra recursive panic behavior that is only specified in the
|
||||
// code. Suppose a first panic P1 happens and starts processing defer calls. If a
|
||||
// second panic P2 happens while processing defer call D in frame F, then defer
|
||||
// call processing is restarted (with some potentially new defer calls created by
|
||||
// D or its callees). If the defer processing reaches the started defer call D
|
||||
// again in the defer stack, then the original panic P1 is aborted and cannot
|
||||
// continue panic processing or be recovered. If the panic P2 does a recover at
|
||||
// some point, it will naturally remove the original panic P1 from the stack
|
||||
// (since the original panic had to be in frame F or a descendant of F).
|
||||
func TestAbortedPanic(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
t.Fatalf("wanted nil recover, got %v", r)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != "panic2" {
|
||||
t.Fatalf("wanted %v, got %v", "panic2", r)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
panic("panic2")
|
||||
}()
|
||||
panic("panic1")
|
||||
}
|
||||
|
||||
// This tests that recover() does not succeed unless it is called directly from a
|
||||
// defer function that is directly called by the panic. Here, we first call it
|
||||
// from a defer function that is created by the defer function called directly by
|
||||
// the panic. In
|
||||
func TestRecoverMatching(t *testing.T) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != "panic1" {
|
||||
t.Fatalf("wanted %v, got %v", "panic1", r)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
defer func() {
|
||||
// Shouldn't succeed, even though it is called directly
|
||||
// from a defer function, since this defer function was
|
||||
// not directly called by the panic.
|
||||
r := recover()
|
||||
if r != nil {
|
||||
t.Fatalf("wanted nil recover, got %v", r)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
panic("panic1")
|
||||
}
|
||||
|
||||
type nonSSAable [128]byte
|
||||
|
||||
type bigStruct struct {
|
||||
x, y, z, w, p, q int64
|
||||
}
|
||||
|
||||
type containsBigStruct struct {
|
||||
element bigStruct
|
||||
}
|
||||
|
||||
func mknonSSAable() nonSSAable {
|
||||
globint1++
|
||||
return nonSSAable{0, 0, 0, 0, 5}
|
||||
}
|
||||
|
||||
var globint1, globint2, globint3 int
|
||||
|
||||
//go:noinline
|
||||
func sideeffect(n int64) int64 {
|
||||
globint2++
|
||||
return n
|
||||
}
|
||||
|
||||
func sideeffect2(in containsBigStruct) containsBigStruct {
|
||||
globint3++
|
||||
return in
|
||||
}
|
||||
|
||||
// Test that nonSSAable arguments to defer are handled correctly and only evaluated once.
|
||||
func TestNonSSAableArgs(t *testing.T) {
|
||||
globint1 = 0
|
||||
globint2 = 0
|
||||
globint3 = 0
|
||||
var save1 byte
|
||||
var save2 int64
|
||||
var save3 int64
|
||||
var save4 int64
|
||||
|
||||
defer func() {
|
||||
if globint1 != 1 {
|
||||
t.Fatalf("globint1: wanted: 1, got %v", globint1)
|
||||
}
|
||||
if save1 != 5 {
|
||||
t.Fatalf("save1: wanted: 5, got %v", save1)
|
||||
}
|
||||
if globint2 != 1 {
|
||||
t.Fatalf("globint2: wanted: 1, got %v", globint2)
|
||||
}
|
||||
if save2 != 2 {
|
||||
t.Fatalf("save2: wanted: 2, got %v", save2)
|
||||
}
|
||||
if save3 != 4 {
|
||||
t.Fatalf("save3: wanted: 4, got %v", save3)
|
||||
}
|
||||
if globint3 != 1 {
|
||||
t.Fatalf("globint3: wanted: 1, got %v", globint3)
|
||||
}
|
||||
if save4 != 4 {
|
||||
t.Fatalf("save1: wanted: 4, got %v", save4)
|
||||
}
|
||||
}()
|
||||
|
||||
// Test function returning a non-SSAable arg
|
||||
defer func(n nonSSAable) {
|
||||
save1 = n[4]
|
||||
}(mknonSSAable())
|
||||
// Test composite literal that is not SSAable
|
||||
defer func(b bigStruct) {
|
||||
save2 = b.y
|
||||
}(bigStruct{1, 2, 3, 4, 5, sideeffect(6)})
|
||||
|
||||
// Test struct field reference that is non-SSAable
|
||||
foo := containsBigStruct{}
|
||||
foo.element.z = 4
|
||||
defer func(element bigStruct) {
|
||||
save3 = element.z
|
||||
}(foo.element)
|
||||
defer func(element bigStruct) {
|
||||
save4 = element.z
|
||||
}(sideeffect2(foo).element)
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func doPanic() {
|
||||
panic("Test panic")
|
||||
}
|
||||
|
||||
func TestDeferForFuncWithNoExit(t *testing.T) {
|
||||
cond := 1
|
||||
defer func() {
|
||||
if cond != 2 {
|
||||
t.Fatalf("cond: wanted 2, got %v", cond)
|
||||
}
|
||||
if recover() != "Test panic" {
|
||||
t.Fatal("Didn't find expected panic")
|
||||
}
|
||||
}()
|
||||
x := 0
|
||||
// Force a stack copy, to make sure that the &cond pointer passed to defer
|
||||
// function is properly updated.
|
||||
growStackIter(&x, 1000)
|
||||
cond = 2
|
||||
doPanic()
|
||||
|
||||
// This function has no exit/return, since it ends with an infinite loop
|
||||
for {
|
||||
}
|
||||
}
|
||||
|
||||
// Test case approximating issue #37664, where a recursive function (interpreter)
|
||||
// may do repeated recovers/re-panics until it reaches the frame where the panic
|
||||
// can actually be handled. The recurseFnPanicRec() function is testing that there
|
||||
// are no stale defer structs on the defer chain after the interpreter() sequence,
|
||||
// by writing a bunch of 0xffffffffs into several recursive stack frames, and then
|
||||
// doing a single panic-recover which would invoke any such stale defer structs.
|
||||
func TestDeferWithRepeatedRepanics(t *testing.T) {
|
||||
interpreter(0, 6, 2)
|
||||
recurseFnPanicRec(0, 10)
|
||||
interpreter(0, 5, 1)
|
||||
recurseFnPanicRec(0, 10)
|
||||
interpreter(0, 6, 3)
|
||||
recurseFnPanicRec(0, 10)
|
||||
}
|
||||
|
||||
func interpreter(level int, maxlevel int, rec int) {
|
||||
defer func() {
|
||||
e := recover()
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
if level != e.(int) {
|
||||
//fmt.Fprintln(os.Stderr, "re-panicing, level", level)
|
||||
panic(e)
|
||||
}
|
||||
//fmt.Fprintln(os.Stderr, "Recovered, level", level)
|
||||
}()
|
||||
if level+1 < maxlevel {
|
||||
interpreter(level+1, maxlevel, rec)
|
||||
} else {
|
||||
//fmt.Fprintln(os.Stderr, "Initiating panic")
|
||||
panic(rec)
|
||||
}
|
||||
}
|
||||
|
||||
func recurseFnPanicRec(level int, maxlevel int) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
recurseFn(level, maxlevel)
|
||||
}
|
||||
|
||||
var saveInt uint32
|
||||
|
||||
func recurseFn(level int, maxlevel int) {
|
||||
a := [40]uint32{0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff}
|
||||
if level+1 < maxlevel {
|
||||
// Make sure a array is referenced, so it is not optimized away
|
||||
saveInt = a[4]
|
||||
recurseFn(level+1, maxlevel)
|
||||
} else {
|
||||
panic("recurseFn panic")
|
||||
}
|
||||
}
|
||||
|
||||
// Try to reproduce issue #37688, where a pointer to an open-coded defer struct is
|
||||
// mistakenly held, and that struct keeps a pointer to a stack-allocated defer
|
||||
// struct, and that stack-allocated struct gets overwritten or the stack gets
|
||||
// moved, so a memory error happens on GC.
|
||||
func TestIssue37688(t *testing.T) {
|
||||
for j := 0; j < 10; j++ {
|
||||
g2()
|
||||
g3()
|
||||
}
|
||||
}
|
||||
|
||||
type foo struct {
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func (f *foo) method1() {
|
||||
}
|
||||
|
||||
//go:noinline
|
||||
func (f *foo) method2() {
|
||||
}
|
||||
|
||||
func g2() {
|
||||
var a foo
|
||||
ap := &a
|
||||
// The loop forces this defer to be heap-allocated and the remaining two
|
||||
// to be stack-allocated.
|
||||
for i := 0; i < 1; i++ {
|
||||
defer ap.method1()
|
||||
}
|
||||
defer ap.method2()
|
||||
defer ap.method1()
|
||||
ff1(ap, 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
||||
// Try to get the stack to be moved by growing it too large, so
|
||||
// existing stack-allocated defer becomes invalid.
|
||||
rec1(2000)
|
||||
}
|
||||
|
||||
func g3() {
|
||||
// Mix up the stack layout by adding in an extra function frame
|
||||
g2()
|
||||
}
|
||||
|
||||
var globstruct struct {
|
||||
a, b, c, d, e, f, g, h, i int
|
||||
}
|
||||
|
||||
func ff1(ap *foo, a, b, c, d, e, f, g, h, i int) {
|
||||
defer ap.method1()
|
||||
|
||||
// Make a defer that has a very large set of args, hence big size for the
|
||||
// defer record for the open-coded frame (which means it won't use the
|
||||
// defer pool)
|
||||
defer func(ap *foo, a, b, c, d, e, f, g, h, i int) {
|
||||
if v := recover(); v != nil {
|
||||
}
|
||||
globstruct.a = a
|
||||
globstruct.b = b
|
||||
globstruct.c = c
|
||||
globstruct.d = d
|
||||
globstruct.e = e
|
||||
globstruct.f = f
|
||||
globstruct.g = g
|
||||
globstruct.h = h
|
||||
}(ap, a, b, c, d, e, f, g, h, i)
|
||||
panic("ff1 panic")
|
||||
}
|
||||
|
||||
func rec1(max int) {
|
||||
if max > 0 {
|
||||
rec1(max - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue43921(t *testing.T) {
|
||||
defer func() {
|
||||
expect(t, 1, recover())
|
||||
}()
|
||||
func() {
|
||||
// Prevent open-coded defers
|
||||
for {
|
||||
defer func() {}()
|
||||
break
|
||||
}
|
||||
|
||||
defer func() {
|
||||
defer func() {
|
||||
expect(t, 4, recover())
|
||||
}()
|
||||
panic(4)
|
||||
}()
|
||||
panic(1)
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
func expect(t *testing.T, n int, err any) {
|
||||
if n != err {
|
||||
t.Fatalf("have %v, want %v", err, n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue43920(t *testing.T) {
|
||||
var steps int
|
||||
|
||||
defer func() {
|
||||
expect(t, 1, recover())
|
||||
}()
|
||||
defer func() {
|
||||
defer func() {
|
||||
defer func() {
|
||||
expect(t, 5, recover())
|
||||
}()
|
||||
defer panic(5)
|
||||
func() {
|
||||
panic(4)
|
||||
}()
|
||||
}()
|
||||
defer func() {
|
||||
expect(t, 3, recover())
|
||||
}()
|
||||
defer panic(3)
|
||||
}()
|
||||
func() {
|
||||
defer step(t, &steps, 1)
|
||||
panic(1)
|
||||
}()
|
||||
}
|
||||
|
||||
func step(t *testing.T, steps *int, want int) {
|
||||
*steps++
|
||||
if *steps != want {
|
||||
t.Fatalf("have %v, want %v", *steps, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue43941(t *testing.T) {
|
||||
var steps int = 7
|
||||
defer func() {
|
||||
step(t, &steps, 14)
|
||||
expect(t, 4, recover())
|
||||
}()
|
||||
func() {
|
||||
func() {
|
||||
defer func() {
|
||||
defer func() {
|
||||
expect(t, 3, recover())
|
||||
}()
|
||||
defer panic(3)
|
||||
panic(2)
|
||||
}()
|
||||
defer func() {
|
||||
expect(t, 1, recover())
|
||||
}()
|
||||
defer panic(1)
|
||||
}()
|
||||
defer func() {}()
|
||||
defer func() {}()
|
||||
defer step(t, &steps, 10)
|
||||
defer step(t, &steps, 9)
|
||||
step(t, &steps, 8)
|
||||
}()
|
||||
func() {
|
||||
defer step(t, &steps, 13)
|
||||
defer step(t, &steps, 12)
|
||||
func() {
|
||||
defer step(t, &steps, 11)
|
||||
panic(4)
|
||||
}()
|
||||
|
||||
// Code below isn't executed,
|
||||
// but removing it breaks the test case.
|
||||
defer func() {}()
|
||||
defer panic(-1)
|
||||
defer step(t, &steps, -1)
|
||||
defer step(t, &steps, -1)
|
||||
defer func() {}()
|
||||
}()
|
||||
}
|
||||
Reference in New Issue
Block a user