From 45cc5b58cd8fa164c60ef72c934e9fe16f2ba2de Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Mon, 14 Oct 2024 11:22:29 +0200 Subject: [PATCH] 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. --- src/runtime/panic.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 8cc62aeebe..abe4e7d279 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -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