Make stdlib optional. Remove dependency on SDL3. Update README.
This commit is contained in:
@@ -1,23 +1,21 @@
|
||||
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(AKERROR_USE_STDLIB 1 CACHE BOOL "Use C standard library for error logging")
|
||||
set(akerror_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/akerror")
|
||||
|
||||
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 STATIC
|
||||
add_library(akerror STATIC
|
||||
src/error.c
|
||||
)
|
||||
|
||||
target_compile_definitions(akerror
|
||||
PUBLIC AKERROR_USE_STDLIB=${AKERROR_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)
|
||||
@@ -26,35 +24,35 @@ add_test(NAME err_cleanup COMMAND test_err_cleanup)
|
||||
add_test(NAME err_trace COMMAND test_err_trace)
|
||||
|
||||
# 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)
|
||||
|
||||
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(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 +60,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)
|
||||
|
||||
159
README.md
159
README.md
@@ -2,19 +2,17 @@
|
||||
|
||||
This library provides a TRY/CATCH style exception handling mechanism for C.
|
||||
|
||||
# Dependencies
|
||||
# Why?
|
||||
|
||||
This library depends on the `SDL3` library and `stdlib`. Specifically it uses the SDL_Log method from SDL3.
|
||||
There is nothing wrong with C as it is. This library does not claim to fix some problem with C.
|
||||
|
||||
# Installation
|
||||
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.
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
Why? Because some programmers prefer to have the power of C with just a little bit of help in managing their errors.
|
||||
|
||||
# Philosophy of Use
|
||||
# Library Architecture
|
||||
|
||||
## Philosophy of Use
|
||||
|
||||
This library has 6 guiding principles:
|
||||
|
||||
@@ -25,60 +23,130 @@ This library has 6 guiding principles:
|
||||
* 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
|
||||
|
||||
# Using the library
|
||||
## Lifecycle of an error in the AKError library
|
||||
|
||||
## Simply
|
||||
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
|
||||
|
||||
Include it
|
||||
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
|
||||
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
|
||||
|
||||
```c
|
||||
#include <sdlerror.h>
|
||||
```
|
||||
## What is in an Error Context
|
||||
|
||||
Link against it
|
||||
The Error Context object is a simple object which contains a few things:
|
||||
|
||||
```sh
|
||||
cc -lsdlerror
|
||||
```
|
||||
* 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
|
||||
|
||||
.. Done.
|
||||
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.
|
||||
|
||||
## CMake dependencies
|
||||
## What are the control structures
|
||||
|
||||
Using pkg-config:
|
||||
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.
|
||||
|
||||
```sh
|
||||
pkg-config sdlerror --cflags
|
||||
pkg-config sdlerror --ldflags
|
||||
```
|
||||
## Functions and Return Codes
|
||||
|
||||
Using cmake:
|
||||
|
||||
```cmake
|
||||
find_package(sdlerror REQUIRED)
|
||||
pkg_check_modules(sdlerror REQUIRED sdlerror)
|
||||
target_link_libraries(YOUR_TARGET PRIVATE sdlerror::sdlerror)
|
||||
```
|
||||
|
||||
# Functions and Return Codes
|
||||
|
||||
This library can perform tests on any function or expression that returns an integer value.
|
||||
This library can catch errors from any function or expression that returns an integer value, or from functions that return `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()`.
|
||||
|
||||
# Error codes
|
||||
## Error codes
|
||||
|
||||
The library uses integer values to specify error codes inside of its context. These integer return codes are defined in `sdlerror.h` in the form of `ERR_xxxxx` where `xxxxx` is the name of the error code in question. See `sdlerror.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 `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.
|
||||
|
||||
You can define additional error types by defining additional `ERR_xxxxx` values. 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 `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:
|
||||
|
||||
```c
|
||||
error_name_for_status(129, "Some Error Code Description")
|
||||
```
|
||||
|
||||
When you add additional error codes, you need to define `-DMAX_ERR_VALUE=n` where `n` is the maximum error code you have defined.
|
||||
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.
|
||||
|
||||
# Setting up the error context
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
## 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 -DAKERROR_USE_STDLIB=OFF
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
```
|
||||
|
||||
# Using the library
|
||||
|
||||
## Setting up your project
|
||||
|
||||
Include it
|
||||
|
||||
```c
|
||||
#include <akerror.h>
|
||||
```
|
||||
|
||||
Link the library directly, or
|
||||
|
||||
```sh
|
||||
cc -lakerror
|
||||
```
|
||||
|
||||
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 */
|
||||
error_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.
|
||||
|
||||
@@ -88,9 +156,7 @@ 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.
|
||||
|
||||
#
|
||||
|
||||
# Attempting an operation
|
||||
## Attempting an Operation
|
||||
|
||||
```c
|
||||
ATTEMPT {
|
||||
@@ -122,7 +188,7 @@ ATTEMPT {
|
||||
} // ...
|
||||
```
|
||||
|
||||
This will call 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 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.
|
||||
|
||||
## Setting errors from functions or expressions returning integer
|
||||
|
||||
@@ -277,3 +343,4 @@ From bottom to top, we have:
|
||||
* 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}/akerror.cmake")
|
||||
@@ -1,11 +1,12 @@
|
||||
#ifndef _ERROR_H_
|
||||
#define _ERROR_H_
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_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
|
||||
@@ -15,7 +16,7 @@
|
||||
|
||||
#define ERR_NULLPOINTER 1
|
||||
#define ERR_OUTOFBOUNDS 2
|
||||
#define ERR_SDL 3
|
||||
#define ERR_API 3
|
||||
#define ERR_ATTRIBUTE 4
|
||||
#define ERR_TYPE 5
|
||||
#define ERR_KEY 6
|
||||
@@ -55,9 +56,11 @@ typedef struct
|
||||
#define ERROR_NOIGNORE __attribute__((warn_unused_result))
|
||||
|
||||
typedef void (*ErrorUnhandledErrorHandler)(ErrorContext *errctx);
|
||||
typedef void (*ErrorLogFunction)(const char *f, ...);
|
||||
|
||||
extern ErrorContext HEAP_ERROR[MAX_HEAP_ERROR];
|
||||
extern ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
extern ErrorLogFunction error_log_method;
|
||||
extern ErrorContext *__error_last_ignored;
|
||||
|
||||
ErrorContext ERROR_NOIGNORE *heap_release_error(ErrorContext *ptr);
|
||||
@@ -65,9 +68,10 @@ 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);
|
||||
void error_default_logger(const char *f, ...);
|
||||
|
||||
#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); \
|
||||
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); \
|
||||
|
||||
#define LOG_ERROR(__err_context) \
|
||||
LOG_ERROR_WITH_MESSAGE(__err_context, "");
|
||||
@@ -85,7 +89,7 @@ void error_default_handler_unhandled_error(ErrorContext *ptr);
|
||||
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__); \
|
||||
error_log_method("%s:%s:%d: Unable to pull an ErrorContext from the heap!", __FILE__, (char *)__func__, __LINE__); \
|
||||
exit(1); \
|
||||
} \
|
||||
} \
|
||||
27
src/error.c
27
src/error.c
@@ -1,14 +1,32 @@
|
||||
#include "sdlerror.h"
|
||||
#include "stdlib.h"
|
||||
#include "akerror.h"
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_USE_STDLIB == 1
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#endif // AKERROR_USE_STDLIB
|
||||
|
||||
ErrorContext __error_last_ditch;
|
||||
ErrorContext *__error_last_ignored;
|
||||
ErrorUnhandledErrorHandler error_handler_unhandled_error;
|
||||
ErrorLogFunction error_log_method = NULL;
|
||||
|
||||
char __ERROR_NAMES[MAX_ERR_VALUE+1][MAX_ERROR_NAME_LENGTH];
|
||||
|
||||
ErrorContext HEAP_ERROR[MAX_HEAP_ERROR];
|
||||
|
||||
void error_default_logger(const char *fmt, ...)
|
||||
{
|
||||
#if defined(AKERROR_USE_STDLIB) && AKERROR_USE_STDLIB == 1
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
void error_init()
|
||||
{
|
||||
static int inited = 0;
|
||||
@@ -21,12 +39,15 @@ void error_init()
|
||||
__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;
|
||||
}
|
||||
error_handler_unhandled_error = &error_default_handler_unhandled_error;
|
||||
memset((void *)&__ERROR_NAMES[0], 0x00, ((MAX_ERR_VALUE+1) * 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_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");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "sdlerror.h"
|
||||
#include "akerror.h"
|
||||
|
||||
ErrorContext *func2(void)
|
||||
{
|
||||
@@ -31,6 +31,6 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, ERR_NULLPOINTER) {
|
||||
SDL_Log("Caught exception");
|
||||
error_log_method("Caught exception");
|
||||
} FINISH_NORETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "sdlerror.h"
|
||||
#include "akerror.h"
|
||||
|
||||
int x;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "sdlerror.h"
|
||||
#include "akerror.h"
|
||||
|
||||
ErrorContext *func2(void)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user