3.5 KiB
Error handling
Error structs
Errors are represented using hbe_err_s
structs (type hbe_err_t
). It has two fields:
code
: A value from the enumhbe_errcode
(typehbe_errcode_t
).message
: A character array (hb_char_t *
) describing the error and providing context.
Error-prone functions
Every function that may result in errors should declare hbe_err_t *hbe_err
as its first parameter.
Functions can result in errors if:
- it calls any function that may result in an error
- it sets the variable pointed to by
hbe_err
If the function needs to do cleanup operations, it should declare a finally:
label at the end of the function and put the cleanup code there. If the function returns a value, the function should start with a rv_t rv = 0;
declaration (where rv_t
is the return type), and the finally
section should end with a return rv;
.
rv
should be initialised because technically an error can occur at any time after it, including immediately afterwards.
Creating errors
To create an error, use the hbe_err_t hbe_error(hbe_errcode_t code, hb_char_t *message)
function.
The result should be set to *hbe_err
, and then the function should return.
When an error occurs, the function should return some arbitrary return value such as 0
.
Return values from a function call are not considered reliable if errors occurred during their execution.
int error_prone(hbe_err_t *hbe_err, char *msg) {
if (some_error_condition) {
*hbe_err = hbe_error(1, "Bad!");
return 0;
}
printf("%s\n", msg);
return 42;
}
To simplify this code, a macro is available:
int error_prone(hbe_err_t *hbe_err, char *msg) {
if (some_error_condition) {
HBE_THROW(1, "Bad!");
/* Translates to:
*hbe_err = hbe_error(1, "Bad!");
return 0;
*/
}
printf("%s\n", msg);
return 42;
}
If the return type is void
, use HBE_THROW_V
instead of HBE_THROW
.
If there is a cleanup section, use HBE_THROW_F
.
Handling errors
When a function call may result in an error, pass hbe_err
to the function and check if the value dereferenced is not NULL
. If it isn't, an error occurred and the callee should return.
The return value should not be used if an error occurred.
int callee(hbe_err_t *hbe_err, int a, int b) {
int meaning_of_life = error_prone(hbe_err, "Yes");
if (*hbe_err != NULL) {
// An error occurred, $meaning_of_life is unreliable
return 0;
}
return 3;
}
To simplify this code, a macro is available:
int callee(hbe_err_t *hbe_err, int a, int b) {
int meaning_of_life = HBE_CATCH(error_prone, hbe_err, "Yes");
/* Translates to:
int meaning_of_life = error_prone(hbe_err, "Yes");
if (*hbe_err != NULL) {
return 0;
}
*/
return 3;
}
If the return type is void
, use HBE_CATCH_V
instead.
If there is a cleanup section, use HBE_CATCH_F
.
Returning with cleanup
Use the macro HBE_RETURN_F
to set the return value and go to the cleanup section:
int fn(hbe_err_t *hbe_err) {
int rv = 0;
HBE_RETURN_F(1);
/* Translates to:
rv = 1;
goto finally;
*/
finally:
return rv;
}
Top-level error handler
At the very root, where the call to the first error-prone function resides, create a variable with type hbe_err_t
set to NULL
on the stack, and pass a reference to it:
After the call, if an error occurred, the variable will be set to a value other than NULL
.
int main(void) {
hbe_err_t err = NULL;
fn(&err);
if (err != NULL) {
// An error occurred
}
}