Cleanup the code and unified everything (except the macros) under the akerr_ namespace
This commit is contained in:
@@ -4,7 +4,7 @@ project(akerror LANGUAGES C)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
set(AKERROR_USE_STDLIB 1 CACHE BOOL "Use the C standard library")
|
||||
set(AKERR_USE_STDLIB 1 CACHE BOOL "Use the C standard library")
|
||||
set(akerror_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akerror")
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
@@ -13,7 +13,7 @@ add_library(akerror STATIC
|
||||
)
|
||||
|
||||
target_compile_definitions(akerror
|
||||
PUBLIC AKERROR_USE_STDLIB=${AKERROR_USE_STDLIB}
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB}
|
||||
)
|
||||
|
||||
add_executable(test_err_catch tests/err_catch.c)
|
||||
|
||||
78
README.md
78
README.md
@@ -25,16 +25,16 @@ This library has 6 guiding principles:
|
||||
|
||||
## Lifecycle of an error in the AKError library
|
||||
|
||||
TL;DR - `ErrorContext` objects are filled with error context information and bubbled up through nested control structures until they are handled or reach the top level, where an unhandled error halts program termination with a stack trace
|
||||
TL;DR - `akerr_ErrorContext` objects are filled with error context information and bubbled up through nested control structures until they are handled or reach the top level, where an unhandled error halts program termination with a stack trace
|
||||
|
||||
1. At the point where an error occurs, an `ErrorContext` object is created and populated with information regarding the failure
|
||||
2. The ErrorContext is returned from the scope where the error was detected
|
||||
3. The ErrorContext enters a control structure provided by the AKError library through a series of macros that examine `ErrorContext` objects as they pass through
|
||||
4. The control structure checks to see if the `ErrorContext` has an error set, and if so, if there are any handlers in the current control structure that can handle it
|
||||
5. If the current control structure can handle the `ErrorContext`, it does so
|
||||
6. If the current control structure can not handle the `ErrorContext`, then the current control structure's cleanup code (if any) is executed, and the `ErrorContext` object is passed out of the current control structure to the parent control structure
|
||||
1. At the point where an error occurs, an `akerr_ErrorContext` object is created and populated with information regarding the failure
|
||||
2. The akerr_ErrorContext is returned from the scope where the error was detected
|
||||
3. The akerr_ErrorContext enters a control structure provided by the AKError library through a series of macros that examine `akerr_ErrorContext` objects as they pass through
|
||||
4. The control structure checks to see if the `akerr_ErrorContext` has an error set, and if so, if there are any handlers in the current control structure that can handle it
|
||||
5. If the current control structure can handle the `akerr_ErrorContext`, it does so
|
||||
6. If the current control structure can not handle the `akerr_ErrorContext`, then the current control structure's cleanup code (if any) is executed, and the `akerr_ErrorContext` object is passed out of the current control structure to the parent control structure
|
||||
7. Steps 2-6 are repeated through as many control structures as are necessary to reach the first level of the control structure
|
||||
8. When the first level of the control structure is reached, if the `ErrorContext` has an error set in it, then the stack trace information in the `ErrorContext` object is used to print a stack trace using the configured logging function, and program termination is halted
|
||||
8. When the first level of the control structure is reached, if the `akerr_ErrorContext` has an error set in it, then the stack trace information in the `akerr_ErrorContext` object is used to print a stack trace using the configured logging function, and program termination is halted
|
||||
|
||||
## What is in an Error Context
|
||||
|
||||
@@ -50,25 +50,25 @@ The structure also contains housekeeping information for the library which are o
|
||||
|
||||
## What are the control structures
|
||||
|
||||
The library is structured around a series of macros that construct `switch` statements that perform logic against an `ErrorContext` which exists in the current scope and has been initialized. These macros must be assembled in a specific order to produce a syntactically correct `switch` statement which performs correct operations against the `ErrorContext` to attempt operations, detect failures, perform cleanup operations, handle errors, and then exit a given scope in a success or failure state.
|
||||
The library is structured around a series of macros that construct `switch` statements that perform logic against an `akerr_ErrorContext` which exists in the current scope and has been initialized. These macros must be assembled in a specific order to produce a syntactically correct `switch` statement which performs correct operations against the `akerr_ErrorContext` to attempt operations, detect failures, perform cleanup operations, handle errors, and then exit a given scope in a success or failure state.
|
||||
|
||||
## Functions and Return Codes
|
||||
|
||||
This library can catch errors from any function or expression that returns an integer value, or from functions that return `ErrorContext *`.
|
||||
This library can catch errors from any function or expression that returns an integer value, or from functions that return `akerr_ErrorContext *`.
|
||||
|
||||
Any function which uses the `PREPARE_ERROR` macro should have a return type of `ErrorContext *`. The macros within this library, when they detect an unhandled error, will attempt to pass up the unhandled error to the context of the previous function in the call stack. This allows for errors to propagate up through the call stack in the same way as exceptions. (For example, if you use traditional C error handling in a call stack of `a() -> b() -> c()`, and `c()` fails because it runs out of memory, `b()` will likely detect that error and return some error to `a()`, but it may or may not return the context of what failed and why. With this, you get that context all the way up in `a()` without knowing anything about `c()`.
|
||||
Any function which uses the `PREPARE_ERROR` macro should have a return type of `akerr_ErrorContext *`. The macros within this library, when they detect an unhandled error, will attempt to pass up the unhandled error to the context of the previous function in the call stack. This allows for errors to propagate up through the call stack in the same way as exceptions. (For example, if you use traditional C error handling in a call stack of `a() -> b() -> c()`, and `c()` fails because it runs out of memory, `b()` will likely detect that error and return some error to `a()`, but it may or may not return the context of what failed and why. With this, you get that context all the way up in `a()` without knowing anything about `c()`.
|
||||
|
||||
## Error codes
|
||||
|
||||
The library uses integer values to specify error codes inside of its context. These integer return codes are defined in `akerror.h` in the form of `ERR_xxxxx` where `xxxxx` is the name of the error code in question. See `akerror.h` for a list of defined errors and their descriptions.
|
||||
The library uses integer values to specify error codes inside of its context. These integer return codes are defined in `akerror.h` in the form of `AKERR_xxxxx` where `xxxxx` is the name of the error code in question. See `akerror.h` for a list of defined errors and their descriptions.
|
||||
|
||||
You can define additional error types by defining additional `ERR_xxxxx` values. Error values up to 127 are reserved by the library, so begin your error values at 128. Define a human-friendly name for the error with the `error_name_for_status` method:
|
||||
You can define additional error types by defining additional `AKERR_xxxxx` values. Error values up to 127 are reserved by the library, so begin your error values at 128. Define a human-friendly name for the error with the `error_name_for_status` method:
|
||||
|
||||
```c
|
||||
error_name_for_status(129, "Some Error Code Description")
|
||||
```
|
||||
|
||||
When you add additional error codes, you need to define `-DMAX_ERR_VALUE=n` to the compiler, where `n` is the maximum error code you have defined.
|
||||
When you add additional error codes, you need to define `-DAKERR_MAX_ERR_VALUE=n` to the compiler, where `n` is the maximum error code you have defined.
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -92,7 +92,7 @@ This library depends upon `stdlib`. If you don't want to link against stdlib, yo
|
||||
... then you can compile it thusly:
|
||||
|
||||
```
|
||||
cmake -S . -B build -DAKERROR_USE_STDLIB=OFF
|
||||
cmake -S . -B build -DAKERR_USE_STDLIB=OFF
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
@@ -154,7 +154,7 @@ Before you can use any of these macros you must set up an error context inside o
|
||||
PREPARE_ERROR(errctx);
|
||||
```
|
||||
|
||||
This will create a ErrorContext structure inside of the current scope named `errctx` and initialize it. This structure is used for all operations of the library within the current scope. Attempting to use the library in a given scope before calling this will result in compile-time errors.
|
||||
This will create a akerr_ErrorContext structure inside of the current scope named `errctx` and initialize it. This structure is used for all operations of the library within the current scope. Attempting to use the library in a given scope before calling this will result in compile-time errors.
|
||||
|
||||
## Attempting an Operation
|
||||
|
||||
@@ -172,15 +172,15 @@ ATTEMPT {
|
||||
|
||||
`PROCESS(errctx) { ... }` is the block within which you will handle any errors that were caught inside of the `ATTEMPT` block. See "Handling Errors" below.
|
||||
|
||||
`FINISH(errctx, true)` terminates the attempt operation. The `FINISH` macro takes two arguments: the name of the ErrorContext, and a boolean regarding whether or not to pass unhandled errors up to the calling function. Unless you have a good reason not to, this should be true.
|
||||
`FINISH(errctx, true)` terminates the attempt operation. The `FINISH` macro takes two arguments: the name of the akerr_ErrorContext, and a boolean regarding whether or not to pass unhandled errors up to the calling function. Unless you have a good reason not to, this should be true.
|
||||
|
||||
# Capturing errors
|
||||
|
||||
Inside of an `ATTEMPT` block, any operation which could generate or represent an error should be wrapped in one of several macros.
|
||||
|
||||
## Capturing errors from functions which return ErrorContext *
|
||||
## Capturing errors from functions which return akerr_ErrorContext *
|
||||
|
||||
For functions that return `ErrorContext *`, you should use the `CATCH` macro.
|
||||
For functions that return `akerr_ErrorContext *`, you should use the `CATCH` macro.
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
@@ -188,7 +188,7 @@ ATTEMPT {
|
||||
} // ...
|
||||
```
|
||||
|
||||
This will assign the return value of the function in question to the ErrorContext previously prepared in the current scope. If the function returns an ErrorContext that indicates any type of error, the `ATTEMPT` block is immediately exited, and the `CLEANUP` block begins.
|
||||
This will assign the return value of the function in question to the akerr_ErrorContext previously prepared in the current scope. If the function returns an akerr_ErrorContext that indicates any type of error, the `ATTEMPT` block is immediately exited, and the `CLEANUP` block begins.
|
||||
|
||||
## Setting errors from functions or expressions returning integer
|
||||
|
||||
@@ -198,7 +198,7 @@ Here is an example of checking for a NULL pointer
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(errctx, (somePointer == NULL), ERR_NULLPOINTER, "Someone gave me a NULL pointer")
|
||||
FAIL_ZERO_BREAK(errctx, (somePointer == NULL), AKERR_NULLPOINTER, "Someone gave me a NULL pointer")
|
||||
} // ...
|
||||
```
|
||||
|
||||
@@ -206,7 +206,7 @@ Here is an example of checking for two strings that are not equal
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp("not", "equal"), ERR_VALUE, "Strings are not equal")
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp("not", "equal"), AKERR_VALUE, "Strings are not equal")
|
||||
} // ...
|
||||
```
|
||||
|
||||
@@ -222,7 +222,7 @@ In order to handle a specific error code, use the `HANDLE` macro.
|
||||
|
||||
```c
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_NULLPOINTER) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// Something is complaining about a null pointer error. Do something about it.
|
||||
} // ...
|
||||
```
|
||||
@@ -233,35 +233,35 @@ In order to handle a group of related errors that all require the same failure b
|
||||
|
||||
```c
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_IO) {
|
||||
} HANDLE_GROUP(errctx, ERR_KEY) {
|
||||
} HANDLE_GROUP(errctx, ERR_INDEX) {
|
||||
} HANDLE(errctx, AKERR_IO) {
|
||||
} HANDLE_GROUP(errctx, AKERR_KEY) {
|
||||
} HANDLE_GROUP(errctx, AKERR_INDEX) {
|
||||
// error handling code goes here
|
||||
}
|
||||
```
|
||||
|
||||
This creates a fallthrough mechanism where all 3 errors get the same error handling code. Note that while the cases fall through, you can still (if desired) put some code specific to each error in that error's `HANDLE` or `HANDLE_GROUP` block; but this is not required, only the final handler needs to get any code.
|
||||
|
||||
The fallthrough behavior stops as soon as another `HANDLE` macro is encountered. For example, in this example, `ERR_IO`, `ERR_KEY` and `ERR_INDEX` are all handled as a group, but `ERR_RELATIONSHIP` is not.
|
||||
The fallthrough behavior stops as soon as another `HANDLE` macro is encountered. For example, in this example, `AKERR_IO`, `AKERR_KEY` and `AKERR_INDEX` are all handled as a group, but `AKERR_RELATIONSHIP` is not.
|
||||
|
||||
```c
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_IO) {
|
||||
} HANDLE_GROUP(errctx, ERR_KEY) {
|
||||
} HANDLE_GROUP(errctx, ERR_INDEX) {
|
||||
} HANDLE(errctx, AKERR_IO) {
|
||||
} HANDLE_GROUP(errctx, AKERR_KEY) {
|
||||
} HANDLE_GROUP(errctx, AKERR_INDEX) {
|
||||
// This code handles 3 error cases
|
||||
} HANDLE(errctx, ERR_RELATIONSHIP) {
|
||||
} HANDLE(errctx, AKERR_RELATIONSHIP) {
|
||||
// This code handles 1 error case
|
||||
}
|
||||
```
|
||||
|
||||
# Returning success or failure from functions returning ErrorContext *
|
||||
# Returning success or failure from functions returning akerr_ErrorContext *
|
||||
|
||||
If at all possible, when using this library, your functiions should return `ErrorContext *`. When returning from such functions, you should use the `SUCCEED_RETURN` and `FAIL_RETURN` macros.
|
||||
If at all possible, when using this library, your functiions should return `akerr_ErrorContext *`. When returning from such functions, you should use the `SUCCEED_RETURN` and `FAIL_RETURN` macros.
|
||||
|
||||
## SUCCEED_RETURN
|
||||
|
||||
This macro is used when your function has reached the end of its happy code path and is prepared to exit successfully. This sets the ErrorContext to a successful state and exits the function.
|
||||
This macro is used when your function has reached the end of its happy code path and is prepared to exit successfully. This sets the akerr_ErrorContext to a successful state and exits the function.
|
||||
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -281,7 +281,7 @@ The function allows you to provide printf-style variable arguments to provide a
|
||||
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_RETURN(ERR_BEHAVIOR, "Something went horribly wrong!")
|
||||
FAIL_RETURN(AKERR_BEHAVIOR, "Something went horribly wrong!")
|
||||
```
|
||||
|
||||
## Conditionally failing and returning
|
||||
@@ -290,22 +290,22 @@ In addition to `FAIL_RETURN` you can also test for zero or non-zero conditions,
|
||||
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, (somePointer == NULL), ERR_NULLPOINTER, "Someone gave me a NULL pointer")
|
||||
FAIL_ZERO_RETURN(errctx, (somePointer == NULL), AKERR_NULLPOINTER, "Someone gave me a NULL pointer")
|
||||
```
|
||||
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp("not", "equal"), ERR_VALUE, "Strings are not equal")
|
||||
FAIL_NONZERO_RETURN(errctx, strcmp("not", "equal"), AKERR_VALUE, "Strings are not equal")
|
||||
```
|
||||
|
||||
# Uncaught errors
|
||||
|
||||
## Ensuring that all error codes are captured
|
||||
|
||||
Any function which returns `ErrorContext *` should also be marked with `ERROR_NOIGNORE`.
|
||||
Any function which returns `akerr_ErrorContext *` should also be marked with `ERROR_NOIGNORE`.
|
||||
|
||||
```c
|
||||
ErrorContext ERROR_NOIGNORE *f(...);
|
||||
akerr_ErrorContext ERROR_NOIGNORE *f(...);
|
||||
```
|
||||
|
||||
This will cause a compile-time error if the return value of such a function is not used. "Used" here means assigned to a variable - it does not necessarily mean that the value is checked. However assuming that such functions are called inside of `ATTEMPT { ... }` blocks, it is safe to assume that such returns will be caught with `CATCH(...)`; therefore this error is a generally effective safeguard against careless coding where errors are not checked.
|
||||
|
||||
@@ -1,102 +1,102 @@
|
||||
#ifndef _ERROR_H_
|
||||
#define _ERROR_H_
|
||||
#ifndef _AKERR_H_
|
||||
#define _AKERR_H_
|
||||
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_USE_STDLIB == 1
|
||||
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#define MAX_ERROR_CONTEXT_STRING_LENGTH 1024
|
||||
#define MAX_ERROR_NAME_LENGTH 64
|
||||
#define MAX_ERROR_FNAME_LENGTH 256
|
||||
#define MAX_ERROR_FUNCTION_LENGTH 128
|
||||
#define MAX_ERROR_STACKTRACE_BUF_LENGTH 2048
|
||||
#define AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH 1024
|
||||
#define AKERR_MAX_ERROR_NAME_LENGTH 64
|
||||
#define AKERR_MAX_ERROR_FNAME_LENGTH 256
|
||||
#define AKERR_MAX_ERROR_FUNCTION_LENGTH 128
|
||||
#define AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH 2048
|
||||
|
||||
#define ERR_NULLPOINTER 1
|
||||
#define ERR_OUTOFBOUNDS 2
|
||||
#define ERR_API 3
|
||||
#define ERR_ATTRIBUTE 4
|
||||
#define ERR_TYPE 5
|
||||
#define ERR_KEY 6
|
||||
#define ERR_HEAP 7
|
||||
#define ERR_INDEX 8
|
||||
#define ERR_FORMAT 9
|
||||
#define ERR_IO 10
|
||||
#define ERR_REGISTRY 11
|
||||
#define ERR_VALUE 12
|
||||
#define ERR_BEHAVIOR 13
|
||||
#define ERR_RELATIONSHIP 14
|
||||
#define AKERR_NULLPOINTER 1
|
||||
#define AKERR_OUTOFBOUNDS 2
|
||||
#define AKERR_API 3
|
||||
#define AKERR_ATTRIBUTE 4
|
||||
#define AKERR_TYPE 5
|
||||
#define AKERR_KEY 6
|
||||
#define AKERR_HEAP 7
|
||||
#define AKERR_INDEX 8
|
||||
#define AKERR_FORMAT 9
|
||||
#define AKERR_IO 10
|
||||
#define AKERR_REGISTRY 11
|
||||
#define AKERR_VALUE 12
|
||||
#define AKERR_BEHAVIOR 13
|
||||
#define AKERR_RELATIONSHIP 14
|
||||
|
||||
#ifndef MAX_ERR_VALUE
|
||||
#define MAX_ERR_VALUE 14
|
||||
#ifndef AKERR_MAX_ERR_VALUE
|
||||
#define AKERR_MAX_ERR_VALUE 14
|
||||
#endif
|
||||
|
||||
extern char __ERROR_NAMES[MAX_ERR_VALUE+1][MAX_ERROR_NAME_LENGTH];
|
||||
extern char __AKERR_ERROR_NAMES[AKERR_MAX_ERR_VALUE+1][AKERR_MAX_ERROR_NAME_LENGTH];
|
||||
|
||||
#define MAX_ARRAY_ERROR 128
|
||||
#define AKERR_MAX_ARRAY_ERROR 128
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char message[MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
int arrayid;
|
||||
int status;
|
||||
bool handled;
|
||||
int refcount;
|
||||
char fname[MAX_ERROR_FNAME_LENGTH];
|
||||
char function[MAX_ERROR_FNAME_LENGTH];
|
||||
char fname[AKERR_MAX_ERROR_FNAME_LENGTH];
|
||||
char function[AKERR_MAX_ERROR_FNAME_LENGTH];
|
||||
int lineno;
|
||||
bool reported;
|
||||
char stacktracebuf[MAX_ERROR_STACKTRACE_BUF_LENGTH];
|
||||
char stacktracebuf[AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH];
|
||||
char *stacktracebufptr;
|
||||
} ErrorContext;
|
||||
} akerr_ErrorContext;
|
||||
|
||||
#define ERROR_NOIGNORE __attribute__((warn_unused_result))
|
||||
#define AKERR_NOIGNORE __attribute__((warn_unused_result))
|
||||
|
||||
typedef void (*ErrorUnhandledErrorHandler)(ErrorContext *errctx);
|
||||
typedef void (*ErrorLogFunction)(const char *f, ...);
|
||||
typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx);
|
||||
typedef void (*akerr_ErrorLogFunction)(const char *f, ...);
|
||||
|
||||
extern ErrorContext ARRAY_ERROR[MAX_ARRAY_ERROR];
|
||||
extern ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
extern ErrorLogFunction error_log_method;
|
||||
extern ErrorContext *__error_last_ignored;
|
||||
extern akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
||||
extern akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error;
|
||||
extern akerr_ErrorLogFunction akerr_log_method;
|
||||
extern akerr_ErrorContext *__akerr_last_ignored;
|
||||
|
||||
ErrorContext ERROR_NOIGNORE *array_release_error(ErrorContext *ptr);
|
||||
ErrorContext ERROR_NOIGNORE *array_next_error();
|
||||
char *error_name_for_status(int status, char *name);
|
||||
void error_init();
|
||||
void error_default_handler_unhandled_error(ErrorContext *ptr);
|
||||
void error_default_logger(const char *f, ...);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akerr_release_error(akerr_ErrorContext *ptr);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akerr_next_error();
|
||||
char *akerr_name_for_status(int status, char *name);
|
||||
void akerr_init();
|
||||
void akerr_default_handler_unhandled_error(akerr_ErrorContext *ptr);
|
||||
void akerr_default_logger(const char *f, ...);
|
||||
|
||||
#define LOG_ERROR_WITH_MESSAGE(__err_context, __err_message) \
|
||||
error_log_method("%s%s:%s:%d: %s %d (%s): %s", (char *)&__err_context->stacktracebuf, (char *)__FILE__, (char *)__func__, __LINE__, __err_message, __err_context->status, error_name_for_status(__err_context->status, NULL), __err_context->message); \
|
||||
akerr_log_method("%s%s:%s:%d: %s %d (%s): %s", (char *)&__err_context->stacktracebuf, (char *)__FILE__, (char *)__func__, __LINE__, __err_message, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message); \
|
||||
|
||||
#define LOG_ERROR(__err_context) \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "");
|
||||
|
||||
#define RELEASE_ERROR(__err_context) \
|
||||
if ( __err_context != NULL ) { \
|
||||
__err_context = array_release_error(__err_context); \
|
||||
__err_context = akerr_release_error(__err_context); \
|
||||
}
|
||||
|
||||
#define PREPARE_ERROR(__err_context) \
|
||||
error_init(); \
|
||||
ErrorContext __attribute__ ((unused)) *__err_context = NULL;
|
||||
akerr_init(); \
|
||||
akerr_ErrorContext __attribute__ ((unused)) *__err_context = NULL;
|
||||
|
||||
#define ENSURE_ERROR_READY(__err_context) \
|
||||
if ( __err_context == NULL ) { \
|
||||
__err_context = array_next_error(); \
|
||||
__err_context = akerr_next_error(); \
|
||||
if ( __err_context == NULL ) { \
|
||||
error_log_method("%s:%s:%d: Unable to pull an ErrorContext from the array!", __FILE__, (char *)__func__, __LINE__); \
|
||||
akerr_log_method("%s:%s:%d: Unable to pull an error context from the array!", __FILE__, (char *)__func__, __LINE__); \
|
||||
exit(1); \
|
||||
} \
|
||||
} \
|
||||
__err_context->refcount += 1;
|
||||
|
||||
/*
|
||||
* Failure and success methods for functions that return ErrorContext *
|
||||
* Failure and success methods for functions that return akerr_ErrorContext *
|
||||
*/
|
||||
|
||||
#define FAIL_ZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
||||
@@ -150,11 +150,11 @@ void error_default_logger(const char *f, ...);
|
||||
#define FAIL(__err_context, __err, __message, ...) \
|
||||
ENSURE_ERROR_READY(__err_context); \
|
||||
__err_context->status = __err; \
|
||||
snprintf((char *)__err_context->fname, MAX_ERROR_FNAME_LENGTH, __FILE__); \
|
||||
snprintf((char *)__err_context->function, MAX_ERROR_FUNCTION_LENGTH, __func__); \
|
||||
snprintf((char *)__err_context->fname, AKERR_MAX_ERROR_FNAME_LENGTH, __FILE__); \
|
||||
snprintf((char *)__err_context->function, AKERR_MAX_ERROR_FUNCTION_LENGTH, __func__); \
|
||||
__err_context->lineno = __LINE__; \
|
||||
snprintf((char *)__err_context->message, MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, error_name_for_status(__err_context->status, NULL), __err_context->message);
|
||||
snprintf((char *)__err_context->message, AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: %d (%s) : %s\n", (char *)__err_context->fname, (char *)__err_context->function, __err_context->lineno, __err_context->status, akerr_name_for_status(__err_context->status, NULL), __err_context->message);
|
||||
|
||||
|
||||
#define SUCCEED(__err_context) \
|
||||
@@ -172,9 +172,9 @@ void error_default_logger(const char *f, ...);
|
||||
#define DETECT(__err_context, __stmt) \
|
||||
__stmt; \
|
||||
if ( __err_context != NULL ) { \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: Detected error %d from array (refcount %d)\n", (char *)__FILE__, (char *)__func__, __LINE__, __err_context->arrayid, __err_context->refcount); \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d: Detected error %d from array (refcount %d)\n", (char *)__FILE__, (char *)__func__, __LINE__, __err_context->arrayid, __err_context->refcount); \
|
||||
if ( __err_context->status != 0 ) { \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
@@ -183,9 +183,9 @@ void error_default_logger(const char *f, ...);
|
||||
DETECT(__err_context, __err_context = __stmt);
|
||||
|
||||
#define IGNORE(__stmt) \
|
||||
__error_last_ignored = __stmt; \
|
||||
if ( __error_last_ignored != NULL ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__error_last_ignored, "** IGNORED ERROR **"); \
|
||||
__akerr_last_ignored = __stmt; \
|
||||
if ( __akerr_last_ignored != NULL ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__akerr_last_ignored, "** IGNORED ERROR **"); \
|
||||
}
|
||||
|
||||
#define CLEANUP \
|
||||
@@ -219,7 +219,7 @@ void error_default_logger(const char *f, ...);
|
||||
}; \
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false && __pass_up == true ) { \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
__err_context->stacktracebufptr += snprintf(__err_context->stacktracebufptr, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
return __err_context; \
|
||||
} \
|
||||
} \
|
||||
@@ -231,7 +231,7 @@ void error_default_logger(const char *f, ...);
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "Unhandled Error"); \
|
||||
error_handler_unhandled_error(__err_context); \
|
||||
akerr_handler_unhandled_error(__err_context); \
|
||||
} \
|
||||
} \
|
||||
RELEASE_ERROR(__err_context);
|
||||
@@ -244,4 +244,4 @@ void error_default_logger(const char *f, ...);
|
||||
} FINISH(__err_context, true);
|
||||
|
||||
|
||||
#endif // _ERROR_H_
|
||||
#endif // _AKERR_H_
|
||||
|
||||
100
src/error.c
100
src/error.c
@@ -1,22 +1,22 @@
|
||||
#include "akerror.h"
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_USE_STDLIB == 1
|
||||
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#endif // AKERROR_USE_STDLIB
|
||||
#endif // AKERR_USE_STDLIB
|
||||
|
||||
ErrorContext __error_last_ditch;
|
||||
ErrorContext *__error_last_ignored;
|
||||
ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
ErrorLogFunction error_log_method = NULL;
|
||||
akerr_ErrorContext __akerr_last_ditch;
|
||||
akerr_ErrorContext *__akerr_last_ignored;
|
||||
akerr_ErrorUnhandledErrorHandler akerr_handler_unhandled_error;
|
||||
akerr_ErrorLogFunction akerr_log_method = NULL;
|
||||
|
||||
char __ERROR_NAMES[MAX_ERR_VALUE+1][MAX_ERROR_NAME_LENGTH];
|
||||
char __AKERR_ERROR_NAMES[AKERR_MAX_ERR_VALUE+1][AKERR_MAX_ERROR_NAME_LENGTH];
|
||||
|
||||
ErrorContext ARRAY_ERROR[MAX_ARRAY_ERROR];
|
||||
akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
||||
|
||||
void error_default_logger(const char *fmt, ...)
|
||||
void akerr_default_logger(const char *fmt, ...)
|
||||
{
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_USE_STDLIB == 1
|
||||
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
@@ -27,44 +27,44 @@ void error_default_logger(const char *fmt, ...)
|
||||
#endif
|
||||
}
|
||||
|
||||
void error_init()
|
||||
void akerr_init()
|
||||
{
|
||||
static int inited = 0;
|
||||
if ( inited == 0 ) {
|
||||
for (int i = 0; i < MAX_ARRAY_ERROR; i++ ) {
|
||||
memset((void *)&ARRAY_ERROR[i], 0x00, sizeof(ErrorContext));
|
||||
ARRAY_ERROR[i].arrayid = i;
|
||||
ARRAY_ERROR[i].stacktracebufptr = (char *)&ARRAY_ERROR[i].stacktracebuf;
|
||||
for (int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
||||
memset((void *)&AKERR_ARRAY_ERROR[i], 0x00, sizeof(akerr_ErrorContext));
|
||||
AKERR_ARRAY_ERROR[i].arrayid = i;
|
||||
AKERR_ARRAY_ERROR[i].stacktracebufptr = (char *)&AKERR_ARRAY_ERROR[i].stacktracebuf;
|
||||
}
|
||||
__error_last_ignored = NULL;
|
||||
memset((void *)&__error_last_ditch, 0x00, sizeof(ErrorContext));
|
||||
__error_last_ditch.stacktracebufptr = (char *)&__error_last_ditch.stacktracebuf;
|
||||
if ( error_log_method == NULL ) {
|
||||
error_log_method = &error_default_logger;
|
||||
__akerr_last_ignored = NULL;
|
||||
memset((void *)&__akerr_last_ditch, 0x00, sizeof(akerr_ErrorContext));
|
||||
__akerr_last_ditch.stacktracebufptr = (char *)&__akerr_last_ditch.stacktracebuf;
|
||||
if ( akerr_log_method == NULL ) {
|
||||
akerr_log_method = &akerr_default_logger;
|
||||
}
|
||||
error_handler_unhandled_error = &error_default_handler_unhandled_error;
|
||||
memset((void *)&__ERROR_NAMES[0], 0x00, ((MAX_ERR_VALUE+1) * MAX_ERROR_NAME_LENGTH));
|
||||
akerr_handler_unhandled_error = &akerr_default_handler_unhandled_error;
|
||||
memset((void *)&__AKERR_ERROR_NAMES[0], 0x00, ((AKERR_MAX_ERR_VALUE+1) * AKERR_MAX_ERROR_NAME_LENGTH));
|
||||
|
||||
error_name_for_status(ERR_NULLPOINTER, "Null Pointer Error");
|
||||
error_name_for_status(ERR_OUTOFBOUNDS, "Out Of Bounds Error");
|
||||
error_name_for_status(ERR_API, "API Error");
|
||||
error_name_for_status(ERR_ATTRIBUTE, "Attribute Error");
|
||||
error_name_for_status(ERR_TYPE, "Type Error");
|
||||
error_name_for_status(ERR_KEY, "Key Error");
|
||||
error_name_for_status(ERR_HEAP, "Heap Error");
|
||||
error_name_for_status(ERR_INDEX, "Index Error");
|
||||
error_name_for_status(ERR_FORMAT, "Format Error");
|
||||
error_name_for_status(ERR_IO, "Input Output Error");
|
||||
error_name_for_status(ERR_REGISTRY, "Registry Error");
|
||||
error_name_for_status(ERR_VALUE, "Value Error");
|
||||
error_name_for_status(ERR_BEHAVIOR, "Behavior Error");
|
||||
error_name_for_status(ERR_RELATIONSHIP, "Relationship Error");
|
||||
akerr_name_for_status(AKERR_NULLPOINTER, "Null Pointer Error");
|
||||
akerr_name_for_status(AKERR_OUTOFBOUNDS, "Out Of Bounds Error");
|
||||
akerr_name_for_status(AKERR_API, "API Error");
|
||||
akerr_name_for_status(AKERR_ATTRIBUTE, "Attribute Error");
|
||||
akerr_name_for_status(AKERR_TYPE, "Type Error");
|
||||
akerr_name_for_status(AKERR_KEY, "Key Error");
|
||||
akerr_name_for_status(AKERR_HEAP, "Heap Error");
|
||||
akerr_name_for_status(AKERR_INDEX, "Index Error");
|
||||
akerr_name_for_status(AKERR_FORMAT, "Format Error");
|
||||
akerr_name_for_status(AKERR_IO, "Input Output Error");
|
||||
akerr_name_for_status(AKERR_REGISTRY, "Registry Error");
|
||||
akerr_name_for_status(AKERR_VALUE, "Value Error");
|
||||
akerr_name_for_status(AKERR_BEHAVIOR, "Behavior Error");
|
||||
akerr_name_for_status(AKERR_RELATIONSHIP, "Relationship Error");
|
||||
|
||||
inited = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void error_default_handler_unhandled_error(ErrorContext *errctx)
|
||||
void akerr_default_handler_unhandled_error(akerr_ErrorContext *errctx)
|
||||
{
|
||||
if ( errctx == NULL ) {
|
||||
exit(1);
|
||||
@@ -72,29 +72,29 @@ void error_default_handler_unhandled_error(ErrorContext *errctx)
|
||||
exit(errctx->status);
|
||||
}
|
||||
|
||||
ErrorContext *array_next_error()
|
||||
akerr_ErrorContext *akerr_next_error()
|
||||
{
|
||||
for (int i = 0; i < MAX_ARRAY_ERROR; i++ ) {
|
||||
if ( ARRAY_ERROR[i].refcount == 0 ) {
|
||||
return &ARRAY_ERROR[i];
|
||||
for (int i = 0; i < AKERR_MAX_ARRAY_ERROR; i++ ) {
|
||||
if ( AKERR_ARRAY_ERROR[i].refcount == 0 ) {
|
||||
return &AKERR_ARRAY_ERROR[i];
|
||||
}
|
||||
}
|
||||
return (ErrorContext *)NULL;
|
||||
return (akerr_ErrorContext *)NULL;
|
||||
}
|
||||
|
||||
ErrorContext *array_release_error(ErrorContext *err)
|
||||
akerr_ErrorContext *akerr_release_error(akerr_ErrorContext *err)
|
||||
{
|
||||
int oldid = 0;
|
||||
if ( err == NULL ) {
|
||||
ErrorContext *errctx = &__error_last_ditch;
|
||||
FAIL_RETURN(errctx, ERR_NULLPOINTER, "array_release_error got NULL context pointer");
|
||||
akerr_ErrorContext *errctx = &__akerr_last_ditch;
|
||||
FAIL_RETURN(errctx, AKERR_NULLPOINTER, "akerr_release_error got NULL context pointer");
|
||||
}
|
||||
if ( err->refcount > 0 ) {
|
||||
err->refcount -= 1;
|
||||
}
|
||||
if ( err->refcount == 0 ) {
|
||||
oldid = err->arrayid;
|
||||
memset(err, 0x00, sizeof(ErrorContext));
|
||||
memset(err, 0x00, sizeof(akerr_ErrorContext));
|
||||
err->stacktracebufptr = (char *)&err->stacktracebuf;
|
||||
err->arrayid = oldid;
|
||||
return NULL;
|
||||
@@ -105,13 +105,13 @@ ErrorContext *array_release_error(ErrorContext *err)
|
||||
|
||||
// returns or sets the name for the given status.
|
||||
// Call with name = NULL to retrieve a status.
|
||||
char *error_name_for_status(int status, char *name)
|
||||
char *akerr_name_for_status(int status, char *name)
|
||||
{
|
||||
if ( status > MAX_ERR_VALUE ) {
|
||||
if ( status > AKERR_MAX_ERR_VALUE ) {
|
||||
return "Unknown Error";
|
||||
}
|
||||
if ( name != NULL ) {
|
||||
strncpy((char *)&__ERROR_NAMES[status], name, MAX_ERROR_NAME_LENGTH);
|
||||
strncpy((char *)&__AKERR_ERROR_NAMES[status], name, AKERR_MAX_ERROR_NAME_LENGTH);
|
||||
}
|
||||
return (char *)&__ERROR_NAMES[status];
|
||||
return (char *)&__AKERR_ERROR_NAMES[status];
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#include "akerror.h"
|
||||
|
||||
ErrorContext *func2(void)
|
||||
akerr_ErrorContext *func2(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
FAIL(errctx, ERR_NULLPOINTER, "This is a failure in func2");
|
||||
FAIL(errctx, AKERR_NULLPOINTER, "This is a failure in func2");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
ErrorContext *func1(void)
|
||||
akerr_ErrorContext *func1(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
@@ -30,7 +30,7 @@ int main(void)
|
||||
CATCH(errctx, func1());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_NULLPOINTER) {
|
||||
error_log_method("Caught exception");
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
akerr_log_method("Caught exception");
|
||||
} FINISH_NORETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
|
||||
int x;
|
||||
|
||||
ErrorContext *func2(void)
|
||||
akerr_ErrorContext *func2(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
FAIL(errctx, ERR_NULLPOINTER, "This is a failure in func2");
|
||||
FAIL(errctx, AKERR_NULLPOINTER, "This is a failure in func2");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
ErrorContext *func1(void)
|
||||
akerr_ErrorContext *func1(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
@@ -33,7 +33,7 @@ int main(void)
|
||||
CATCH(errctx, func1());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_NULLPOINTER) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
if ( x == 0 ) {
|
||||
fprintf(stderr, "Cleanup works\n");
|
||||
return 0;
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#include "akerror.h"
|
||||
|
||||
ErrorContext *func2(void)
|
||||
akerr_ErrorContext *func2(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
FAIL(errctx, ERR_NULLPOINTER, "This is a failure in func2");
|
||||
FAIL(errctx, AKERR_NULLPOINTER, "This is a failure in func2");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
ErrorContext *func1(void)
|
||||
akerr_ErrorContext *func1(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
|
||||
Reference in New Issue
Block a user