runtime: disallow defer in interrupts

This often doesn't work because there might not be a current task to
push the defer frame to. It will instead show an unhelpful nil pointer
dereference panic.

We could make this work with a separate defer stack for interrupts (as
if they were newly started goroutines) but that is difficult with
multiple interrupts happening at the same time (we shouldn't jump to a
previous interrupt in `panic()`!). So instead, disable defer altogether
in interrupts and adjust panic/recover accordingly.
This commit is contained in:
Ayke van Laethem
2024-10-14 11:22:29 +02:00
committed by Ron Evans
parent c6acaa981d
commit 45cc5b58cd
+15 -3
View File
@@ -2,6 +2,7 @@ package runtime
import (
"internal/task"
"runtime/interrupt"
"unsafe"
)
@@ -50,7 +51,9 @@ func _panic(message interface{}) {
if panicStrategy() == panicStrategyTrap {
trap()
}
if supportsRecover() {
// Note: recover is not supported inside interrupts.
// (This could be supported, like defer, but we currently don't).
if supportsRecover() && !interrupt.In() {
frame := (*deferFrame)(task.Current().DeferFrame)
if frame != nil {
frame.PanicValue = message
@@ -95,6 +98,12 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
//go:inline
//go:nobounds
func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) {
if interrupt.In() {
// Defer is not currently allowed in interrupts.
// We could add support for this, but since defer might also allocate
// (especially in loops) it might not be a good idea anyway.
runtimePanicAt(returnAddress(0), "defer in interrupt")
}
currentTask := task.Current()
frame.Previous = (*deferFrame)(currentTask.DeferFrame)
frame.JumpSP = jumpSP
@@ -122,8 +131,11 @@ func destroyDeferFrame(frame *deferFrame) {
// useParentFrame is set when the caller of runtime._recover has a defer frame
// itself. In that case, recover() shouldn't check that frame but one frame up.
func _recover(useParentFrame bool) interface{} {
if !supportsRecover() {
// Compiling without stack unwinding support, so make this a no-op.
if !supportsRecover() || interrupt.In() {
// Either we're compiling without stack unwinding support, or we're
// inside an interrupt where panic/recover is not supported. Either way,
// make this a no-op since panic() won't do any long jumps to a deferred
// function.
return nil
}
// TODO: somehow check that recover() is called directly by a deferred