mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
+36
-8
@@ -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()
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package task
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//go:linkname runtimeFatal runtime.runtimeFatal
|
||||
func runtimeFatal(msg string)
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
//
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ func dynamicLoader(base uintptr, dyn *dyn64) {
|
||||
}
|
||||
|
||||
if rela == nil {
|
||||
runtimePanic("bad reloc")
|
||||
runtimeFatal("bad reloc")
|
||||
}
|
||||
|
||||
if debugLoader {
|
||||
|
||||
+15
-15
@@ -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.
|
||||
|
||||
@@ -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{}) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -33,7 +33,7 @@ func GC() {
|
||||
}
|
||||
|
||||
func markRoots(start, end uintptr) {
|
||||
runtimePanic("unreachable: markRoots")
|
||||
runtimeFatal("unreachable: markRoots")
|
||||
}
|
||||
|
||||
func SetFinalizer(obj interface{}, finalizer interface{}) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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{})))
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<<numCPU)-1 {
|
||||
runtimePanic("all cores are sleeping - deadlock!")
|
||||
runtimeFatal("all cores are sleeping - deadlock!")
|
||||
}
|
||||
|
||||
// Need to disable interrupts while saving the checkpoint, otherwise if the
|
||||
|
||||
@@ -54,6 +54,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
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func mainReturnExit() {
|
||||
// WASIp2 does not use _start, instead it uses _initialize and a custom
|
||||
// WASIp2-specific main function. So this should never be called in
|
||||
// practice.
|
||||
runtimePanic("unreachable: _start was called")
|
||||
runtimeFatal("unreachable: _start was called")
|
||||
}
|
||||
|
||||
// TinyGo does not yet support any form of parallelism on WebAssembly, so these
|
||||
@@ -84,6 +84,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
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func abort() {
|
||||
func preinit() {
|
||||
uefi.BS().SetWatchdogTimer(0, 0, 0, nil)
|
||||
if !growHeap() {
|
||||
runtimePanic("could not allocate initial UEFI heap")
|
||||
runtimeFatal("could not allocate initial UEFI heap")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -155,7 +155,7 @@ func tinygo_sigpanic() {
|
||||
case sig_SIGFPE:
|
||||
runtimePanic("divide by zero")
|
||||
default:
|
||||
runtimePanic("signal")
|
||||
runtimeFatal("signal")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ func allocateHeap() {
|
||||
// This can happen on 32-bit systems.
|
||||
heapMaxSize /= 2
|
||||
if heapMaxSize < 4096 {
|
||||
runtimePanic("cannot allocate heap memory")
|
||||
runtimeFatal("cannot allocate heap memory")
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -463,7 +463,7 @@ func signal_recv() uint32 {
|
||||
// There are no signals to receive. Sleep until there are.
|
||||
if signalRecvWaiter.Swap(task.Current()) != nil {
|
||||
// We expect only a single goroutine to call signal_recv.
|
||||
runtimePanic("signal_recv called concurrently")
|
||||
runtimeFatal("signal_recv called concurrently")
|
||||
}
|
||||
task.Pause()
|
||||
continue
|
||||
@@ -507,6 +507,6 @@ func waitForEvents() {
|
||||
}
|
||||
} else {
|
||||
// The program doesn't use signals, so this is a deadlock.
|
||||
runtimePanic("deadlocked: no event source")
|
||||
runtimeFatal("deadlocked: no event source")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +321,6 @@ func tinygo_sigpanic_windows(exceptionCode int32) {
|
||||
case _EXCEPTION_INT_OVERFLOW:
|
||||
runtimePanic("integer overflow")
|
||||
default:
|
||||
runtimePanic("unknown exception")
|
||||
runtimeFatal("unknown exception")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ package runtime
|
||||
|
||||
//go:linkname syscall_syscalln syscall.syscalln
|
||||
func syscall_syscalln(fn, n uintptr, args ...uintptr) (r1, r2, err uintptr) {
|
||||
panic("unreachable: syscall.syscalln should be handled by the compiler")
|
||||
runtimeFatal("unreachable: syscall.syscalln should be handled by the compiler")
|
||||
return 0, 0, 0
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ var (
|
||||
func deadlock() {
|
||||
// call yield without requesting a wakeup
|
||||
task.Pause()
|
||||
panic("unreachable")
|
||||
runtimeFatal("unreachable")
|
||||
}
|
||||
|
||||
func goexit() {
|
||||
@@ -78,7 +78,7 @@ func addSleepTask(t *task.Task, duration timeUnit) {
|
||||
if schedulerDebug {
|
||||
println(" set sleep:", t, duration)
|
||||
if t.Next != nil {
|
||||
panic("runtime: addSleepTask: expected next task to be nil")
|
||||
runtimeFatal("runtime: addSleepTask: expected next task to be nil")
|
||||
}
|
||||
}
|
||||
now := ticks()
|
||||
|
||||
@@ -54,7 +54,7 @@ func scheduleTask(t *task.Task) {
|
||||
t.RunState = task.RunStateResuming
|
||||
default:
|
||||
if schedulerAsserts {
|
||||
runtimePanic("scheduler: unknown run state")
|
||||
runtimeFatal("scheduler: unknown run state")
|
||||
}
|
||||
}
|
||||
schedulerLock.Unlock()
|
||||
|
||||
@@ -28,5 +28,5 @@ func waitForEvents() {
|
||||
pollIO(-1)
|
||||
return
|
||||
}
|
||||
runtimePanic("deadlocked: no event source")
|
||||
runtimeFatal("deadlocked: no event source")
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ func sleepTicks(d timeUnit) {
|
||||
// Without the cooperative scheduler running poll_oneoff on FDs, wasip1 has
|
||||
// nothing to wake on, so this is a hard deadlock.
|
||||
func waitForEvents() {
|
||||
runtimePanic("deadlocked: no event source")
|
||||
runtimeFatal("deadlocked: no event source")
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func sleep(duration int64) {
|
||||
|
||||
func deadlock() {
|
||||
// The only goroutine available is deadlocked.
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
|
||||
func goexit() {
|
||||
@@ -75,14 +75,14 @@ func removeTimer(tim *timer) *timerNode {
|
||||
func schedulerRunQueue() *task.Queue {
|
||||
// This function is not actually used, it is only called when hasScheduler
|
||||
// is true.
|
||||
runtimePanic("unreachable: no runqueue without a scheduler")
|
||||
runtimeFatal("unreachable: no runqueue without a scheduler")
|
||||
return nil
|
||||
}
|
||||
|
||||
func scheduler(returnAtDeadlock bool) {
|
||||
// The scheduler should never be run when using -scheduler=none. Meaning,
|
||||
// this code should be unreachable.
|
||||
runtimePanic("unreachable: scheduler must not be called with the 'none' scheduler")
|
||||
runtimeFatal("unreachable: scheduler must not be called with the 'none' scheduler")
|
||||
}
|
||||
|
||||
func lockAtomics() interrupt.State {
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
package runtime
|
||||
|
||||
func waitForEvents() {
|
||||
runtimePanic("deadlocked: no event source")
|
||||
runtimeFatal("deadlocked: no event source")
|
||||
}
|
||||
|
||||
+3
-3
@@ -6,8 +6,8 @@ import (
|
||||
|
||||
type Mutex = task.Mutex
|
||||
|
||||
//go:linkname runtimePanic runtime.runtimePanic
|
||||
func runtimePanic(msg string)
|
||||
//go:linkname runtimeFatal runtime.runtimeFatal
|
||||
func runtimeFatal(msg string)
|
||||
|
||||
type RWMutex struct {
|
||||
// Reader count, with the number of readers that currently have read-locked
|
||||
@@ -124,7 +124,7 @@ func (rw *RWMutex) RUnlock() {
|
||||
|
||||
// Check whether RUnlock was called too often.
|
||||
if readers == -1 || readers == (-rwMutexMaxReaders)-1 {
|
||||
runtimePanic("sync: RUnlock of unlocked RWMutex")
|
||||
runtimeFatal("sync: RUnlock of unlocked RWMutex")
|
||||
}
|
||||
|
||||
if readers == -rwMutexMaxReaders {
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import "sync"
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
println("recovered:", recover())
|
||||
}()
|
||||
|
||||
var mutex sync.Mutex
|
||||
mutex.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user