// 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 { } }