| // 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 ( | |
| "runtime" | |
| "slices" | |
| "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 !slices.Equal(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. | |
| 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() {}() | |
| }() | |
| } |