From 16fc1ea2bb4135d3087e37be906c1dc1893a4d6f Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 24 Jul 2026 10:49:50 -0700 Subject: [PATCH] runtime: make fatal failures unrecoverable Match the Go runtime by terminating for deadlocks, stack overflows, runtime and GC invariants, invalid lock operations, and platform initialization failures instead of routing them through panic/recover. Keep language-level runtime errors and unsupported user operations recoverable. Add crash coverage that verifies fatal errors bypass deferred recover calls. --- builder/sizes_test.go | 6 ++-- main_test.go | 44 ++++++++++++++++++----- src/internal/task/fatal.go | 6 ++++ src/internal/task/mutex-cooperative.go | 2 +- src/internal/task/mutex-preemptive.go | 2 +- src/internal/task/queue.go | 4 +-- src/internal/task/task_asyncify.go | 7 ++-- src/internal/task/task_exit.go | 6 ++-- src/internal/task/task_stack.go | 3 -- src/internal/task/task_stack_multicore.go | 4 +-- src/internal/task/task_stack_unicore.go | 4 +-- src/internal/task/task_threads.go | 17 ++++----- src/runtime/arch_tinygowasm_malloc.go | 4 +-- src/runtime/dynamic_arm64.go | 2 +- src/runtime/gc_blocks.go | 30 ++++++++-------- src/runtime/gc_boehm.go | 11 ++++-- src/runtime/gc_leaking.go | 2 +- src/runtime/gc_none.go | 2 +- src/runtime/os_darwin.go | 4 +-- src/runtime/os_windows.go | 2 +- src/runtime/os_windows_pe.go | 2 +- src/runtime/panic.go | 4 +-- src/runtime/rand.go | 2 +- src/runtime/runtime.go | 2 +- src/runtime/runtime_nintendoswitch.go | 6 ++-- src/runtime/runtime_rp2.go | 2 +- src/runtime/runtime_tinygoriscv_qemu.go | 8 ++--- src/runtime/runtime_tinygowasm_unknown.go | 2 +- src/runtime/runtime_tinygowasmp2.go | 4 +-- src/runtime/runtime_uefi.go | 2 +- src/runtime/runtime_unix.go | 8 ++--- src/runtime/runtime_windows.go | 2 +- src/runtime/runtime_windows_go126.go | 3 +- src/runtime/scheduler_cooperative.go | 4 +-- src/runtime/scheduler_cores.go | 2 +- src/runtime/scheduler_idle_wasip1.go | 2 +- src/runtime/scheduler_idle_wasip1_none.go | 2 +- src/runtime/scheduler_none.go | 6 ++-- src/runtime/wait_other.go | 2 +- src/sync/mutex.go | 6 ++-- testdata/runtimefatal.go | 12 +++++++ 41 files changed, 144 insertions(+), 101 deletions(-) create mode 100644 src/internal/task/fatal.go create mode 100644 testdata/runtimefatal.go diff --git a/builder/sizes_test.go b/builder/sizes_test.go index f6bb112fa..639c32b5c 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) { // This is a small number of very diverse targets that we want to test. tests := []sizeTest{ // microcontrollers - {"hifive1b", "examples/echo", 4277, 307, 0, 2260}, - {"microbit", "examples/serial", 2836, 368, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 8013, 1663, 132, 7488}, + {"hifive1b", "examples/echo", 4313, 323, 0, 2260}, + {"microbit", "examples/serial", 2838, 382, 8, 2256}, + {"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/main_test.go b/main_test.go index 4b354ef4c..2dfd1683f 100644 --- a/main_test.go +++ b/main_test.go @@ -993,15 +993,15 @@ func TestGoexitCrash(t *testing.T) { name string want string }{ - {"main", "all goroutines are asleep - deadlock!"}, - {"deadlock", "all goroutines are asleep - deadlock!"}, - {"exit", "all goroutines are asleep - deadlock!"}, - {"main-other", "all goroutines are asleep - deadlock!"}, - {"in-panic", "all goroutines are asleep - deadlock!"}, + {"main", "fatal error: all goroutines are asleep - deadlock!"}, + {"deadlock", "fatal error: all goroutines are asleep - deadlock!"}, + {"exit", "fatal error: all goroutines are asleep - deadlock!"}, + {"main-other", "fatal error: all goroutines are asleep - deadlock!"}, + {"in-panic", "fatal error: all goroutines are asleep - deadlock!"}, {"panic", "panic: panic after Goexit"}, - {"recovered-panic", "all goroutines are asleep - deadlock!"}, - {"recover-before-panic", "all goroutines are asleep - deadlock!"}, - {"recover-before-panic-loop", "all goroutines are asleep - deadlock!"}, + {"recovered-panic", "fatal error: all goroutines are asleep - deadlock!"}, + {"recover-before-panic", "fatal error: all goroutines are asleep - deadlock!"}, + {"recover-before-panic-loop", "fatal error: all goroutines are asleep - deadlock!"}, } { t.Run(tc.name, func(t *testing.T) { output := &bytes.Buffer{} @@ -1022,6 +1022,34 @@ func TestGoexitCrash(t *testing.T) { } } +func TestRuntimeFatal(t *testing.T) { + t.Parallel() + + options := optionsFromTarget("", sema) + config, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + + output := &bytes.Buffer{} + _, err = buildAndRun("testdata/runtimefatal.go", config, output, nil, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { + cmd.Stdout = nil + cmd.Stderr = nil + data, err := cmd.CombinedOutput() + output.Write(data) + return err + }) + if err == nil { + t.Fatal("program unexpectedly exited successfully") + } + if want := "fatal error: sync: unlock of unlocked Mutex"; !strings.Contains(output.String(), want) { + t.Fatalf("output does not contain %q:\n%s", want, output.String()) + } + if strings.Contains(output.String(), "recovered:") { + t.Fatalf("fatal runtime error was recovered:\n%s", output.String()) + } +} + func TestTest(t *testing.T) { t.Parallel() diff --git a/src/internal/task/fatal.go b/src/internal/task/fatal.go new file mode 100644 index 000000000..53be72c9c --- /dev/null +++ b/src/internal/task/fatal.go @@ -0,0 +1,6 @@ +package task + +import _ "unsafe" + +//go:linkname runtimeFatal runtime.runtimeFatal +func runtimeFatal(msg string) diff --git a/src/internal/task/mutex-cooperative.go b/src/internal/task/mutex-cooperative.go index 90274df2b..57d66d013 100644 --- a/src/internal/task/mutex-cooperative.go +++ b/src/internal/task/mutex-cooperative.go @@ -20,7 +20,7 @@ func (m *Mutex) Lock() { func (m *Mutex) Unlock() { if !m.locked { - panic("sync: unlock of unlocked Mutex") + runtimeFatal("sync: unlock of unlocked Mutex") } // Wake up a blocked task, if applicable. diff --git a/src/internal/task/mutex-preemptive.go b/src/internal/task/mutex-preemptive.go index ec83a6135..3ab0f2bac 100644 --- a/src/internal/task/mutex-preemptive.go +++ b/src/internal/task/mutex-preemptive.go @@ -49,7 +49,7 @@ func (m *Mutex) Lock() { func (m *Mutex) Unlock() { if old := m.futex.Swap(0); old == 0 { // Mutex wasn't locked before. - panic("sync: unlock of unlocked Mutex") + runtimeFatal("sync: unlock of unlocked Mutex") } else if old == 2 { // Mutex was a contended lock, so we need to wake the next waiter. m.futex.Wake() diff --git a/src/internal/task/queue.go b/src/internal/task/queue.go index 7242c4e31..1095e8bbd 100644 --- a/src/internal/task/queue.go +++ b/src/internal/task/queue.go @@ -15,7 +15,7 @@ func (q *Queue) Push(t *Task) { mask := lockAtomics() if asserts && t.Next != nil { unlockAtomics(mask) - panic("runtime: pushing a task to a queue with a non-nil Next pointer") + runtimeFatal("runtime: pushing a task to a queue with a non-nil Next pointer") } if q.tail != nil { q.tail.Next = t @@ -78,7 +78,7 @@ func (s *Stack) Push(t *Task) { mask := lockAtomics() if asserts && t.Next != nil { unlockAtomics(mask) - panic("runtime: pushing a task to a stack with a non-nil Next pointer") + runtimeFatal("runtime: pushing a task to a stack with a non-nil Next pointer") } s.top, t.Next = t, s.top unlockAtomics(mask) diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index 0f7437067..d7d9a6de4 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -11,9 +11,6 @@ import ( // otherwise Go wouldn't allow the cast to a smaller integer size. const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0))) -//go:linkname runtimePanic runtime.runtimePanic -func runtimePanic(str string) - // state is a structure which holds a reference to the state of the task. // When the task is suspended, the stack pointers are saved here. type state struct { @@ -95,7 +92,7 @@ func Current() *Task { // This function may only be called when running on a goroutine stack, not when running on the system stack. func Pause() { if *currentTask.state.canaryPtr != stackCanary { - runtimePanic("stack overflow") + runtimeFatal("stack overflow") } currentTask.state.unwind() @@ -124,7 +121,7 @@ func (t *Task) Resume() { currentTask = prevTask t.gcData.swap() if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) { - runtimePanic("stack overflow") + runtimeFatal("stack overflow") } } diff --git a/src/internal/task/task_exit.go b/src/internal/task/task_exit.go index 0c2bb8ee8..40f988058 100644 --- a/src/internal/task/task_exit.go +++ b/src/internal/task/task_exit.go @@ -28,15 +28,15 @@ func exit(goexit bool) { if t == mainTask { if goexit { if remaining == 0 { - runtimePanic("all goroutines are asleep - deadlock!") + runtimeFatal("all goroutines are asleep - deadlock!") } atomic.StoreUint32(&mainExitedByGoexit, 1) } } else if atomic.LoadUint32(&mainExitedByGoexit) != 0 && remaining == 0 { - runtimePanic("all goroutines are asleep - deadlock!") + runtimeFatal("all goroutines are asleep - deadlock!") } // TODO: explicitly free the stack after switching back to the scheduler. Pause() - runtimePanic("unreachable") + runtimeFatal("unreachable") } diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 9db5c5890..23f3b9097 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -6,9 +6,6 @@ import ( "unsafe" ) -//go:linkname runtimePanic runtime.runtimePanic -func runtimePanic(str string) - // Stack canary, to detect a stack overflow. The number is a random number // generated by random.org. The bit fiddling dance is necessary because // otherwise Go wouldn't allow the cast to a smaller integer size. diff --git a/src/internal/task/task_stack_multicore.go b/src/internal/task/task_stack_multicore.go index 65cf3a004..dd2a266ae 100644 --- a/src/internal/task/task_stack_multicore.go +++ b/src/internal/task/task_stack_multicore.go @@ -23,10 +23,10 @@ func PauseLocked() { // valid. If it is not, a stack overflow has occurred. current := Current() if *current.state.canaryPtr != stackCanary { - runtimePanic("goroutine stack overflow") + runtimeFatal("goroutine stack overflow") } if interrupt.In() { - runtimePanic("blocked inside interrupt") + runtimeFatal("blocked inside interrupt") } if current.RunState == RunStateResuming { // Another core already marked this goroutine as ready to resume. diff --git a/src/internal/task/task_stack_unicore.go b/src/internal/task/task_stack_unicore.go index b4425de38..e1b172f9c 100644 --- a/src/internal/task/task_stack_unicore.go +++ b/src/internal/task/task_stack_unicore.go @@ -18,10 +18,10 @@ func Pause() { // Check whether the canary (the lowest address of the stack) is still // valid. If it is not, a stack overflow has occurred. if *currentTask.state.canaryPtr != stackCanary { - runtimePanic("goroutine stack overflow") + runtimeFatal("goroutine stack overflow") } if interrupt.In() { - runtimePanic("blocked inside interrupt") + runtimeFatal("blocked inside interrupt") } currentTask.state.pause() } diff --git a/src/internal/task/task_threads.go b/src/internal/task/task_threads.go index 0d14eae34..52374dec8 100644 --- a/src/internal/task/task_threads.go +++ b/src/internal/task/task_threads.go @@ -48,7 +48,7 @@ var activeTaskLock PMutex var mainExitedByGoexit bool func OnSystemStack() bool { - runtimePanic("todo: task.OnSystemStack") + runtimeFatal("todo: task.OnSystemStack") return false } @@ -63,7 +63,7 @@ func Init(sp uintptr) { func Current() *Task { t := (*Task)(tinygo_task_current()) if t == nil { - runtimePanic("unknown current task") + runtimeFatal("unknown current task") } return t } @@ -112,7 +112,7 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { activeTaskLock.Lock() errCode := tinygo_task_start(fn, args, t, &t.state.thread, &t.state.stackTop, stackSize) if errCode != 0 { - runtimePanic("could not start thread") + runtimeFatal("could not start thread") } t.state.QueueNext = activeTasks activeTasks = t @@ -127,7 +127,7 @@ func taskExited(t *Task) { } if exit(t) { - runtimePanic("all goroutines are asleep - deadlock!") + runtimeFatal("all goroutines are asleep - deadlock!") } } @@ -149,7 +149,7 @@ func exit(t *Task) bool { // Sanity check. if !found { - runtimePanic("taskExited failed") + runtimeFatal("taskExited failed") } return deadlocked } @@ -173,11 +173,11 @@ func Exit() { } activeTaskLock.Unlock() if noOtherTasks { - runtimePanic("all goroutines are asleep - deadlock!") + runtimeFatal("all goroutines are asleep - deadlock!") } } if exit(t) { - runtimePanic("all goroutines are asleep - deadlock!") + runtimeFatal("all goroutines are asleep - deadlock!") } tinygo_task_exit() } @@ -325,9 +325,6 @@ func StackTop() uintptr { return Current().state.stackTop } -//go:linkname runtimePanic runtime.runtimePanic -func runtimePanic(msg string) - // Using //go:linkname instead of //export so that we don't tell the compiler // that the 't' parameter won't escape (because it will). // diff --git a/src/runtime/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index 471361bd7..df824881e 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -34,7 +34,7 @@ func libc_free(ptr unsafe.Pointer) { if _, ok := allocs[(*byte)(ptr)]; ok { delete(allocs, (*byte)(ptr)) } else { - panic("free: invalid pointer") + runtimeFatal("free: invalid pointer") } } @@ -63,7 +63,7 @@ func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer { copy(newBuf, oldBuf) delete(allocs, (*byte)(oldPtr)) } else { - panic("realloc: invalid pointer") + runtimeFatal("realloc: invalid pointer") } } diff --git a/src/runtime/dynamic_arm64.go b/src/runtime/dynamic_arm64.go index e167f6f2e..9f126b009 100644 --- a/src/runtime/dynamic_arm64.go +++ b/src/runtime/dynamic_arm64.go @@ -49,7 +49,7 @@ func dynamicLoader(base uintptr, dyn *dyn64) { } if rela == nil { - runtimePanic("bad reloc") + runtimeFatal("bad reloc") } if debugLoader { diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 0cb11c00f..2aadaeaf8 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -108,7 +108,7 @@ type gcBlock uintptr // might not be heap-aligned). func blockFromAddr(addr uintptr) gcBlock { if gcAsserts && (addr < heapStart || addr >= uintptr(metadataStart)) { - runtimePanic("gc: trying to get block from invalid address") + runtimeFatal("gc: trying to get block from invalid address") } return gcBlock((addr - heapStart) / bytesPerBlock) } @@ -122,7 +122,7 @@ func (b gcBlock) pointer() unsafe.Pointer { func (b gcBlock) address() uintptr { addr := heapStart + uintptr(b)*bytesPerBlock if gcAsserts && addr > uintptr(metadataStart) { - runtimePanic("gc: block pointing inside metadata") + runtimeFatal("gc: block pointing inside metadata") } return addr } @@ -154,7 +154,7 @@ func (b gcBlock) findHead() gcBlock { } if gcAsserts { if b.state() != blockStateHead && b.state() != blockStateMark { - runtimePanic("gc: found tail without head") + runtimeFatal("gc: found tail without head") } } return b @@ -182,14 +182,14 @@ func (b gcBlock) setState(newState blockState) { stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) *stateBytePtr |= uint8(newState << (b % blocksPerStateByte)) if gcAsserts && b.state() != newState { - runtimePanic("gc: setState() was not successful") + runtimeFatal("gc: setState() was not successful") } } // unmark changes the state of b from blockStateMark to blockStateHead. func (b gcBlock) unmark() { if gcAsserts && b.state() != blockStateMark { - runtimePanic("gc: block not marked") + runtimeFatal("gc: block not marked") } stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) *stateBytePtr ^= uint8(blockStateMark^blockStateHead) << (b % blocksPerStateByte) @@ -234,7 +234,7 @@ type freeRangeMore struct { // insertFreeRange inserts a range of len blocks starting at ptr into the free list. func insertFreeRange(ptr unsafe.Pointer, len uintptr) { if gcAsserts && len == 0 { - runtimePanic("gc: insert 0-length free range") + runtimeFatal("gc: insert 0-length free range") } // Find the insertion point by length. @@ -267,7 +267,7 @@ func insertFreeRange(ptr unsafe.Pointer, len uintptr) { // It returns nil if there are no sufficiently long ranges. func popFreeRange(len uintptr) unsafe.Pointer { if gcAsserts && len == 0 { - runtimePanic("gc: pop 0-length free range") + runtimeFatal("gc: pop 0-length free range") } // Find the removal point by length. @@ -331,7 +331,7 @@ func initHeap() { // will be expensive. func setHeapEnd(newHeapEnd uintptr) { if gcAsserts && newHeapEnd <= heapEnd { - runtimePanic("gc: setHeapEnd didn't grow the heap") + runtimeFatal("gc: setHeapEnd didn't grow the heap") } // Save some old variables we need later. @@ -354,7 +354,7 @@ func setHeapEnd(newHeapEnd uintptr) { // should be used to avoid corruption. // This assert checks whether that's true. if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize { - runtimePanic("gc: heap did not grow enough at once") + runtimeFatal("gc: heap did not grow enough at once") } // Insert the new free range. This range will be separate from any previous @@ -393,7 +393,7 @@ func calculateHeapAddresses() { } if gcAsserts && metadataSize*blocksPerStateByte < numBlocks { // sanity check - runtimePanic("gc: metadata array is too small") + runtimeFatal("gc: metadata array is too small") } } @@ -407,7 +407,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { } if interrupt.In() { - runtimePanicAt(returnAddress(0), "heap alloc in interrupt") + runtimeFatal("heap alloc in interrupt") } // Round the size up to a multiple of blocks, adding space for the header. @@ -622,10 +622,10 @@ func markRoots(start, end uintptr) { } if gcAsserts { if start >= end { - runtimePanic("gc: unexpected range to mark") + runtimeFatal("gc: unexpected range to mark") } if start%unsafe.Alignof(start) != 0 { - runtimePanic("gc: unaligned start pointer") + runtimeFatal("gc: unaligned start pointer") } } @@ -881,10 +881,10 @@ func SetFinalizer(obj interface{}, finalizer interface{}) { // exact func(*T) match is not checked; the closure ABI is uniform for any // single pointer argument, which is why callFinalizer can reinterpret it. if reflectlite.ValueOf(obj).Kind() != reflectlite.Pointer { - runtimePanic("runtime.SetFinalizer: first argument is not a pointer") + runtimeFatal("runtime.SetFinalizer: first argument is not a pointer") } if finalizer != nil && reflectlite.ValueOf(finalizer).Kind() != reflectlite.Func { - runtimePanic("runtime.SetFinalizer: second argument is not a function") + runtimeFatal("runtime.SetFinalizer: second argument is not a function") } // For an interface holding a pointer, the value word is the pointer itself. diff --git a/src/runtime/gc_boehm.go b/src/runtime/gc_boehm.go index a0adc93d3..e0fc16a67 100644 --- a/src/runtime/gc_boehm.go +++ b/src/runtime/gc_boehm.go @@ -55,7 +55,7 @@ func markCurrentGoroutineStack(sp uintptr) { // more pointers alive than needed on the current stack). base := libgc_base(sp) if base == 0 { // && asserts - runtimePanic("goroutine stack not in a heap allocation?") + runtimeFatal("goroutine stack not in a heap allocation?") } stackBottom := base + libgc_size(base) libgc_push_all_stack(sp, stackBottom) @@ -69,6 +69,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { gcLock.Lock() var ptr unsafe.Pointer + var needsZero bool if layout == gclayout.NoPtrs.AsPtr() { // This object is entirely pointer free, for example make([]int, ...). // Make sure the GC knows this so it doesn't scan the object @@ -76,7 +77,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { ptr = libgc_malloc_atomic(size) // Memory returned from libgc_malloc_atomic has not been zeroed so we // have to do that manually. - memzero(ptr, size) + needsZero = true } else { // TODO: bdwgc supports typed allocations, which could be useful to // implement a mostly-precise GC. @@ -88,6 +89,10 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { gcLock.Unlock() if ptr == nil { runtimeFatal("gc: out of memory") + return nil + } + if needsZero { + memzero(ptr, size) } return ptr @@ -129,7 +134,7 @@ func ReadMemStats(m *MemStats) { } func setHeapEnd(newHeapEnd uintptr) { - runtimePanic("gc: did not expect setHeapEnd call") + runtimeFatal("gc: did not expect setHeapEnd call") } func SetFinalizer(obj interface{}, finalizer interface{}) { diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 1467cb6bc..4ec9e6097 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -79,7 +79,7 @@ func free(ptr unsafe.Pointer) { } func markRoots(start, end uintptr) { - runtimePanic("unreachable: markRoots") + runtimeFatal("unreachable: markRoots") } // ReadMemStats populates m with memory statistics. diff --git a/src/runtime/gc_none.go b/src/runtime/gc_none.go index 3c092c65c..ce9649c71 100644 --- a/src/runtime/gc_none.go +++ b/src/runtime/gc_none.go @@ -33,7 +33,7 @@ func GC() { } func markRoots(start, end uintptr) { - runtimePanic("unreachable: markRoots") + runtimeFatal("unreachable: markRoots") } func SetFinalizer(obj interface{}, finalizer interface{}) { diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 3bc939e81..6a151af80 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -80,7 +80,7 @@ func findGlobals(found func(start, end uintptr)) { // Sanity check that we're actually looking at a MachO header. if gcAsserts && libc_mh_execute_header.magic != MH_MAGIC_64 { - runtimePanic("gc: unexpected MachO header") + runtimeFatal("gc: unexpected MachO header") } // Iterate through the load commands. @@ -106,7 +106,7 @@ func findGlobals(found func(start, end uintptr)) { // Note that when ASLR is disabled (for example, when // running inside lldb), the offset is zero. That's why we // need a separate hasOffset for this assert. - runtimePanic("gc: did not detect ASLR offset") + runtimeFatal("gc: did not detect ASLR offset") } // Scan this segment for GC roots. // This could be improved by only reading the memory areas diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 908ce7052..c6d221850 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -29,7 +29,7 @@ func findGlobals(found func(start, end uintptr)) { // it using GetModuleHandle to account for ASLR etc. result := _GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, &module) if gcAsserts && (!result || module.signature != 0x5A4D) { // 0x4D5A is "MZ" - runtimePanic("cannot get module handle") + runtimeFatal("cannot get module handle") } } diff --git a/src/runtime/os_windows_pe.go b/src/runtime/os_windows_pe.go index 53bc62f2b..61f2f7fa0 100644 --- a/src/runtime/os_windows_pe.go +++ b/src/runtime/os_windows_pe.go @@ -48,7 +48,7 @@ func findGlobalsForPE(found func(start, end uintptr)) { pe := (*peHeader)(unsafe.Add(unsafe.Pointer(module), module.peHeader)) if gcAsserts && pe.magic != 0x00004550 { // 0x4550 is "PE" - runtimePanic("cannot find PE header") + runtimeFatal("cannot find PE header") } section := (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(pe)) + uintptr(pe.sizeOfOptionalHeader) + unsafe.Sizeof(peHeader{}))) diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 3c5b4029a..ff4bb8af2 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -145,7 +145,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { // 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") + runtimeFatal("defer in interrupt") } currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) @@ -272,5 +272,5 @@ func blockingPanic() { //go:linkname fips_fatal crypto/internal/fips140.fatal func fips_fatal(msg string) { - runtimePanic(msg) + runtimeFatal(msg) } diff --git a/src/runtime/rand.go b/src/runtime/rand.go index 531910ef0..620988bfa 100644 --- a/src/runtime/rand.go +++ b/src/runtime/rand.go @@ -8,4 +8,4 @@ import _ "unsafe" func vgetrandom(p []byte, flags uint32) (ret int, supported bool) { return 0, false } //go:linkname fatal crypto/internal/sysrand.fatal -func fatal(msg string) { runtimePanic(msg) } +func fatal(msg string) { runtimeFatal(msg) } diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 26d29dbc9..6ad14fe10 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -94,7 +94,7 @@ func nanotime() int64 { // //go:linkname os_sigpipe os.sigpipe func os_sigpipe() { - runtimePanic("too many writes on closed pipe") + runtimeFatal("too many writes on closed pipe") } // LockOSThread wires the calling goroutine to its current operating system thread. diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index b2d13f86b..02b7e8c1c 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -181,7 +181,7 @@ func setupEnv() { default: if entry.Flags&1 > 0 { // Mandatory but not parsed - runtimePanic("mandatory config entry not parsed") + runtimeFatal("mandatory config entry not parsed") } } ptr += unsafe.Sizeof(configEntry{}) @@ -224,7 +224,7 @@ func setupHeap() { svcSetHeapSize(&heapStart, uint64(size)) if heapStart == 0 { - runtimePanic("failed to allocate heap") + runtimeFatal("failed to allocate heap") } totalHeap = uint64(size) @@ -340,6 +340,6 @@ func hardwareRand() (n uint64, ok bool) { func libc_errno_location() *int32 { // CGo is unavailable, so this function should be unreachable. - runtimePanic("runtime: no cgo errno") + runtimeFatal("runtime: no cgo errno") return nil } diff --git a/src/runtime/runtime_rp2.go b/src/runtime/runtime_rp2.go index f6d31f980..09ec364cd 100644 --- a/src/runtime/runtime_rp2.go +++ b/src/runtime/runtime_rp2.go @@ -270,7 +270,7 @@ func coreStackTop(core uint32) uintptr { case 1: return uintptr(unsafe.Pointer(&stack1TopSymbol)) default: - runtimePanic("unexpected core") + runtimeFatal("unexpected core") return 0 } } diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go index 1ee0d22d8..7f4035c4f 100644 --- a/src/runtime/runtime_tinygoriscv_qemu.go +++ b/src/runtime/runtime_tinygoriscv_qemu.go @@ -117,7 +117,7 @@ func handleInterrupt() { sleepCheckpoint.Jump() } default: - runtimePanic("unknown interrupt") + runtimeFatal("unknown interrupt") abort() } } else { @@ -200,7 +200,7 @@ func coreStackTop(core uint32) uintptr { case 3: return uintptr(unsafe.Pointer(&stack3TopSymbol)) default: - runtimePanic("unexpected core") + runtimeFatal("unexpected core") return 0 } } @@ -367,7 +367,7 @@ func (l *spinLock) Lock() { func (l *spinLock) Unlock() { // Safety check: the spinlock should have been locked. if schedulerAsserts && l.Uint32.Load() != 1 { - runtimePanic("unlock of unlocked spinlock") + runtimeFatal("unlock of unlocked spinlock") } // Unlock the lock. Simply write 0, because we already know it is locked. @@ -417,7 +417,7 @@ func schedulerUnlockAndWait() { // We can do this check since this is not baremetal: there won't be any // external interrupts that might unblock a goroutine. if sleepingHarts == (1<