mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-07 20:43:40 +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:
@@ -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).
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user