summaryrefslogtreecommitdiffstats
path: root/src/runtime/defer_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 13:16:40 +0000
commit47ab3d4a42e9ab51c465c4322d2ec233f6324e6b (patch)
treea61a0ffd83f4a3def4b36e5c8e99630c559aa723 /src/runtime/defer_test.go
parentInitial commit. (diff)
downloadgolang-1.18-upstream.tar.xz
golang-1.18-upstream.zip
Adding upstream version 1.18.10.upstream/1.18.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/defer_test.go')
-rw-r--r--src/runtime/defer_test.go518
1 files changed, 518 insertions, 0 deletions
diff --git a/src/runtime/defer_test.go b/src/runtime/defer_test.go
new file mode 100644
index 0000000..3a54951
--- /dev/null
+++ b/src/runtime/defer_test.go
@@ -0,0 +1,518 @@
+// 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 (
+ "fmt"
+ "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.Fatal(fmt.Sprintf("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.Fatal(fmt.Sprintf("wanted nil recover, got %v", r))
+ }
+ }()
+ defer func() {
+ r := recover()
+ if r != "panic2" {
+ t.Fatal(fmt.Sprintf("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.Fatal(fmt.Sprintf("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.Fatal(fmt.Sprintf("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.Fatal(fmt.Sprintf("globint1: wanted: 1, got %v", globint1))
+ }
+ if save1 != 5 {
+ t.Fatal(fmt.Sprintf("save1: wanted: 5, got %v", save1))
+ }
+ if globint2 != 1 {
+ t.Fatal(fmt.Sprintf("globint2: wanted: 1, got %v", globint2))
+ }
+ if save2 != 2 {
+ t.Fatal(fmt.Sprintf("save2: wanted: 2, got %v", save2))
+ }
+ if save3 != 4 {
+ t.Fatal(fmt.Sprintf("save3: wanted: 4, got %v", save3))
+ }
+ if globint3 != 1 {
+ t.Fatal(fmt.Sprintf("globint3: wanted: 1, got %v", globint3))
+ }
+ if save4 != 4 {
+ t.Fatal(fmt.Sprintf("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.Fatal(fmt.Sprintf("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() {}()
+ }()
+}