mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
16eefc59eb
Keep a pending Goexit separate from the active panic state so recovered deferred panics do not cancel the original Goexit unwind. Goexit should also ignore -panic=trap, since it is not a panic. Wire testing.FailNow and SkipNow through runtime.Goexit, and run tests and benchmarks in goroutines. This lets Goexit terminate the user function without skipping test bookkeeping. Add Goexit/recover coverage and enable crypto/ecdh in the Linux stdlib test list.
344 lines
6.2 KiB
Go
344 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"runtime"
|
|
"sync"
|
|
)
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
func main() {
|
|
println("# simple recover")
|
|
recoverSimple()
|
|
|
|
println("\n# recover with result")
|
|
result := recoverWithResult()
|
|
println("result:", result)
|
|
|
|
println("\n# nested defer frame")
|
|
nestedDefer()
|
|
|
|
println("\n# nested panic: panic inside recover")
|
|
nestedPanic()
|
|
|
|
println("\n# panic inside defer")
|
|
panicInsideDefer()
|
|
|
|
println("\n# panic replace")
|
|
panicReplace()
|
|
|
|
println("\n# defer panic")
|
|
deferPanic()
|
|
|
|
println("\n# indirect recover")
|
|
indirectRecover()
|
|
|
|
println("\n# runtime.Goexit")
|
|
runtimeGoexit()
|
|
|
|
println("\n# repanic")
|
|
recoverRepanic()
|
|
|
|
println("\n# recover runtime errors")
|
|
recoverRuntimeError()
|
|
|
|
println("\n# recover from nil map and closed channel")
|
|
recoverNilMapAndChan()
|
|
|
|
println("\n# recover from hardware signals")
|
|
recoverSignals()
|
|
}
|
|
|
|
func recoverSimple() {
|
|
defer func() {
|
|
println("recovering...")
|
|
printitf("recovered:", recover())
|
|
}()
|
|
println("running panic...")
|
|
panic("panic")
|
|
}
|
|
|
|
func recoverWithResult() (result int) {
|
|
defer func() {
|
|
printitf("recovered:", recover())
|
|
}()
|
|
result = 3
|
|
println("running panic...")
|
|
panic("panic")
|
|
}
|
|
|
|
func nestedDefer() {
|
|
defer func() {
|
|
printitf("recovered:", recover())
|
|
}()
|
|
|
|
func() {
|
|
// The defer here doesn't catch the panic using recover(), so the outer
|
|
// panic should do that.
|
|
defer func() {
|
|
println("deferred nested function")
|
|
}()
|
|
panic("panic")
|
|
}()
|
|
println("unreachable")
|
|
}
|
|
|
|
func nestedPanic() {
|
|
defer func() {
|
|
printitf("recovered 1:", recover())
|
|
|
|
defer func() {
|
|
printitf("recovered 2:", recover())
|
|
}()
|
|
|
|
panic("foo")
|
|
}()
|
|
panic("panic")
|
|
}
|
|
|
|
func panicInsideDefer() {
|
|
defer func() {
|
|
printitf("recovered:", recover())
|
|
}()
|
|
defer func() {
|
|
panic("panic")
|
|
}()
|
|
}
|
|
|
|
func panicReplace() {
|
|
defer func() {
|
|
printitf("recovered:", recover())
|
|
}()
|
|
defer func() {
|
|
println("panic 2")
|
|
panic("panic 2")
|
|
}()
|
|
println("panic 1")
|
|
panic("panic 1")
|
|
}
|
|
|
|
func deferPanic() {
|
|
defer func() {
|
|
printitf("recovered from deferred call:", recover())
|
|
}()
|
|
|
|
// This recover should not do anything.
|
|
defer recover()
|
|
|
|
defer panic("deferred panic")
|
|
println("defer panic")
|
|
}
|
|
|
|
// TODO: Go only allows recover() to succeed when called directly from a
|
|
// deferred function. Update this test once runtime recover can distinguish it.
|
|
func indirectRecover() {
|
|
defer func() {
|
|
if r := indirectRecoverHelper(); r == nil {
|
|
println("indirect recover returned nil")
|
|
} else {
|
|
printitf("indirect recover returned:", r)
|
|
}
|
|
}()
|
|
panic("indirect panic")
|
|
}
|
|
|
|
//go:noinline
|
|
func indirectRecoverHelper() interface{} {
|
|
return recover()
|
|
}
|
|
|
|
func runtimeGoexit() {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer func() {
|
|
println("Goexit deferred function, recover is nil:", recover() == nil)
|
|
wg.Done()
|
|
}()
|
|
|
|
runtime.Goexit()
|
|
}()
|
|
wg.Wait()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
runtimeGoexitRecoveredPanic()
|
|
println("unreachable after Goexit helper")
|
|
}()
|
|
wg.Wait()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
runtimeGoexitNestedRecoveredPanic()
|
|
println("unreachable after nested Goexit helper")
|
|
}()
|
|
wg.Wait()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
runtimeGoexitLoopDefers()
|
|
println("unreachable after loop Goexit helper")
|
|
}()
|
|
wg.Wait()
|
|
}
|
|
|
|
func runtimeGoexitRecoveredPanic() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
printitf("Goexit recovered deferred panic:", r)
|
|
}
|
|
}()
|
|
defer func() {
|
|
panic("panic during Goexit")
|
|
}()
|
|
runtime.Goexit()
|
|
}
|
|
|
|
func runtimeGoexitNestedRecoveredPanic() {
|
|
defer func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
printitf("Goexit nested recovered deferred panic:", r)
|
|
}
|
|
}()
|
|
panic("nested panic during Goexit")
|
|
}()
|
|
runtime.Goexit()
|
|
}
|
|
|
|
func runtimeGoexitLoopDefers() {
|
|
for i := 0; i < 2; i++ {
|
|
defer func() {
|
|
println("Goexit loop defer")
|
|
}()
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
printitf("Goexit loop recovered deferred panic:", r)
|
|
}
|
|
}()
|
|
defer func() {
|
|
panic("loop panic during Goexit")
|
|
}()
|
|
runtime.Goexit()
|
|
}
|
|
|
|
// Test that a repanic inside a deferred function propagates correctly
|
|
// instead of re-running the same defer. This is a regression test for
|
|
// tinygo-org/tinygo issue 3449.
|
|
func recoverRepanic() {
|
|
// Two defers: inner recovers and repanics, outer should catch it.
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
printitf("outer recovered:", r)
|
|
}
|
|
}()
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
println("inner, repanicking")
|
|
panic(r)
|
|
}
|
|
}()
|
|
panic("repanic value")
|
|
}
|
|
|
|
func printitf(msg string, itf interface{}) {
|
|
switch itf := itf.(type) {
|
|
case string:
|
|
println(msg, itf)
|
|
default:
|
|
println(msg, itf)
|
|
}
|
|
}
|
|
|
|
// Test recovering from runtime errors (bounds checks, type assertions, etc.)
|
|
func recoverRuntimeError() {
|
|
recoverMustPanic("index", func() {
|
|
s := make([]int, 5)
|
|
_ = s[99]
|
|
})
|
|
recoverMustPanic("index from helper", func() {
|
|
s := []byte{1}
|
|
_ = readOutOfBounds(s)
|
|
})
|
|
recoverMustPanic("slice", func() {
|
|
s := make([]int, 5)
|
|
_ = s[3:99]
|
|
})
|
|
recoverMustPanic("type assert", func() {
|
|
var x interface{} = 1
|
|
_ = x.(string)
|
|
})
|
|
recoverEmptyInterfaceTypeAssert()
|
|
}
|
|
|
|
//go:noinline
|
|
func readOutOfBounds(s []byte) byte {
|
|
return s[2]
|
|
}
|
|
|
|
func recoverEmptyInterfaceTypeAssert() {
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
println(" failed empty interface type assert")
|
|
} else {
|
|
println(" recovered: empty interface type assert")
|
|
}
|
|
}()
|
|
var intf interface{} = 3
|
|
typed := intf.(interface{})
|
|
useEmptyInterface(typed)
|
|
}
|
|
|
|
func useEmptyInterface(typed interface{}) {
|
|
if typed.(int) != 3 {
|
|
println(" failed empty interface value")
|
|
}
|
|
}
|
|
|
|
func recoverMustPanic(name string, f func()) {
|
|
defer func() {
|
|
r := recover()
|
|
if r != nil {
|
|
println(" recovered:", name)
|
|
} else {
|
|
println(" failed to recover:", name)
|
|
}
|
|
}()
|
|
f()
|
|
}
|
|
|
|
// Test recovering from nil map assignment and closed channel send.
|
|
func recoverNilMapAndChan() {
|
|
recoverMustPanic("nil map", func() {
|
|
var m map[string]int
|
|
m["x"] = 1
|
|
})
|
|
recoverMustPanic("closed chan", func() {
|
|
ch := make(chan int)
|
|
close(ch)
|
|
ch <- 1
|
|
})
|
|
recoverMustPanic("close nil chan", func() {
|
|
var ch chan int
|
|
close(ch)
|
|
})
|
|
}
|
|
|
|
// Test recovering from hardware signals (SIGFPE, SIGSEGV).
|
|
func recoverSignals() {
|
|
recoverMustPanic("divide by zero", func() {
|
|
var x int
|
|
println(1 / x)
|
|
})
|
|
recoverMustPanic("nil pointer dereference", func() {
|
|
var p *int
|
|
println(*p)
|
|
})
|
|
}
|