mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
We add entries to the defer list at panic/goexit time on-the-fly for frames with open-coded defers. We do this so that we can correctly process open-coded defers and non-open-coded defers in the correct order during panics/goexits. But we need to remove entries for open-coded defers from the defer list when there is a recover, since those entries may never get removed otherwise and will get stale, since their corresponding defers may now be processed normally (inline). This bug here is that we were only removing higher-up stale entries during a recover if all defers in the current frame were done. But we could have more defers in the current frame (as the new test case shows). In this case, we need to leave the current defer entry around for use by deferreturn, but still remove any stale entries further along the chain. For bug 43921, simple change that we should abort the removal loop for any defer entry that is started (i.e. in process by a still not-recovered outer panic), even if it is not an open-coded defer. This change does not fix bug 43920, which looks to be a more complex fix. Fixes #43882 Fixes #43921 Change-Id: Ie05b2fa26973aa26b25c8899a2abc916090ee4f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/286712 Run-TryBot: Dan Scales <danscales@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Keith Randall <khr@golang.org> Trust: Dan Scales <danscales@google.com>
440 lines
10 KiB
Go
440 lines
10 KiB
Go
// 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 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 interface{}) {
|
|
if n != err {
|
|
t.Fatalf("have %v, want %v", err, n)
|
|
}
|
|
}
|