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  }