Compare commits
10 Commits
new_readme
...
akmain
| Author | SHA1 | Date | |
|---|---|---|---|
|
350d198015
|
|||
|
c840536e1d
|
|||
|
14e903a81c
|
|||
|
6acae958ff
|
|||
|
cb9af93e0b
|
|||
|
c526bb1ba3
|
|||
|
6821752ec7
|
|||
|
f2ce97224a
|
|||
|
3e4f0d1cda
|
|||
|
cf0f2bbeac
|
@@ -1,60 +1,88 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(sdlerror LANGUAGES C)
|
||||
project(akerror LANGUAGES C)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
set(sdlerror_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/sdlerror")
|
||||
set(AKERR_USE_STDLIB 1 CACHE BOOL "Use the C standard library")
|
||||
set(akerror_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akerror")
|
||||
|
||||
set(SCRIPT ${CMAKE_SOURCE_DIR}/scripts/generrno.sh)
|
||||
set(INFILE ${CMAKE_SOURCE_DIR}/include/akerror.tmpl.h)
|
||||
set(OUTFILES ${CMAKE_SOURCE_DIR}/src/errno.c ${CMAKE_SOURCE_DIR}/include/akerror.h)
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTFILES}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_SOURCE_DIR}
|
||||
COMMAND /usr/bin/env bash ${SCRIPT} ${CMAKE_SOURCE_DIR}
|
||||
DEPENDS ${SCRIPT} ${INFILE}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_source_files_properties(src/errno.c PROPERTIES GENERATED TRUE)
|
||||
|
||||
add_custom_target(generrno DEPENDS src/errno.c)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-static)
|
||||
|
||||
# Check for SDL3 using pkg-config
|
||||
pkg_check_modules(SDL3 REQUIRED sdl3)
|
||||
|
||||
# Add include directories
|
||||
include_directories(${SDL3_INCLUDE_DIRS})
|
||||
add_library(sdlerror SHARED
|
||||
add_library(akerror STATIC
|
||||
src/error.c
|
||||
src/errno.c
|
||||
)
|
||||
add_dependencies(akerror generrno)
|
||||
|
||||
target_compile_definitions(akerror
|
||||
PUBLIC AKERR_USE_STDLIB=${AKERR_USE_STDLIB}
|
||||
)
|
||||
|
||||
add_executable(test_err_catch tests/err_catch.c)
|
||||
add_executable(test_err_cleanup tests/err_cleanup.c)
|
||||
add_executable(test_err_trace tests/err_trace.c)
|
||||
add_executable(test_err_unhandled tests/err_unhandled.c)
|
||||
add_test(NAME err_catch COMMAND test_err_catch)
|
||||
add_test(NAME err_cleanup COMMAND test_err_cleanup)
|
||||
add_test(NAME err_trace COMMAND test_err_trace)
|
||||
add_test(NAME err_unhandled COMMAND test_err_unhandled)
|
||||
|
||||
# Specify include directories for the library's headers (if applicable)
|
||||
target_include_directories(sdlerror PUBLIC
|
||||
target_include_directories(akerror PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/>
|
||||
)
|
||||
target_link_libraries(test_err_catch PRIVATE sdlerror SDL3::SDL3)
|
||||
target_link_libraries(test_err_cleanup PRIVATE sdlerror SDL3::SDL3)
|
||||
target_link_libraries(test_err_trace PRIVATE sdlerror SDL3::SDL3)
|
||||
target_link_libraries(test_err_catch PRIVATE akerror)
|
||||
target_link_libraries(test_err_cleanup PRIVATE akerror)
|
||||
target_link_libraries(test_err_trace PRIVATE akerror)
|
||||
target_link_libraries(test_err_unhandled PRIVATE akerror)
|
||||
|
||||
set(main_lib_dest "lib/my_library-${MY_LIBRARY_VERSION}")
|
||||
install(TARGETS sdlerror EXPORT sdlerror DESTINATION "lib/")
|
||||
install(FILES "include/sdlerror.h" DESTINATION "include/")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/sdlerror.pc DESTINATION "lib/pkgconfig/")
|
||||
install(TARGETS akerror EXPORT akerror DESTINATION "lib/")
|
||||
install(TARGETS akerror
|
||||
EXPORT akerrorTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
install(EXPORT akerror FILE akerrorTargets.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/akerror)
|
||||
|
||||
install(FILES "include/akerror.h" DESTINATION "include/")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akerror.pc DESTINATION "lib/pkgconfig/")
|
||||
|
||||
|
||||
install(EXPORT sdlerror
|
||||
FILE sdlerrorTargets.cmake
|
||||
NAMESPACE sdlerror::
|
||||
DESTINATION ${sdlerror_install_cmakedir}
|
||||
install(EXPORT akerror
|
||||
FILE akerrorTargets.cmake
|
||||
NAMESPACE akerror::
|
||||
DESTINATION ${akerror_install_cmakedir}
|
||||
)
|
||||
|
||||
configure_package_config_file(
|
||||
cmake/sdlerror.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/sdlerrorConfig.cmake"
|
||||
INSTALL_DESTINATION ${sdlerror_install_cmakedir}
|
||||
cmake/akerror.cmake.in
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/akerrorConfig.cmake"
|
||||
INSTALL_DESTINATION ${akerror_install_cmakedir}
|
||||
)
|
||||
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/sdlerrorConfig.cmake"
|
||||
DESTINATION ${sdlerror_install_cmakedir}
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/akerrorConfig.cmake"
|
||||
DESTINATION ${akerror_install_cmakedir}
|
||||
)
|
||||
|
||||
# pkgconfig
|
||||
@@ -62,4 +90,4 @@ set(prefix ${CMAKE_INSTALL_PREFIX})
|
||||
set(exec_prefix "\${prefix}")
|
||||
set(libdir "\${exec_prefix}/lib")
|
||||
set(includedir "\${prefix}/include")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/sdlerror.pc.in ${CMAKE_CURRENT_BINARY_DIR}/sdlerror.pc @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/akerror.pc.in ${CMAKE_CURRENT_BINARY_DIR}/akerror.pc @ONLY)
|
||||
|
||||
521
README.md
521
README.md
@@ -1,291 +1,356 @@
|
||||
# libsdlerror
|
||||
# Summary
|
||||
|
||||
**Explicit, disciplined error handling for C — built on SDL3.**
|
||||
This library provides a TRY/CATCH style exception handling mechanism for C.
|
||||
|
||||
`libsdlerror` is a small library that helps C programmers write correct, readable, and maintainable error-handling code without pretending that C is something it is not.
|
||||
# Why?
|
||||
|
||||
It does not add exceptions to C.
|
||||
It does not hide control flow.
|
||||
It does not allocate memory at runtime.
|
||||
There is nothing wrong with C as it is. This library does not claim to fix some problem with C.
|
||||
|
||||
If you want magic, look elsewhere.
|
||||
Instead, this library implements a pragmatic and stylistic choice to assist the programmer in better handling errors in their programs. Vanilla C provides everything you need to do this out of the box, but this library makes it easier to avoid pointing certain guns at your foot, and when you do, it provides better context with those errors to help you more quickly recover.
|
||||
|
||||
---
|
||||
Why? Because some programmers prefer to have the power of C with just a little bit of help in managing their errors.
|
||||
|
||||
## Why This Library Exists
|
||||
# Library Architecture
|
||||
|
||||
Error handling in C is not hard — but it is tedious, repetitive, and easy to get wrong.
|
||||
## Philosophy of Use
|
||||
|
||||
The usual pattern:
|
||||
This library has 6 guiding principles:
|
||||
|
||||
* Manually checking every possible return code for every possible meaning of that return code is tedious and prone to miss unpredicted failure cases
|
||||
* Functions should return rich descriptive error contexts, not values
|
||||
* Uncaught errors should cause program termination with a stacktrace
|
||||
* Dynamic memory allocation is the source of many errors and should be avoided if possible
|
||||
* Manipulating the call stack directly is error prone and dangerous
|
||||
* Declaring, capturing, and reacting to errors should be intuitive and no more difficult than managing return codes
|
||||
|
||||
## Lifecycle of an error in the AKError library
|
||||
|
||||
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 `akerr_ErrorContext` object is initialized 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 `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
|
||||
|
||||
The Error Context object is a simple object which contains a few things:
|
||||
|
||||
* A numeric error code
|
||||
* The name of the file in which the error occurred
|
||||
* The name of the function in which the error occurred
|
||||
* The line number in the file at which the error occurred
|
||||
* A character buffer containing a message about the error in question
|
||||
|
||||
The structure also contains housekeeping information for the library which are of no specific interest to the user. See [include/akerror.h](include/akerror.h) for more details.
|
||||
|
||||
## What are the control structures
|
||||
|
||||
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 `akerr_ErrorContext *`.
|
||||
|
||||
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 `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 `AKERR_xxxxx` values. Error values up to 255 are reserved by the library (`AKERR_xxxxx` begins where `errno` stops), so please begin your error values at 256. 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. If you define custom error codes, `AKERR_MAX_ERR_VALUE` must be >= 256 or the compiler will throw an error.
|
||||
|
||||
Define a human-friendly name for the error with the `akerr_name_for_status` method somewhere in your code's initialization before the error may be used:
|
||||
|
||||
```c
|
||||
if (foo() < 0) {
|
||||
fprintf(stderr, "%s\n", SDL_GetError());
|
||||
cleanup();
|
||||
return -1;
|
||||
}
|
||||
akerr_name_for_status(129, "Some Error Code Description")
|
||||
```
|
||||
|
||||
works, until it doesn’t. As programs grow:
|
||||
|
||||
- Error checks get duplicated
|
||||
- Cleanup paths multiply
|
||||
- Context disappears
|
||||
- Control flow becomes fragmented
|
||||
# Installation
|
||||
|
||||
SDL compounds this by storing errors in a single thread-local string, forcing programmers to either handle errors immediately or lose information.
|
||||
|
||||
`libsdlerror` exists to impose structure and discipline on this process — without compromising the explicitness that makes C reliable.
|
||||
|
||||
---
|
||||
|
||||
## What This Library Is — and Is Not
|
||||
|
||||
This library is deliberately conservative.
|
||||
|
||||
### 🚫 No `setjmp`, No `longjmp`
|
||||
|
||||
Many C error libraries attempt to simulate exceptions using `setjmp` / `longjmp`.
|
||||
|
||||
That approach is rejected here.
|
||||
|
||||
Non-local jumps:
|
||||
|
||||
- Bypass normal control flow
|
||||
- Skip cleanup invisibly
|
||||
- Break assumptions about stack lifetime
|
||||
- Make reasoning about correctness harder, not easier
|
||||
|
||||
If control flow moves in `libsdlerror`, it does so because the source code says so.
|
||||
|
||||
Nothing jumps. Nothing unwinds itself. Nothing “just happens.”
|
||||
|
||||
---
|
||||
|
||||
### 🚫 No Runtime Memory Allocation — Ever
|
||||
|
||||
`libsdlerror` never performs dynamic memory allocation at runtime.
|
||||
|
||||
It does not call:
|
||||
|
||||
- `malloc`
|
||||
- `calloc`
|
||||
- `realloc`
|
||||
- `free`
|
||||
|
||||
There is no allocator dependency and no runtime allocation failure mode.
|
||||
|
||||
---
|
||||
|
||||
### 📌 Explicit Storage Model
|
||||
|
||||
All memory used by `libsdlerror` falls into one of two categories:
|
||||
|
||||
- **Automatic storage (stack)**
|
||||
Caller-owned error contexts and local state
|
||||
|
||||
- **Static storage duration**
|
||||
Fixed-size internal tables allocated at program load time
|
||||
|
||||
There is no heap allocation and no runtime resizing.
|
||||
|
||||
---
|
||||
|
||||
### ✅ C Means C
|
||||
|
||||
- No compiler extensions
|
||||
- No undefined behavior
|
||||
- No optimizer tricks
|
||||
|
||||
If it compiles as C, it works as C.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Designed for SDL, Not Against It
|
||||
|
||||
SDL already has an error system. It is simple, global, and fragile.
|
||||
|
||||
`libsdlerror` does not replace it. It captures SDL error state at the right moment, preserves context, and makes it usable without forcing error checks everywhere.
|
||||
|
||||
---
|
||||
|
||||
## The Six Guiding Principles
|
||||
|
||||
This library is opinionated, and intentionally so.
|
||||
|
||||
1. **Control flow must be explicit**
|
||||
If execution moves, the source code must show why.
|
||||
|
||||
2. **No `setjmp` / `longjmp`**
|
||||
Errors must not bypass the stack.
|
||||
|
||||
3. **No runtime memory allocation**
|
||||
The library relies only on stack and static storage.
|
||||
|
||||
4. **The caller owns all state**
|
||||
No hidden globals exposed to the user.
|
||||
|
||||
5. **Cleanup must be deterministic**
|
||||
Cleanup always runs, exactly once.
|
||||
|
||||
6. **Errors are values, not side effects**
|
||||
They are captured, inspected, propagated, or handled deliberately.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Build and Install
|
||||
|
||||
```sh
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
### Compile Your Program
|
||||
## Templating and autogenerated code
|
||||
|
||||
```sh
|
||||
gcc `pkg-config --cflags sdlerror` main.c \
|
||||
`pkg-config --libs sdlerror` \
|
||||
-o app
|
||||
The build process relies upon `scripts/generrno.sh` which performs the following:
|
||||
|
||||
1. Executes `errno --list` and gathers up the output
|
||||
1. Templates `include/akerror.tmpl.h` into `include/akerror.h` to set the `AKERR_LAST_ERRNO_VALUE` equal to the highest integer defined by `errno`
|
||||
2. Generates `src/errno.c` which contains a function called by `akerr_init` which initializes all of the status names for the previously defined values of `errno`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This library depends upon `stdlib`. If you don't want to link against stdlib, you must modify the library code to include headers and link against a library that provides the following:
|
||||
|
||||
- `memset` function
|
||||
- `strncpy` function
|
||||
- `sprintf` function
|
||||
- `exit` function
|
||||
- `bool` type
|
||||
- `NULL` type
|
||||
|
||||
... then you can compile it thusly:
|
||||
|
||||
```
|
||||
cmake -S . -B build -DAKERR_USE_STDLIB=OFF
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
### Minimal Example
|
||||
# Using the library
|
||||
|
||||
## Setting up your project
|
||||
|
||||
Include it
|
||||
|
||||
```c
|
||||
#include <sdlerror.h>
|
||||
#include <akerror.h>
|
||||
```
|
||||
|
||||
int main(void) {
|
||||
PREPARE_ERROR(err);
|
||||
Link the library directly, or
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(err, SDL_Init(SDL_INIT_VIDEO));
|
||||
CATCH(err, do_something_that_can_fail());
|
||||
}
|
||||
CLEANUP {
|
||||
SDL_Quit();
|
||||
}
|
||||
PROCESS(err) {
|
||||
fprintf(stderr, "Error: %s\n", ERROR_MESSAGE(err));
|
||||
return 1;
|
||||
}
|
||||
FINISH(err, true);
|
||||
```sh
|
||||
cc -lakerror
|
||||
```
|
||||
|
||||
return 0;
|
||||
Using pkg-config, or
|
||||
|
||||
```sh
|
||||
pkg-config akerror --cflags
|
||||
pkg-config akerror --ldflags
|
||||
```
|
||||
|
||||
Using cmake:
|
||||
|
||||
```cmake
|
||||
find_package(akerror REQUIRED)
|
||||
pkg_check_modules(akerror REQUIRED akerror)
|
||||
target_link_libraries(YOUR_TARGET PRIVATE akerror::akerror)
|
||||
```
|
||||
|
||||
|
||||
## (Optional) Configuring the logging function
|
||||
|
||||
The default logging function (used for logging stack traces on failure) defaults to a wrapper that calls `fprintf(stderr, f, ...)`. If you want to override this behavior, then set the error handler to a function with a printf-style signature:
|
||||
|
||||
```
|
||||
void my_logger(const char *fmt, ...)
|
||||
{
|
||||
/* ... do something */
|
||||
}
|
||||
|
||||
|
||||
/* set your custom error handler */
|
||||
akerr_log_method = &my_logger;
|
||||
|
||||
/* proceed to use the library */
|
||||
```
|
||||
|
||||
## Setting Up the Error Context
|
||||
|
||||
Before you can use any of these macros you must set up an error context inside of the current scope.
|
||||
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
// ... code
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true)
|
||||
```
|
||||
|
||||
`ATTEMPT { ... }` is the block within which you will perform operations which may cause errors that need to be caught. See "Capturing errors", below.
|
||||
|
||||
`CLEANUP { ... }` is the block within which you will perform any code which MUST be executed REGARDLESS of whether or not errors were thrown. Closing open file handles, or releasing memory, for example.
|
||||
|
||||
`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 akerr_ErrorContext, and a boolean regarding whether or not to pass unhandled errors up to the calling function. Unless you are inside of your `main()` method, this should be true. Inside of your `main()` method, call `FINISH_NORExbTURN(errctx)` instead.
|
||||
|
||||
|
||||
# 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 akerr_ErrorContext *
|
||||
|
||||
For functions that return `akerr_ErrorContext *`, you should use the `CATCH` macro.
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
CATCH(errctx, errorGeneratingFunction())
|
||||
} // ...
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
For functions that return integer, such as logical comparisons or most standard library functions, use the `FAIL_ZERO_BREAK` and `FAIL_NONZERO_BREAK` macros. These macros allow you to capture an integer return code from an expression or function and set an error code in the current context based off that return.
|
||||
|
||||
Here is an example of checking for a NULL pointer
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(errctx, (somePointer == NULL), AKERR_NULLPOINTER, "Someone gave me a NULL pointer")
|
||||
} // ...
|
||||
```
|
||||
|
||||
Here is an example of checking for two strings that are not equal
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp("not", "equal"), AKERR_VALUE, "Strings are not equal")
|
||||
} // ...
|
||||
```
|
||||
|
||||
When either of these two macros are used, the `ATTEMPT` block is immediately exited, and the `CLEANUP` block begins.
|
||||
|
||||
# Handling errors
|
||||
|
||||
Inside of the `PROCESS { ... }` block, you must handle any errors that occurred during the `ATTEMPT { ... }` block. You do this with `HANDLE`, `HANDLE_GROUP`, and `HANDLE_DEFAULT`.
|
||||
|
||||
## Handling a specific error with HANDLE
|
||||
|
||||
In order to handle a specific error code, use the `HANDLE` macro.
|
||||
|
||||
```c
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// Something is complaining about a null pointer error. Do something about it.
|
||||
} // ...
|
||||
```
|
||||
|
||||
## Handling a group of errors with HANDLE_GROUP
|
||||
|
||||
In order to handle a group of related errors that all require the same failure behavior, use `HANDLE` followed by `HANDLE_GROUP`. For example, to handle a scenario where an IO error, key error, and index error all need to be handled the same way:
|
||||
|
||||
```c
|
||||
} PROCESS(errctx) {
|
||||
} 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.
|
||||
|
||||
## User-Configurable Error Codes
|
||||
|
||||
While most aspects of `libsdlerror` are intentionally fixed, error codes themselves are user-defined.
|
||||
|
||||
### Defining `ERR_*` Codes
|
||||
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
|
||||
#define ERR_OK 0
|
||||
#define ERR_SDL 1
|
||||
#define ERR_IO 2
|
||||
#define ERR_CONFIG 3
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_IO) {
|
||||
} HANDLE_GROUP(errctx, AKERR_KEY) {
|
||||
} HANDLE_GROUP(errctx, AKERR_INDEX) {
|
||||
// This code handles 3 error cases
|
||||
} HANDLE(errctx, AKERR_RELATIONSHIP) {
|
||||
// This code handles 1 error case
|
||||
}
|
||||
```
|
||||
|
||||
These are simple integer status values.
|
||||
# Returning success or failure from functions returning akerr_ErrorContext *
|
||||
|
||||
---
|
||||
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.
|
||||
|
||||
### `MAX_ERR_VALUE`
|
||||
## SUCCEED_RETURN
|
||||
|
||||
`MAX_ERR_VALUE` must be greater than or equal to the highest `ERR_*` value you define.
|
||||
|
||||
It determines the size of internal static tables used to map error codes to names.
|
||||
|
||||
If you add new error codes, you must update `MAX_ERR_VALUE` and recompile.
|
||||
|
||||
---
|
||||
|
||||
### Naming Errors: `error_name_for_status`
|
||||
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
|
||||
error_name_for_status(ERR_IO, "I/O error");
|
||||
error_name_for_status(ERR_CONFIG, "Configuration error");
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
// ... stuff
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
```
|
||||
|
||||
Names are copied into fixed-size static buffers and persist for the lifetime of the program.
|
||||
## FAIL_RETURN
|
||||
|
||||
No allocation occurs.
|
||||
If the code path in the current function reaches a state wherein an error must be set and the function must return early, you can use `FAIL_RETURN` to accomplish this. Note that this should not be used inside of an `ATTEMPT { ... }` block; this immediately exits the function, preventing a `CLEANUP { ... }` block from executing. This can be safely used from inside of a `CLEANUP` or `PROCESS` block, or from anywhere within the function not inside of an `ATTEMPT { ... }` block.
|
||||
|
||||
---
|
||||
The function allows you to provide printf-style variable arguments to provide a meaningful failure message.
|
||||
|
||||
## Appendix: Memory Model
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_RETURN(AKERR_BEHAVIOR, "Something went horribly wrong!")
|
||||
```
|
||||
|
||||
### Storage Duration Overview
|
||||
## Conditionally failing and returning
|
||||
|
||||
`libsdlerror` uses only automatic (stack) storage and static storage duration.
|
||||
In addition to `FAIL_RETURN` you can also test for zero or non-zero conditions, set an error, and return from the function immediately. Use the `FAIL_ZERO_RETURN` and `FAIL_NONZERO_RETURN` macros for this. These macros can be used anywhere that `FAIL_RETURN` can be used.
|
||||
|
||||
No heap allocation occurs at runtime.
|
||||
```c
|
||||
PREPARE_ERROR(errctx);
|
||||
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"), AKERR_VALUE, "Strings are not equal")
|
||||
```
|
||||
|
||||
### Compile-Time Limits (`MAX_ERR*`)
|
||||
# Uncaught errors
|
||||
|
||||
The `MAX_ERR*` macros in `sdlerror.h` define fixed upper bounds for:
|
||||
## Ensuring that all error codes are captured
|
||||
|
||||
- Error message length
|
||||
- Error name length
|
||||
- Stacktrace buffer size
|
||||
- Number of error contexts
|
||||
Any function which returns `akerr_ErrorContext *` should also be marked with `ERROR_NOIGNORE`.
|
||||
|
||||
These values are not user-tunable at runtime.
|
||||
```c
|
||||
akerr_ErrorContext ERROR_NOIGNORE *f(...);
|
||||
```
|
||||
|
||||
If you need different limits, you are expected to edit the header and recompile.
|
||||
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.
|
||||
|
||||
---
|
||||
Beware that `ERROR_NOIGNORE` is not a failsafe - it implements the `warn_unused_result` mechanic. By design users may explicitly ignore an error code from a function marked with `warn_unused_result` by explicitly casting the return to `void`.
|
||||
|
||||
### Static Memory Usage (x86_64)
|
||||
```c
|
||||
#define ERROR_NOIGNORE __attribute__((warn_unused_result))
|
||||
```
|
||||
|
||||
On a typical x86_64 system:
|
||||
## Stack Traces
|
||||
|
||||
- Error name table: ~1 KB
|
||||
- Static error pool: ~450 KB
|
||||
- Miscellaneous globals: a few KB
|
||||
Whenever an error is captured using the `FAIL_*` or `CATCH` methods, and is unhandled such that it manages to propagate all the way to the top of the caller stack without being managed, the last `FINISH` macro to touch the error will trigger a stack trace and kill the program.
|
||||
|
||||
**Total static footprint: ~470 KB**
|
||||
Consider the `tests/err_trace.c` program which intentionally triggers this behavior. It produces output like this:
|
||||
|
||||
All of this memory is allocated at program load time.
|
||||
```
|
||||
tests/err_trace.c:func2:7: 1 (Null Pointer Error) : This is a failure in func2
|
||||
tests/err_trace.c:func2:10
|
||||
tests/err_trace.c:func1:18: Detected error 0 from array (refcount 1)
|
||||
tests/err_trace.c:func1:18
|
||||
tests/err_trace.c:func1:21
|
||||
tests/err_trace.c:main:30: Detected error 0 from array (refcount 1)
|
||||
tests/err_trace.c:main:30
|
||||
tests/err_trace.c:main:33: Unhandled Error 1 (Null Pointer Error): This is a failure in func2
|
||||
```
|
||||
|
||||
---
|
||||
From bottom to top, we have:
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why Use Static Globals Instead of the Heap?
|
||||
|
||||
Because error handling infrastructure must not itself fail.
|
||||
|
||||
Heap allocation introduces failure modes, ordering problems, and unpredictability.
|
||||
|
||||
Static storage avoids all of this.
|
||||
|
||||
The globals used by `libsdlerror` are fixed-size, initialized at load time, and free of allocator dependencies.
|
||||
|
||||
---
|
||||
|
||||
### Why Not Allocate Per-Error Structures Dynamically?
|
||||
|
||||
Because error handling must work when memory is tight, during early startup, and during shutdown.
|
||||
|
||||
Dynamic allocation undermines all three.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
`libsdlerror` is deliberately conservative.
|
||||
|
||||
No jumps.
|
||||
No heap allocation.
|
||||
No hidden control flow.
|
||||
|
||||
Just explicit, disciplined error handling — the way C has always worked when used correctly.
|
||||
* The last line printed is the `FINISH` macro call that triggered the stacktrace.
|
||||
* Above that, the `CATCH()` inside of `main()` which caught the exception from `func1()` but did not handle it
|
||||
* Above that, a statement that the error was detected in the `CATCH()` statement at the same line
|
||||
* Above that, the `FINISH()` macro in the `func1` method which detected the presence of an unhandled error and returned it up the calling stack
|
||||
* Above that, the `CATCH()` macro in the `func1` method which caught the error coming out of `func2()`
|
||||
* Above that, a statement that the error was detected in the `CATCH()` statement at the same line
|
||||
* Above that, the `FINISH()` macro in `func2()` which detected an unhandled error and passed it out of the function
|
||||
* Above that, a reference to the line where the `FAIL()` macro set the error code and provided the message which is printed here
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ exec_prefix=${prefix}
|
||||
libdir=${exec_prefix}/lib
|
||||
includedir=${exec_prefix}/include
|
||||
|
||||
Name: sdlerror
|
||||
Description: A C error handling library that relies on SDL3
|
||||
Name: akerror
|
||||
Description: AKLabs C error handling library
|
||||
Version: @PROJECT_VERSION@
|
||||
Cflags: -I${includedir}/
|
||||
Libs: -L${libdir} -lsdlerror
|
||||
Libs: -L${libdir} -lakerror
|
||||
@@ -2,4 +2,4 @@
|
||||
include(CMakeFindDependencyMacro) # If your library has dependencies
|
||||
# find_dependency(AnotherDependency REQUIRED) # Example dependency
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/sdlerror.cmake")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/akerrorTargets.cmake")
|
||||
255
include/akerror.tmpl.h
Normal file
255
include/akerror.tmpl.h
Normal file
@@ -0,0 +1,255 @@
|
||||
#ifndef _AKERR_H_
|
||||
#define _AKERR_H_
|
||||
|
||||
#if (defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1) || (!defined(AKERR_USE_STDLIB))
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#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 AKERR_LAST_ERRNO_VALUE AKERR_LAST_ERRNO_VALUE_SED
|
||||
|
||||
#define AKERR_NULLPOINTER (AKERR_LAST_ERRNO_VALUE + 1)
|
||||
#define AKERR_OUTOFBOUNDS (AKERR_LAST_ERRNO_VALUE + 2)
|
||||
#define AKERR_API (AKERR_LAST_ERRNO_VALUE + 3)
|
||||
#define AKERR_ATTRIBUTE (AKERR_LAST_ERRNO_VALUE + 4)
|
||||
#define AKERR_TYPE (AKERR_LAST_ERRNO_VALUE + 5)
|
||||
#define AKERR_KEY (AKERR_LAST_ERRNO_VALUE + 6)
|
||||
#define AKERR_HEAP (AKERR_LAST_ERRNO_VALUE + 7)
|
||||
#define AKERR_INDEX (AKERR_LAST_ERRNO_VALUE + 8)
|
||||
#define AKERR_FORMAT (AKERR_LAST_ERRNO_VALUE + 9)
|
||||
#define AKERR_IO (AKERR_LAST_ERRNO_VALUE + 10)
|
||||
#define AKERR_REGISTRY (AKERR_LAST_ERRNO_VALUE + 11)
|
||||
#define AKERR_VALUE (AKERR_LAST_ERRNO_VALUE + 12)
|
||||
#define AKERR_BEHAVIOR (AKERR_LAST_ERRNO_VALUE + 13)
|
||||
#define AKERR_RELATIONSHIP (AKERR_LAST_ERRNO_VALUE + 14)
|
||||
|
||||
#ifndef AKERR_MAX_ERR_VALUE
|
||||
#define AKERR_MAX_ERR_VALUE (AKERR_LAST_ERRNO_VALUE + 14)
|
||||
#elif AKERR_MAX_ERR_VALUE < 256
|
||||
#error user-defined AKERR_MAX_ERR_VALUE must be >= 256
|
||||
#endif
|
||||
|
||||
extern char __AKERR_ERROR_NAMES[AKERR_MAX_ERR_VALUE+1][AKERR_MAX_ERROR_NAME_LENGTH];
|
||||
|
||||
#define AKERR_MAX_ARRAY_ERROR 128
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char message[AKERR_MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
int arrayid;
|
||||
int status;
|
||||
bool handled;
|
||||
int refcount;
|
||||
char fname[AKERR_MAX_ERROR_FNAME_LENGTH];
|
||||
char function[AKERR_MAX_ERROR_FNAME_LENGTH];
|
||||
int lineno;
|
||||
bool reported;
|
||||
char stacktracebuf[AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH];
|
||||
char *stacktracebufptr;
|
||||
int stacktracebufremaining;
|
||||
} akerr_ErrorContext;
|
||||
|
||||
#define AKERR_NOIGNORE __attribute__((warn_unused_result))
|
||||
|
||||
typedef void (*akerr_ErrorUnhandledErrorHandler)(akerr_ErrorContext *errctx);
|
||||
typedef void (*akerr_ErrorLogFunction)(const char *f, ...);
|
||||
|
||||
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;
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akerr_user_main(int argc, char **argv);
|
||||
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, ...);
|
||||
/* defined in src/errno.c which is built dynamically at build time from system errno definitions */
|
||||
void akerr_init_errno(void);
|
||||
|
||||
#define LOG_ERROR_WITH_MESSAGE(__err_context, __err_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 = akerr_release_error(__err_context); \
|
||||
}
|
||||
|
||||
#define PREPARE_ERROR(__err_context) \
|
||||
akerr_init(); \
|
||||
akerr_ErrorContext __attribute__ ((unused)) *__err_context = NULL;
|
||||
|
||||
#define ENSURE_ERROR_READY(__err_context) \
|
||||
if ( __err_context == NULL ) { \
|
||||
__err_context = akerr_next_error(); \
|
||||
if ( __err_context == NULL ) { \
|
||||
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 akerr_ErrorContext *
|
||||
*/
|
||||
|
||||
#define FAIL_ZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x == 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context; \
|
||||
}
|
||||
|
||||
#define FAIL_NONZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x != 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context; \
|
||||
}
|
||||
|
||||
#define FAIL_RETURN(__err_context, __err, __message, ...) \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context;
|
||||
|
||||
#define SUCCEED_RETURN(__err_context) \
|
||||
RELEASE_ERROR(__err_context); \
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Failure and success methods for use inside of ATTEMPT() blocks
|
||||
*/
|
||||
|
||||
#define FAIL_ZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x == 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define FAIL_NONZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x != 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define FAIL_BREAK(__err_context, __err_, __message, ...) \
|
||||
FAIL(__err_context, __err_, __message, ##__VA_ARGS__); \
|
||||
break;
|
||||
|
||||
#define SUCCEED_BREAK(__err_context) \
|
||||
SUCCEED(__err_context); \
|
||||
break;
|
||||
|
||||
/*
|
||||
* General failure and success methods
|
||||
*/
|
||||
|
||||
#define FAIL(__err_context, __err, __message, ...) \
|
||||
ENSURE_ERROR_READY(__err_context); \
|
||||
__err_context->status = __err; \
|
||||
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, 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 == NULL ? "" : __err_context->message));
|
||||
|
||||
|
||||
#define SUCCEED(__err_context) \
|
||||
ENSURE_ERROR_READY(__err_context); \
|
||||
__err_context->status = 0;
|
||||
|
||||
/*
|
||||
* Defines for the ATTEMPT/CATCH/CLEANUP/PROCESS/HANDLE/FINISH process
|
||||
*/
|
||||
|
||||
#define ATTEMPT \
|
||||
switch ( 0 ) { \
|
||||
case 0: \
|
||||
|
||||
#define DETECT(__err_context, __stmt) \
|
||||
__stmt; \
|
||||
if ( __err_context != NULL ) { \
|
||||
__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, AKERR_MAX_ERROR_STACKTRACE_BUF_LENGTH, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CATCH(__err_context, __stmt) \
|
||||
DETECT(__err_context, __err_context = __stmt);
|
||||
|
||||
#define IGNORE(__stmt) \
|
||||
__akerr_last_ignored = __stmt; \
|
||||
if ( __akerr_last_ignored != NULL ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__akerr_last_ignored, "** IGNORED ERROR **"); \
|
||||
}
|
||||
|
||||
#define CLEANUP \
|
||||
};
|
||||
|
||||
#define PROCESS(__err_context) \
|
||||
if ( __err_context != NULL ) { \
|
||||
switch ( __err_context->status ) { \
|
||||
case 0: \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE(__err_context, __err_status) \
|
||||
break; \
|
||||
case __err_status: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE_GROUP(__err_context, __err_status) \
|
||||
case __err_status: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE_DEFAULT(__err_context) \
|
||||
break; \
|
||||
default: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define FINISH(__err_context, __pass_up) \
|
||||
}; \
|
||||
}; \
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false && __pass_up == true ) { \
|
||||
__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; \
|
||||
} \
|
||||
} \
|
||||
RELEASE_ERROR(__err_context);
|
||||
|
||||
#define FINISH_NORETURN(__err_context) \
|
||||
}; \
|
||||
}; \
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "Unhandled Error"); \
|
||||
akerr_handler_unhandled_error(__err_context); \
|
||||
} \
|
||||
} \
|
||||
RELEASE_ERROR(__err_context);
|
||||
|
||||
#define CATCH_AND_RETURN(__err_context, __stmt) \
|
||||
ATTEMPT { \
|
||||
CATCH(__err_context, __stmt); \
|
||||
} CLEANUP { \
|
||||
} PROCESS(__err_context) { \
|
||||
} FINISH(__err_context, true);
|
||||
|
||||
|
||||
#endif // _AKERR_H_
|
||||
@@ -1,243 +0,0 @@
|
||||
#ifndef _ERROR_H_
|
||||
#define _ERROR_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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 ERR_NULLPOINTER 1
|
||||
#define ERR_OUTOFBOUNDS 2
|
||||
#define ERR_SDL 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
|
||||
|
||||
#ifndef MAX_ERR_VALUE
|
||||
#define MAX_ERR_VALUE 14
|
||||
#endif
|
||||
|
||||
extern char __ERROR_NAMES[MAX_ERR_VALUE+1][MAX_ERROR_NAME_LENGTH];
|
||||
|
||||
#define MAX_HEAP_ERROR 128
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char message[MAX_ERROR_CONTEXT_STRING_LENGTH];
|
||||
int heapid;
|
||||
int status;
|
||||
bool handled;
|
||||
int refcount;
|
||||
char fname[MAX_ERROR_FNAME_LENGTH];
|
||||
char function[MAX_ERROR_FNAME_LENGTH];
|
||||
int lineno;
|
||||
bool reported;
|
||||
char stacktracebuf[MAX_ERROR_STACKTRACE_BUF_LENGTH];
|
||||
char *stacktracebufptr;
|
||||
} ErrorContext;
|
||||
|
||||
#define ERROR_NOIGNORE __attribute__((warn_unused_result))
|
||||
|
||||
typedef void (*ErrorUnhandledErrorHandler)(ErrorContext *errctx);
|
||||
|
||||
extern ErrorContext HEAP_ERROR[MAX_HEAP_ERROR];
|
||||
extern ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
extern ErrorContext *__error_last_ignored;
|
||||
|
||||
ErrorContext ERROR_NOIGNORE *heap_release_error(ErrorContext *ptr);
|
||||
ErrorContext ERROR_NOIGNORE *heap_next_error();
|
||||
char *error_name_for_status(int status, char *name);
|
||||
void error_init();
|
||||
void error_default_handler_unhandled_error(ErrorContext *ptr);
|
||||
|
||||
#define LOG_ERROR_WITH_MESSAGE(__err_context, __err_message) \
|
||||
SDL_Log("%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); \
|
||||
|
||||
#define LOG_ERROR(__err_context) \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "");
|
||||
|
||||
#define RELEASE_ERROR(__err_context) \
|
||||
if ( __err_context != NULL ) { \
|
||||
__err_context = heap_release_error(__err_context); \
|
||||
}
|
||||
|
||||
#define PREPARE_ERROR(__err_context) \
|
||||
error_init(); \
|
||||
ErrorContext __attribute__ ((unused)) *__err_context = NULL;
|
||||
|
||||
#define ENSURE_ERROR_READY(__err_context) \
|
||||
if ( __err_context == NULL ) { \
|
||||
__err_context = heap_next_error(); \
|
||||
if ( __err_context == NULL ) { \
|
||||
SDL_Log("%s:%s:%d: Unable to pull an ErrorContext from the heap!", __FILE__, (char *)__func__, __LINE__); \
|
||||
exit(1); \
|
||||
} \
|
||||
} \
|
||||
__err_context->refcount += 1;
|
||||
|
||||
/*
|
||||
* Failure and success methods for functions that return ErrorContext *
|
||||
*/
|
||||
|
||||
#define FAIL_ZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x == 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context; \
|
||||
}
|
||||
|
||||
#define FAIL_NONZERO_RETURN(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x != 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context; \
|
||||
}
|
||||
|
||||
#define FAIL_RETURN(__err_context, __err, __message, ...) \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
return __err_context;
|
||||
|
||||
#define SUCCEED_RETURN(__err_context) \
|
||||
RELEASE_ERROR(__err_context); \
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Failure and success methods for use inside of ATTEMPT() blocks
|
||||
*/
|
||||
|
||||
#define FAIL_ZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x == 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define FAIL_NONZERO_BREAK(__err_context, __x, __err, __message, ...) \
|
||||
if ( __x != 0 ) { \
|
||||
FAIL(__err_context, __err, __message, ##__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define FAIL_BREAK(__err_context, __err_, __message, ...) \
|
||||
FAIL(__err_context, __err_, __message, ##__VA_ARGS__); \
|
||||
break;
|
||||
|
||||
#define SUCCEED_BREAK(__err_context) \
|
||||
SUCCEED(__err_context); \
|
||||
break;
|
||||
|
||||
/*
|
||||
* General failure and success methods
|
||||
*/
|
||||
|
||||
#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__); \
|
||||
__err_context->lineno = __LINE__; \
|
||||
snprintf((char *)__err_context->message, MAX_ERROR_CONTEXT_STRING_LENGTH, __message, ## __VA_ARGS__); \
|
||||
__err_context->stacktracebufptr += sprintf(__err_context->stacktracebufptr, "%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);
|
||||
|
||||
|
||||
#define SUCCEED(__err_context) \
|
||||
ENSURE_ERROR_READY(__err_context); \
|
||||
__err_context->status = 0;
|
||||
|
||||
/*
|
||||
* Defines for the ATTEMPT/CATCH/CLEANUP/PROCESS/HANDLE/FINISH process
|
||||
*/
|
||||
|
||||
#define ATTEMPT \
|
||||
switch ( 0 ) { \
|
||||
case 0: \
|
||||
|
||||
#define DETECT(__err_context, __stmt) \
|
||||
__stmt; \
|
||||
if ( __err_context != NULL ) { \
|
||||
__err_context->stacktracebufptr += sprintf(__err_context->stacktracebufptr, "%s:%s:%d: Detected error %d from heap (refcount %d)\n", (char *)__FILE__, (char *)__func__, __LINE__, __err_context->heapid, __err_context->refcount); \
|
||||
if ( __err_context->status != 0 ) { \
|
||||
__err_context->stacktracebufptr += sprintf(__err_context->stacktracebufptr, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CATCH(__err_context, __stmt) \
|
||||
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 **"); \
|
||||
}
|
||||
|
||||
#define CLEANUP \
|
||||
};
|
||||
|
||||
#define PROCESS(__err_context) \
|
||||
if ( __err_context != NULL ) { \
|
||||
switch ( __err_context->status ) { \
|
||||
case 0: \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE(__err_context, __err_status) \
|
||||
break; \
|
||||
case __err_status: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE_GROUP(__err_context, __err_status) \
|
||||
case __err_status: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define HANDLE_DEFAULT(__err_context) \
|
||||
break; \
|
||||
default: \
|
||||
__err_context->stacktracebufptr = (char *)&__err_context->stacktracebuf; \
|
||||
__err_context->handled = true;
|
||||
|
||||
#define FINISH(__err_context, __pass_up) \
|
||||
}; \
|
||||
}; \
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false && __pass_up == true ) { \
|
||||
__err_context->stacktracebufptr += sprintf(__err_context->stacktracebufptr, "%s:%s:%d\n", (char *)__FILE__, (char *)__func__, __LINE__); \
|
||||
return __err_context; \
|
||||
} \
|
||||
} \
|
||||
RELEASE_ERROR(__err_context);
|
||||
|
||||
#define FINISH_NORETURN(__err_context) \
|
||||
}; \
|
||||
}; \
|
||||
if ( __err_context != NULL ) { \
|
||||
if ( __err_context->handled == false ) { \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "Unhandled Error"); \
|
||||
error_handler_unhandled_error(__err_context); \
|
||||
} \
|
||||
} \
|
||||
RELEASE_ERROR(__err_context);
|
||||
|
||||
#define CATCH_AND_RETURN(__err_context, __stmt) \
|
||||
ATTEMPT { \
|
||||
CATCH(__err_context, __stmt); \
|
||||
} CLEANUP { \
|
||||
} PROCESS(__err_context) { \
|
||||
} FINISH(__err_context, true);
|
||||
|
||||
|
||||
#endif // _ERROR_H_
|
||||
16
scripts/generrno.sh
Normal file
16
scripts/generrno.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
|
||||
srcdir=$1
|
||||
rm -f ${srcdir}/src/errno.c
|
||||
echo "#include <akerror.h>" >> ${srcdir}/src/errno.c
|
||||
echo "#include <errno.h>" >> ${srcdir}/src/errno.c
|
||||
echo "void akerr_init_errno(void) {" >> ${srcdir}/src/errno.c
|
||||
maxval=$(errno --list | cut -d ' ' -f 2 | sort -g | tail -n 1)
|
||||
errno --list | while read LINE; do
|
||||
define=$(echo "$LINE" | cut -d ' ' -f 1);
|
||||
value=$(echo "$LINE" | cut -d ' ' -f 2);
|
||||
desc=$(echo "$LINE" | cut -d ' ' -f 3-);
|
||||
echo " akerr_name_for_status(${define}, \"${desc}\");" >> ${srcdir}/src/errno.c ;
|
||||
done;
|
||||
echo "}" >> ${srcdir}/src/errno.c
|
||||
sed "s/#define AKERR_LAST_ERRNO_VALUE .*/#define AKERR_LAST_ERRNO_VALUE ${maxval}/" ${srcdir}/include/akerror.tmpl.h > ${srcdir}/include/akerror.h
|
||||
137
src/error.c
137
src/error.c
@@ -1,49 +1,92 @@
|
||||
#include "sdlerror.h"
|
||||
#include "stdlib.h"
|
||||
#include "akerror.h"
|
||||
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#endif // AKERR_USE_STDLIB
|
||||
|
||||
ErrorContext __error_last_ditch;
|
||||
ErrorContext *__error_last_ignored;
|
||||
ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
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 HEAP_ERROR[MAX_HEAP_ERROR];
|
||||
akerr_ErrorContext AKERR_ARRAY_ERROR[AKERR_MAX_ARRAY_ERROR];
|
||||
|
||||
void error_init()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int i = 0;
|
||||
PREPARE_ERROR(e);
|
||||
ATTEMPT {
|
||||
CATCH(e, akerr_user_main(argc, argv));
|
||||
if ( e == NULL || e->status == 0 ) {
|
||||
/* User code claims everything went fine, doublecheck the error array before we quit */
|
||||
for ( i = AKERR_MAX_ARRAY_ERROR - 1; i >= 0; i-- ) {
|
||||
if ( AKERR_ARRAY_ERROR[i].status != 0 && AKERR_ARRAY_ERROR[i].refcount != 0 ) {
|
||||
akerr_log_method("%s:%s:%d: Found unhandled and unreported error in the array at index %d\n", __FILE__, (char *)__func__, __LINE__, i);
|
||||
e = &AKERR_ARRAY_ERROR[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH_NORETURN(e);
|
||||
SUCCEED(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void akerr_default_logger(const char *fmt, ...)
|
||||
{
|
||||
#if defined(AKERR_USE_STDLIB) && AKERR_USE_STDLIB == 1
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
void akerr_init()
|
||||
{
|
||||
static int inited = 0;
|
||||
if ( inited == 0 ) {
|
||||
for (int i = 0; i < MAX_HEAP_ERROR; i++ ) {
|
||||
memset((void *)&HEAP_ERROR[i], 0x00, sizeof(ErrorContext));
|
||||
HEAP_ERROR[i].heapid = i;
|
||||
HEAP_ERROR[i].stacktracebufptr = (char *)&HEAP_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;
|
||||
error_handler_unhandled_error = &error_default_handler_unhandled_error;
|
||||
memset((void *)&__ERROR_NAMES[0], 0x00, ((MAX_ERR_VALUE+1) * MAX_ERROR_NAME_LENGTH));
|
||||
__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;
|
||||
}
|
||||
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_SDL, "SDL Library 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);
|
||||
@@ -51,31 +94,31 @@ void error_default_handler_unhandled_error(ErrorContext *errctx)
|
||||
exit(errctx->status);
|
||||
}
|
||||
|
||||
ErrorContext *heap_next_error()
|
||||
akerr_ErrorContext *akerr_next_error()
|
||||
{
|
||||
for (int i = 0; i < MAX_HEAP_ERROR; i++ ) {
|
||||
if ( HEAP_ERROR[i].refcount == 0 ) {
|
||||
return &HEAP_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 *heap_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, "heap_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->heapid;
|
||||
memset(err, 0x00, sizeof(ErrorContext));
|
||||
oldid = err->arrayid;
|
||||
memset(err, 0x00, sizeof(akerr_ErrorContext));
|
||||
err->stacktracebufptr = (char *)&err->stacktracebuf;
|
||||
err->heapid = oldid;
|
||||
err->arrayid = oldid;
|
||||
return NULL;
|
||||
}
|
||||
return err;
|
||||
@@ -84,13 +127,13 @@ ErrorContext *heap_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 "sdlerror.h"
|
||||
#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 {
|
||||
@@ -23,14 +23,15 @@ ErrorContext *func1(void)
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
akerr_ErrorContext *akerr_user_main(int argc, char **argv)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, func1());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_NULLPOINTER) {
|
||||
SDL_Log("Caught exception");
|
||||
} FINISH_NORETURN(errctx);
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
akerr_log_method("Caught exception");
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED(errctx);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
#include "sdlerror.h"
|
||||
#include "akerror.h"
|
||||
|
||||
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 {
|
||||
@@ -25,7 +25,7 @@ ErrorContext *func1(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
akerr_ErrorContext *akerr_user_main(int argc, char **argv)
|
||||
{
|
||||
x = 12345;
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -33,11 +33,10 @@ 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;
|
||||
FAIL_RETURN(errctx, AKERR_API, "Cleanup does not work");
|
||||
}
|
||||
return 1;
|
||||
} FINISH_NORETURN(errctx);
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED(errctx);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#include "sdlerror.h"
|
||||
#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 {
|
||||
@@ -23,12 +23,13 @@ ErrorContext *func1(void)
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
akerr_ErrorContext *akerr_user_main(int argc, char **argv)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, func1());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED(errctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user