134 lines
3.0 KiB
Markdown
134 lines
3.0 KiB
Markdown
# Error Handling System
|
|
|
|
Source: `src/dusk/error/`
|
|
|
|
## Philosophy
|
|
|
|
Error handling is return-value based. Functions that can fail return
|
|
`errorret_t`. There are no exceptions, `errno`, `setjmp`, or global
|
|
error codes. Each thread has its own isolated error state.
|
|
|
|
## Types
|
|
|
|
```c
|
|
typedef uint8_t errorcode_t;
|
|
|
|
typedef struct {
|
|
errorcode_t code;
|
|
char_t *message; // allocated; freed by errorCatch
|
|
char_t *lines; // call-stack trace; allocated; freed by errorCatch
|
|
} errorstate_t;
|
|
|
|
typedef struct {
|
|
errorcode_t code;
|
|
errorstate_t *state; // NULL on success
|
|
} errorret_t;
|
|
```
|
|
|
|
**Constants:** `ERROR_OK = 0`, `ERROR_NOT_OK = 1`.
|
|
|
|
Error state is thread-local:
|
|
|
|
```c
|
|
extern THREAD_LOCAL errorstate_t ERROR_STATE;
|
|
```
|
|
|
|
Each thread has its own `ERROR_STATE` so concurrent errors never
|
|
interfere.
|
|
|
|
## Macros
|
|
|
|
### Throwing an error
|
|
|
|
```c
|
|
errorThrow("message %d", value);
|
|
// Throws with ERROR_NOT_OK, captures __FILE__ / __func__ / __LINE__.
|
|
|
|
errorThrowWithCode(code, "message %d", value);
|
|
// Same but with a specific error code.
|
|
```
|
|
|
|
Both macros **return from the current function** with an `errorret_t`.
|
|
Do not call them in void functions.
|
|
|
|
### Propagating up the call stack
|
|
|
|
```c
|
|
errorChain(someCall());
|
|
```
|
|
|
|
If `someCall()` returned an error, appends the current location to the
|
|
stack trace and **returns** that error from the current function.
|
|
If `someCall()` returned success, execution continues normally.
|
|
|
|
### Returning success
|
|
|
|
```c
|
|
errorOk();
|
|
```
|
|
|
|
Returns `errorret_t` with `code == ERROR_OK` and asserts the thread's
|
|
`ERROR_STATE` is clean (no leftover error). Must be the last statement
|
|
in a fallible function.
|
|
|
|
### Inspecting a result
|
|
|
|
```c
|
|
if(errorIsOk(ret)) { ... }
|
|
if(errorIsNotOk(ret)) { ... }
|
|
```
|
|
|
|
### Cleaning up
|
|
|
|
```c
|
|
errorCatch(ret);
|
|
```
|
|
|
|
Frees `ret.state->message` and `ret.state->lines`, resets the thread's
|
|
`ERROR_STATE.code` to `ERROR_OK`. Safe to call on a success return
|
|
(no-op). **Always call `errorCatch` on errors you are handling** --
|
|
otherwise the allocated message and stack-trace leak.
|
|
|
|
### Logging
|
|
|
|
```c
|
|
errorPrint(ret); // prints code + message + stack trace, returns ret
|
|
```
|
|
|
|
## Typical patterns
|
|
|
|
### Fallible function
|
|
|
|
```c
|
|
errorret_t myFunction(int_t x) {
|
|
if(x < 0) errorThrow("x must be non-negative, got %d", x);
|
|
errorChain(someOtherFallibleCall(x));
|
|
errorOk();
|
|
}
|
|
```
|
|
|
|
### Caller that handles errors
|
|
|
|
```c
|
|
errorret_t ret = myFunction(-1);
|
|
if(errorIsNotOk(ret)) {
|
|
errorCatch(errorPrint(ret));
|
|
// ... fallback logic ...
|
|
}
|
|
```
|
|
|
|
### Stack trace accumulation
|
|
|
|
Each `errorChain()` call appends a line to `ret.state->lines` in the
|
|
format ` at file:line in function\n`. A deeply chained error produces
|
|
a full call path readable from `ret.state->lines`.
|
|
|
|
## What NOT to do
|
|
|
|
- Do not use raw `errno` for in-engine errors.
|
|
- Do not return an error code integer -- always return `errorret_t`.
|
|
- Do not ignore an error return without calling `errorCatch` on it if
|
|
you are not propagating it.
|
|
- Do not mix assertions with error handling. Assertions are for
|
|
programmer mistakes; `errorThrow` is for expected failure paths.
|