github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/panic.go (about) 1 package runtime 2 3 import ( 4 "internal/task" 5 "unsafe" 6 ) 7 8 // trap is a compiler hint that this function cannot be executed. It is 9 // translated into either a trap instruction or a call to abort(). 10 // 11 //export llvm.trap 12 func trap() 13 14 // Inline assembly stub. It is essentially C longjmp but modified a bit for the 15 // purposes of TinyGo. It restores the stack pointer and jumps to the given pc. 16 // 17 //export tinygo_longjmp 18 func tinygo_longjmp(frame *deferFrame) 19 20 // Compiler intrinsic. 21 // Returns whether recover is supported on the current architecture. 22 func supportsRecover() bool 23 24 const ( 25 panicStrategyPrint = 1 26 panicStrategyTrap = 2 27 ) 28 29 // Compile intrinsic. 30 // Returns which strategy is used. This is usually "print" but can be changed 31 // using the -panic= compiler flag. 32 func panicStrategy() uint8 33 34 // DeferFrame is a stack allocated object that stores information for the 35 // current "defer frame", which is used in functions that use the `defer` 36 // keyword. 37 // The compiler knows about the JumpPC struct offset, so it should not be moved 38 // without also updating compiler/defer.go. 39 type deferFrame struct { 40 JumpSP unsafe.Pointer // stack pointer to return to 41 JumpPC unsafe.Pointer // pc to return to 42 ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) 43 Previous *deferFrame // previous recover buffer pointer 44 Panicking bool // true iff this defer frame is panicking 45 PanicValue interface{} // panic value, might be nil for panic(nil) for example 46 } 47 48 // Builtin function panic(msg), used as a compiler intrinsic. 49 func _panic(message interface{}) { 50 if panicStrategy() == panicStrategyTrap { 51 trap() 52 } 53 if supportsRecover() { 54 frame := (*deferFrame)(task.Current().DeferFrame) 55 if frame != nil { 56 frame.PanicValue = message 57 frame.Panicking = true 58 tinygo_longjmp(frame) 59 // unreachable 60 } 61 } 62 printstring("panic: ") 63 printitf(message) 64 printnl() 65 abort() 66 } 67 68 // Cause a runtime panic, which is (currently) always a string. 69 func runtimePanic(msg string) { 70 // As long as this function is inined, llvm.returnaddress(0) will return 71 // something sensible. 72 runtimePanicAt(returnAddress(0), msg) 73 } 74 75 func runtimePanicAt(addr unsafe.Pointer, msg string) { 76 if panicStrategy() == panicStrategyTrap { 77 trap() 78 } 79 if hasReturnAddr { 80 printstring("panic: runtime error at ") 81 printptr(uintptr(addr) - callInstSize) 82 printstring(": ") 83 } else { 84 printstring("panic: runtime error: ") 85 } 86 println(msg) 87 abort() 88 } 89 90 // Called at the start of a function that includes a deferred call. 91 // It gets passed in the stack-allocated defer frame and configures it. 92 // Note that the frame is not zeroed yet, so we need to initialize all values 93 // that will be used. 94 // 95 //go:inline 96 //go:nobounds 97 func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { 98 currentTask := task.Current() 99 frame.Previous = (*deferFrame)(currentTask.DeferFrame) 100 frame.JumpSP = jumpSP 101 frame.Panicking = false 102 currentTask.DeferFrame = unsafe.Pointer(frame) 103 } 104 105 // Called right before the return instruction. It pops the defer frame from the 106 // linked list of defer frames. It also re-raises a panic if the goroutine is 107 // still panicking. 108 // 109 //go:inline 110 //go:nobounds 111 func destroyDeferFrame(frame *deferFrame) { 112 task.Current().DeferFrame = unsafe.Pointer(frame.Previous) 113 if frame.Panicking { 114 // We're still panicking! 115 // Re-raise the panic now. 116 _panic(frame.PanicValue) 117 } 118 } 119 120 // _recover is the built-in recover() function. It tries to recover a currently 121 // panicking goroutine. 122 // useParentFrame is set when the caller of runtime._recover has a defer frame 123 // itself. In that case, recover() shouldn't check that frame but one frame up. 124 func _recover(useParentFrame bool) interface{} { 125 if !supportsRecover() { 126 // Compiling without stack unwinding support, so make this a no-op. 127 return nil 128 } 129 // TODO: somehow check that recover() is called directly by a deferred 130 // function in a panicking goroutine. Maybe this can be done by comparing 131 // the frame pointer? 132 frame := (*deferFrame)(task.Current().DeferFrame) 133 if useParentFrame { 134 // Don't recover panic from the current frame (which can't be panicking 135 // already), but instead from the previous frame. 136 frame = frame.Previous 137 } 138 if frame != nil && frame.Panicking { 139 // Only the first call to recover returns the panic value. It also stops 140 // the panicking sequence, hence setting panicking to false. 141 frame.Panicking = false 142 return frame.PanicValue 143 } 144 // Not panicking, so return a nil interface. 145 return nil 146 } 147 148 // Panic when trying to dereference a nil pointer. 149 func nilPanic() { 150 runtimePanicAt(returnAddress(0), "nil pointer dereference") 151 } 152 153 // Panic when trying to add an entry to a nil map 154 func nilMapPanic() { 155 runtimePanicAt(returnAddress(0), "assignment to entry in nil map") 156 } 157 158 // Panic when trying to acces an array or slice out of bounds. 159 func lookupPanic() { 160 runtimePanicAt(returnAddress(0), "index out of range") 161 } 162 163 // Panic when trying to slice a slice out of bounds. 164 func slicePanic() { 165 runtimePanicAt(returnAddress(0), "slice out of range") 166 } 167 168 // Panic when trying to convert a slice to an array pointer (Go 1.17+) and the 169 // slice is shorter than the array. 170 func sliceToArrayPointerPanic() { 171 runtimePanicAt(returnAddress(0), "slice smaller than array") 172 } 173 174 // Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+) 175 // with a len that's too large (which includes if the ptr is nil and len is 176 // nonzero). 177 func unsafeSlicePanic() { 178 runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range") 179 } 180 181 // Panic when trying to create a new channel that is too big. 182 func chanMakePanic() { 183 runtimePanicAt(returnAddress(0), "new channel is too big") 184 } 185 186 // Panic when a shift value is negative. 187 func negativeShiftPanic() { 188 runtimePanicAt(returnAddress(0), "negative shift") 189 } 190 191 // Panic when there is a divide by zero. 192 func divideByZeroPanic() { 193 runtimePanicAt(returnAddress(0), "divide by zero") 194 } 195 196 func blockingPanic() { 197 runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function") 198 }