Skip to content

Commit

Permalink
runtime: disallow defer in interrupts
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aykevl authored and deadprogram committed Oct 18, 2024
1 parent c6acaa9 commit 45cc5b5
Showing 1 changed file with 15 additions and 3 deletions.
18 changes: 15 additions & 3 deletions src/runtime/panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package runtime

import (
"internal/task"
"runtime/interrupt"
"unsafe"
)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 45cc5b5

Please sign in to comment.