diff --git a/.travis.yml b/.travis.yml index f92119d..88036dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: c -dist: bionic +dist: focal compiler: - gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index 12b5f3b..57d950e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.10) project(panic C) -set(CMAKE_C_STANDARD 99) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror -Wpedantic -pedantic-errors") +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wpedantic -Werror -pedantic-errors") # dependencies include_directories(deps) diff --git a/LICENSE b/LICENSE index affd9b9..dba43de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Davide Di Carlo +Copyright (c) 2020 Davide Di Carlo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index ae1c19f..2efc568 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,32 @@ [![Build Status](https://travis-ci.org/daddinuz/panic.svg?branch=master)](https://travis-ci.org/daddinuz/panic) -A panic library to abort execution on non-recoverable errors with a detailed message. +A panic library to abort execution of the current thread on non-recoverable errors with a detailed message. ```c +#include #include #include -double divide(const double dividend, const double divisor) { - panic_when(0 == divisor); +// thread local handler called before terminating the thread. +static void panicHandler(void) { + fprintf(stderr, " Here: '%s:%d (%s)'\n", __FILE__, __LINE__, __func__); +} + +// global handler called before terminating the whole program, this handler will run after panicHandler. +static void exitHandler(void) { + fprintf(stderr, " Here: '%s:%d (%s)'\n", __FILE__, __LINE__, __func__); +} + +static double divide(const double dividend, const double divisor) { + panic_assert(0.000001 < divisor || divisor < -0.000001, "Division by zero"); return dividend / divisor; } -int main() { - printf("%lf\r\n", divide(8, 0)); +int main(void) { + atexit(exitHandler); + panic_registerHandler(panicHandler); + printf("%f\r\n", divide(8, 0)); return 0; } ``` @@ -26,8 +39,10 @@ Traceback (most recent call last): [0]: (main) ->-: (divide) current function - At: '/panic/examples/main.c:37' -Cause: `0 == divisor` + At: '/panic/examples/main.c:43' +Cause: Division by zero + Here: '/panic/examples/main.c:34 (panicHandler)' + Here: '/panic/examples/main.c:39 (exitHandler)' ``` ### Optional features diff --git a/clib.json b/clib.json index f997632..bbe4b58 100644 --- a/clib.json +++ b/clib.json @@ -1,7 +1,7 @@ { "name": "panic", "repo": "daddinuz/panic", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "description": "A panic library to abort execution on non-recoverable errors with a detailed message.", "keywords": [ @@ -14,11 +14,12 @@ "sources/panic.c" ], "development": { - "daddinuz/traits": "4.0.1", - "daddinuz/traits-unit": "4.0.2" + "daddinuz/traits": "4.1.0", + "daddinuz/traits-unit": "4.1.0" }, "dependencies": { - "daddinuz/trace": "2.0.0" + "daddinuz/trace": "3.0.0", + "daddinuz/stringify": "3.0.0" }, "makefile": "sources/build.cmake" } diff --git a/deps/stringify/clib.json b/deps/stringify/clib.json index 854fa1e..8fbe559 100644 --- a/deps/stringify/clib.json +++ b/deps/stringify/clib.json @@ -1,7 +1,7 @@ { "name": "stringify", "repo": "daddinuz/stringify", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "description": "Stringify all the things!", "keywords": [ diff --git a/deps/stringify/stringify.h b/deps/stringify/stringify.h index 610e365..77bc818 100644 --- a/deps/stringify/stringify.h +++ b/deps/stringify/stringify.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -31,8 +31,8 @@ extern "C" { #endif -#define __stringify(s) #s -#define stringify(s) __stringify(s) +#define stringify_quote(s) #s +#define stringify_lazyQuote(s) stringify_quote(s) #ifdef __cplusplus } diff --git a/deps/trace/clib.json b/deps/trace/clib.json index c713a97..2f577c9 100644 --- a/deps/trace/clib.json +++ b/deps/trace/clib.json @@ -1,10 +1,10 @@ { "name": "trace", "repo": "daddinuz/trace", - "version": "2.0.0", + "version": "3.0.0", "license": "MIT", "dependencies": { - "daddinuz/stringify": "2.0.0" + "daddinuz/stringify": "3.0.0" }, "src": [ "sources/trace.h" diff --git a/deps/trace/trace.h b/deps/trace/trace.h index fac8ae4..e97667c 100644 --- a/deps/trace/trace.h +++ b/deps/trace/trace.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -33,7 +33,7 @@ extern "C" { #endif -#define __TRACE__ __FILE__ ":" stringify(__LINE__) +#define TRACE __FILE__ ":" stringify_lazyQuote(__LINE__) #ifdef __cplusplus } diff --git a/deps/traits-unit/clib.json b/deps/traits-unit/clib.json index 94d7036..ddd0be3 100644 --- a/deps/traits-unit/clib.json +++ b/deps/traits-unit/clib.json @@ -1,7 +1,7 @@ { "name": "traits-unit", "repo": "daddinuz/traits-unit", - "version": "4.0.2", + "version": "4.1.0", "license": "MIT", "description": "Unittest framework written in C99.", "keywords": [ @@ -14,10 +14,10 @@ "test-framework" ], "development": { - "daddinuz/traits": "4.0.1" + "daddinuz/traits": "4.1.0" }, "dependencies": { - "daddinuz/trace": "2.0.0" + "daddinuz/trace": "3.0.0" }, "src": [ "sources/traits-unit.h", diff --git a/deps/traits-unit/traits-unit.c b/deps/traits-unit/traits-unit.c index 8d6ab9d..688197d 100644 --- a/deps/traits-unit/traits-unit.c +++ b/deps/traits-unit/traits-unit.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -132,7 +132,7 @@ static void traitsUnit_signalHandler(int signalId); /* * Define internal macros */ -#define traitsUnit_panic(fmt, ...) do { __traitsUnit_panic(__TRACE__, fmt, __VA_ARGS__); abort(); } while(false) +#define traitsUnit_panic(fmt, ...) do { __traitsUnit_panic(TRACE, fmt, __VA_ARGS__); abort(); } while(false) /* * Define public functions diff --git a/deps/traits-unit/traits-unit.h b/deps/traits-unit/traits-unit.h index 36ae86a..727506e 100644 --- a/deps/traits-unit/traits-unit.h +++ b/deps/traits-unit/traits-unit.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -135,10 +135,10 @@ extern int main(int argc, char *argv[]); #define __traitsUnit_featureId(Name) __traitsUnit_tokenJoin(traitsUnit_userFeature, Name) #define __traitsUnit_runFeature(Name, Fixture, ...) \ - {.fixture=&__traitsUnit_fixtureId(Fixture), .feature=stringify(Name), .call=__traitsUnit_featureId(Name), .skip=false} + {.fixture=&__traitsUnit_fixtureId(Fixture), .feature=stringify_quote(Name), .call=__traitsUnit_featureId(Name), .skip=false} #define __traitsUnit_skipFeature(Name, Fixture, ...) \ - {.fixture=&__traitsUnit_fixtureId(Fixture), .feature=stringify(Name), .call=__traitsUnit_featureId(Name), .skip=true} + {.fixture=&__traitsUnit_fixtureId(Fixture), .feature=stringify_quote(Name), .call=__traitsUnit_featureId(Name), .skip=true} extern jmp_buf __traitsUnitJumpBuffer; diff --git a/deps/traits/clib.json b/deps/traits/clib.json index e79106c..9bc7b5e 100644 --- a/deps/traits/clib.json +++ b/deps/traits/clib.json @@ -1,7 +1,7 @@ { "name": "traits", "repo": "daddinuz/traits", - "version": "4.0.1", + "version": "4.1.0", "license": "MIT", "description": "Assertions library written in C99.", "keywords": [ @@ -10,7 +10,8 @@ "assertions-library" ], "dependencies": { - "daddinuz/trace": "2.0.0" + "daddinuz/trace": "3.0.0", + "daddinuz/stringify": "3.0.0" }, "src": [ "sources/traits.h" diff --git a/deps/traits/traits.h b/deps/traits/traits.h index 9c11553..42d64c2 100644 --- a/deps/traits/traits.h +++ b/deps/traits/traits.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,6 +25,7 @@ #pragma once +#include #include #ifdef __cplusplus @@ -143,7 +144,7 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * Basic */ -#define __assert_that(c, ...) __traits_assert((c), __TRACE__, stringify(c), __VA_ARGS__) +#define __assert_that(c, ...) __traits_assert((c), TRACE, stringify_lazyQuote(c), __VA_ARGS__) #define __assert_that0(c) __assert_that(c, NULL) #define __assert_that1(c, ...) __assert_that(c, __VA_ARGS__) #define assert_that(...) __traits_overload1(__assert_that0, __assert_that1, __VA_ARGS__) @@ -153,12 +154,12 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * Boolean */ -#define __assert_true(c, ...) __traits_assert(eq(true, c), __TRACE__, stringify(c), __VA_ARGS__) +#define __assert_true(c, ...) __traits_assert(eq(true, c), TRACE, stringify_lazyQuote(c), __VA_ARGS__) #define __assert_true0(c) __assert_true(c, "Expected to be true.\r\n") #define __assert_true1(c, ...) __assert_true(c, __VA_ARGS__) #define assert_true(...) __traits_overload1(__assert_true0, __assert_true1, __VA_ARGS__) -#define __assert_false(c, ...) __traits_assert(eq(false, c), __TRACE__, stringify(c), __VA_ARGS__) +#define __assert_false(c, ...) __traits_assert(eq(false, c), TRACE, stringify_lazyQuote(c), __VA_ARGS__) #define __assert_false0(c) __assert_false(c, "Expected to be false.\r\n") #define __assert_false1(c, ...) __assert_false(c, __VA_ARGS__) #define assert_false(...) __traits_overload1(__assert_false0, __assert_false1, __VA_ARGS__) @@ -166,32 +167,32 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * Numerical */ -#define __assert_equal(e, a, ...) __traits_assert(eq(e, a), __TRACE__, stringify(eq(e, a)), __VA_ARGS__) +#define __assert_equal(e, a, ...) __traits_assert(eq(e, a), TRACE, stringify_lazyQuote(eq(e, a)), __VA_ARGS__) #define __assert_equal0(e, a) __assert_equal(e, a, "Expected to be equal.\r\n") #define __assert_equal1(e, a, ...) __assert_equal(e, a, __VA_ARGS__) #define assert_equal(...) __traits_overload2(__assert_equal0, __assert_equal1, __VA_ARGS__) -#define __assert_not_equal(e, a, ...) __traits_assert(ne(e, a), __TRACE__, stringify(ne(e, a)), __VA_ARGS__) +#define __assert_not_equal(e, a, ...) __traits_assert(ne(e, a), TRACE, stringify_lazyQuote(ne(e, a)), __VA_ARGS__) #define __assert_not_equal0(e, a) __assert_not_equal(e, a, "Expected to be not equal.\r\n") #define __assert_not_equal1(e, a, ...) __assert_not_equal(e, a, __VA_ARGS__) #define assert_not_equal(...) __traits_overload2(__assert_not_equal0, __assert_not_equal1, __VA_ARGS__) -#define __assert_greater(e, a, ...) __traits_assert(gt(e, a), __TRACE__, stringify(gt(e, a)), __VA_ARGS__) +#define __assert_greater(e, a, ...) __traits_assert(gt(e, a), TRACE, stringify_lazyQuote(gt(e, a)), __VA_ARGS__) #define __assert_greater0(e, a) __assert_greater(e, a, "Expected to be greater.\r\n") #define __assert_greater1(e, a, ...) __assert_greater(e, a, __VA_ARGS__) #define assert_greater(...) __traits_overload2(__assert_greater0, __assert_greater1, __VA_ARGS__) -#define __assert_greater_equal(e, a, ...) __traits_assert(ge(e, a), __TRACE__, stringify(ge(e, a)), __VA_ARGS__) +#define __assert_greater_equal(e, a, ...) __traits_assert(ge(e, a), TRACE, stringify_lazyQuote(ge(e, a)), __VA_ARGS__) #define __assert_greater_equal0(e, a) __assert_greater_equal(e, a, "Expected to be greater or equal.\r\n") #define __assert_greater_equal1(e, a, ...) __assert_greater_equal(e, a, __VA_ARGS__) #define assert_greater_equal(...) __traits_overload2(__assert_greater_equal0, __assert_greater_equal1, __VA_ARGS__) -#define __assert_less(e, a, ...) __traits_assert(lt(e, a), __TRACE__, stringify(lt(e, a)), __VA_ARGS__) +#define __assert_less(e, a, ...) __traits_assert(lt(e, a), TRACE, stringify_lazyQuote(lt(e, a)), __VA_ARGS__) #define __assert_less0(e, a) __assert_less(e, a, "Expected to be less.\r\n") #define __assert_less1(e, a, ...) __assert_less(e, a, __VA_ARGS__) #define assert_less(...) __traits_overload2(__assert_less0, __assert_less1, __VA_ARGS__) -#define __assert_less_equal(e, a, ...) __traits_assert(le(e, a), __TRACE__, stringify(le(e, a)), __VA_ARGS__) +#define __assert_less_equal(e, a, ...) __traits_assert(le(e, a), TRACE, stringify_lazyQuote(le(e, a)), __VA_ARGS__) #define __assert_less_equal0(e, a) __assert_less_equal(e, a, "Expected to be less or equal.\r\n") #define __assert_less_equal1(e, a, ...) __assert_less_equal(e, a, __VA_ARGS__) #define assert_less_equal(...) __traits_overload2(__assert_less_equal0, __assert_less_equal1, __VA_ARGS__) @@ -199,12 +200,12 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * Pointer */ -#define __assert_null(x, ...) __traits_assert(eq(NULL, x), __TRACE__, stringify(x), __VA_ARGS__) +#define __assert_null(x, ...) __traits_assert(eq(NULL, x), TRACE, stringify_lazyQuote(x), __VA_ARGS__) #define __assert_null0(x) __assert_null(x, "Expected to be null.\r\n") #define __assert_null1(x, ...) __assert_null(x, __VA_ARGS__) #define assert_null(...) __traits_overload1(__assert_null0, __assert_null1, __VA_ARGS__) -#define __assert_not_null(x, ...) __traits_assert(ne(NULL, x), __TRACE__, stringify(x), __VA_ARGS__) +#define __assert_not_null(x, ...) __traits_assert(ne(NULL, x), TRACE, stringify_lazyQuote(x), __VA_ARGS__) #define __assert_not_null0(x) __assert_not_null(x, "Expected to be not null.\r\n") #define __assert_not_null1(x, ...) __assert_not_null(x, __VA_ARGS__) #define assert_not_null(...) __traits_overload1(__assert_not_null0, __assert_not_null1, __VA_ARGS__) @@ -212,12 +213,12 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * Memory */ -#define __assert_memory_equal(e, a, s, ...) __traits_assert(eq(0, memcmp(e, a, s)), __TRACE__, stringify(eq(0, memcmp(e, a, s))), __VA_ARGS__) +#define __assert_memory_equal(e, a, s, ...) __traits_assert(eq(0, memcmp(e, a, s)), TRACE, stringify_lazyQuote(eq(0, memcmp(e, a, s))), __VA_ARGS__) #define __assert_memory_equal0(e, a, s) __assert_memory_equal(e, a, s, NULL) #define __assert_memory_equal1(e, a, s, ...) __assert_memory_equal(e, a, s, __VA_ARGS__) #define assert_memory_equal(...) __traits_overload3(__assert_memory_equal0, __assert_memory_equal1, __VA_ARGS__) -#define __assert_memory_not_equal(e, a, s, ...) __traits_assert(ne(0, memcmp(e, a, s)), __TRACE__, stringify(ne(0, memcmp(e, a, s))), __VA_ARGS__) +#define __assert_memory_not_equal(e, a, s, ...) __traits_assert(ne(0, memcmp(e, a, s)), TRACE, stringify_lazyQuote(ne(0, memcmp(e, a, s))), __VA_ARGS__) #define __assert_memory_not_equal0(e, a, s) __assert_memory_not_equal(e, a, s, NULL) #define __assert_memory_not_equal1(e, a, s, ...) __assert_memory_not_equal(e, a, s, __VA_ARGS__) #define assert_memory_not_equal(...) __traits_overload3(__assert_memory_not_equal0, __assert_memory_not_equal1, __VA_ARGS__) @@ -225,12 +226,12 @@ __traits_assert(const bool condition, const char *const __trace, const char *con /* * String */ -#define __assert_string_equal(e, a, ...) __traits_assert(eq(0, strcmp(e, a)), __TRACE__, stringify(eq(0, strcmp(e, a))), __VA_ARGS__) +#define __assert_string_equal(e, a, ...) __traits_assert(eq(0, strcmp(e, a)), TRACE, stringify_lazyQuote(eq(0, strcmp(e, a))), __VA_ARGS__) #define __assert_string_equal0(e, a) __assert_string_equal(e, a, "Expected to be equal.\r\n") #define __assert_string_equal1(e, a, ...) __assert_string_equal(e, a, __VA_ARGS__) #define assert_string_equal(...) __traits_overload2(__assert_string_equal0, __assert_string_equal1, __VA_ARGS__) -#define __assert_string_not_equal(e, a, ...) __traits_assert(ne(0, strcmp(e, a)), __TRACE__, stringify(ne(0, strcmp(e, a))), __VA_ARGS__) +#define __assert_string_not_equal(e, a, ...) __traits_assert(ne(0, strcmp(e, a)), TRACE, stringify_lazyQuote(ne(0, strcmp(e, a))), __VA_ARGS__) #define __assert_string_not_equal0(e, a) __assert_string_not_equal(e, a, "Expected to be not equal.\r\n") #define __assert_string_not_equal1(e, a, ...) __assert_string_not_equal(e, a, __VA_ARGS__) #define assert_string_not_equal(...) __traits_overload2(__assert_string_not_equal0, __assert_string_not_equal1, __VA_ARGS__) diff --git a/examples/main.c b/examples/main.c index 27fd75f..8073ed6 100644 --- a/examples/main.c +++ b/examples/main.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,15 +25,28 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include -double divide(const double dividend, const double divisor) { - panic_when(0 == divisor); +// thread local handler called before terminating the thread. +static void panicHandler(void) { + fprintf(stderr, " Here: '%s:%d (%s)'\n", __FILE__, __LINE__, __func__); +} + +// global handler called before terminating the whole program, this handler will run after panicHandler. +static void exitHandler(void) { + fprintf(stderr, " Here: '%s:%d (%s)'\n", __FILE__, __LINE__, __func__); +} + +static double divide(const double dividend, const double divisor) { + panic_assert(0.000001 < divisor || divisor < -0.000001, "Division by zero"); return dividend / divisor; } -int main() { - printf("%lf\r\n", divide(8, 0)); +int main(void) { + atexit(exitHandler); + panic_registerHandler(panicHandler); + printf("%f\r\n", divide(8, 0)); return 0; } diff --git a/sources/build.cmake b/sources/build.cmake index 6cc11ef..4e29afc 100644 --- a/sources/build.cmake +++ b/sources/build.cmake @@ -1,12 +1,10 @@ -unset(PANIC_UNWIND_SUPPORT CACHE) -unset(LIBUNWIND_FOUND CACHE) - set(ARCHIVE_NAME panic) message("${ARCHIVE_NAME}@${CMAKE_CURRENT_LIST_DIR} using: ${CMAKE_CURRENT_LIST_FILE}") file(GLOB ARCHIVE_HEADERS ${CMAKE_CURRENT_LIST_DIR}/*.h) file(GLOB ARCHIVE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/*.c) add_library(${ARCHIVE_NAME} ${ARCHIVE_HEADERS} ${ARCHIVE_SOURCES}) +target_link_libraries(${ARCHIVE_NAME} PRIVATE pthread) # Optional features option(PANIC_UNWIND_SUPPORT "Stack unwinding support" OFF) diff --git a/sources/panic.c b/sources/panic.c index 5bdb55b..964466c 100644 --- a/sources/panic.c +++ b/sources/panic.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,30 +25,39 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include +#include #include #include #include +#include +#include +#if PANIC_UNWIND_SUPPORT +#define UNW_LOCAL_ONLY +#include +#endif #include "panic.h" +#ifdef __WIN32 #define NEWLINE "\r\n" +#else +#define NEWLINE "\n" +#endif -static PanicHandler globalPanicHandler = NULL; +thread_local static PanicHandler threadLocalPanicHandler = NULL; -static void terminate(const char *trace, const char *format, va_list args) -__attribute__((__noreturn__, __nonnull__(1, 2), __format__(__printf__, 2, 0))); +noreturn static void terminate(const char *restrict trace, const char *restrict format, va_list args) +__attribute__((__nonnull__(1, 2), __format__(__printf__, 2, 0))); -static void backtrace(FILE *stream) +static void stacktrace(FILE *stream) __attribute__((__nonnull__)); -PanicHandler panic_register(const PanicHandler handler) { - const PanicHandler backup = globalPanicHandler; - globalPanicHandler = handler; +PanicHandler panic_registerHandler(const PanicHandler handler) { + const PanicHandler backup = threadLocalPanicHandler; + threadLocalPanicHandler = handler; return backup; } -void __panic(const char *const trace, const char *const format, ...) { +void panic_abort(const char *const trace, const char *const format, ...) { assert(NULL != trace); assert(NULL != format); va_list args; @@ -56,37 +65,17 @@ void __panic(const char *const trace, const char *const format, ...) { terminate(trace, format, args); } -void __vpanic(const char *const trace, const char *const format, va_list args) { +void panic_vabort(const char *const trace, const char *const format, va_list args) { assert(NULL != trace); assert(NULL != format); terminate(trace, format, args); } -void __panic_when(const bool condition, const char *const trace, const char *const format, ...) { - assert(NULL != trace); - assert(NULL != format); - if (condition) { - va_list args; - va_start(args, format); - terminate(trace, format, args); - } -} - -void __panic_unless(const bool condition, const char *const trace, const char *const format, ...) { - assert(NULL != trace); - assert(NULL != format); - if (!condition) { - va_list args; - va_start(args, format); - terminate(trace, format, args); - } -} - void terminate(const char *const trace, const char *const format, va_list args) { assert(NULL != trace); assert(NULL != format); fputs(NEWLINE, stderr); - backtrace(stderr); + stacktrace(stderr); fprintf(stderr, " At: '%s'" NEWLINE, trace); if (0 != errno) { @@ -98,55 +87,52 @@ void terminate(const char *const trace, const char *const format, va_list args) fputs(NEWLINE, stderr); va_end(args); - if (NULL != globalPanicHandler) { - globalPanicHandler(); + if (NULL != threadLocalPanicHandler) { + threadLocalPanicHandler(); } - abort(); + thrd_exit(EXIT_FAILURE); } +void stacktrace(FILE *const stream) { + assert(NULL != stream); + (void) stream; #if PANIC_UNWIND_SUPPORT -#define N_SIZE 8 -#define M_SIZE 32 +#define UNWIND_STEPS 8u +#define UNWIND_FN_NAME_LEN 32u -#define UNW_LOCAL_ONLY - -#include - -void backtrace(FILE *const stream) { - assert(NULL != stream); - char buffer[N_SIZE][M_SIZE + 1] = {{0}}; - unw_context_t context = {0}; - unw_cursor_t cursor = {0}; + char buffer[UNWIND_STEPS][UNWIND_FN_NAME_LEN + 1] = {{ 0 }}; + unw_context_t context = { 0 }; + unw_cursor_t cursor = { 0 }; const int previousError = errno; - size_t size = 0; + unsigned step = 0; unw_getcontext(&context); unw_init_local(&cursor, &context); // skip: terminate and panics function calls - for (size_t i = 0; i < 2; ++i) { + for (unsigned i = 0; i < 2; ++i) { if (unw_step(&cursor) <= 0) { errno = previousError; // restore errno return; // something wrong, exit } } - while (size < N_SIZE && - unw_step(&cursor) > 0 && - unw_get_proc_name(&cursor, buffer[size], M_SIZE, NULL) == 0 && - strcmp("main", buffer[size++]) != 0); + while (step < UNWIND_STEPS && + unw_step(&cursor) > 0 && + unw_get_proc_name(&cursor, buffer[step], UNWIND_FN_NAME_LEN, NULL) == 0 && + strcmp("main", buffer[step++]) != 0) {} - if (0 < size) { + if (0 < step) { fputs("Traceback (most recent call last):" NEWLINE, stream); - if (0 != strcmp("main", buffer[size - 1])) { + if (0 != strcmp("main", buffer[step - 1])) { fputs(" [ ]: (...)" NEWLINE, stream); } - for (size_t i = 1; i < size; ++i) { - fprintf(stream, " [%zu]: (%s)" NEWLINE, i - 1, buffer[size - i]); + for (unsigned i = 1; i < step; ++i) { + fprintf(stream, " [%d]: (%s)" NEWLINE, i - 1, buffer[step - i]); } fprintf(stream, " ->-: (%s) current function" NEWLINE, buffer[0]); @@ -154,13 +140,5 @@ void backtrace(FILE *const stream) { } errno = previousError; // restore errno -} - -#else - -void backtrace(FILE *const stream) { - assert(NULL != stream); - (void) stream; -} - #endif +} diff --git a/sources/panic.h b/sources/panic.h index 83ef4e1..427a55e 100644 --- a/sources/panic.h +++ b/sources/panic.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -27,6 +27,7 @@ #pragma once +#include #include #ifdef __cplusplus @@ -34,67 +35,60 @@ extern "C" { #endif #include -#include +#include -#if !defined(__GNUC__) +#if !(defined(__GNUC__) || defined(__clang__)) #define __attribute__(...) #endif /** - * Type signature of the callback to be executed before terminating. + * Type signature of the callback to be executed before terminating the current thread. */ typedef void (*PanicHandler)(void); /** - * Registers the handler function to execute before terminating. + * Registers the termination handler to be executed before terminating the current thread. + * The registered handler is thread local. * - * @param handler The function to be executed, if NULL nothing will be executed. + * @param handler The handler to be executed, if NULL nothing will be executed. * @return The previous registered handler if any else NULL. */ -extern PanicHandler panic_register(PanicHandler handler); +extern PanicHandler panic_registerHandler(PanicHandler handler); /** - * Reports the error and terminates execution. + * Reports the error and terminates execution of the current thread. * Takes printf-like arguments. + * + * @param trace Info about call site of this function. + * + * ``` + * panic_abort(__func__, "Some error description"); + * ``` */ -#define panic(...) \ - __panic(__TRACE__, __VA_ARGS__) +noreturn extern void panic_abort(const char *restrict trace, const char *restrict format, ...) +__attribute__((__nonnull__(1, 2), __format__(__printf__, 2, 3))); /** - * Terminates execution if condition is `true`. + * Variadic version of panic_abort. */ -#define panic_when(condition) \ - __panic_when((condition), __TRACE__, "`%s`", stringify(condition)) +noreturn extern void panic_vabort(const char *restrict trace, const char *restrict format, va_list args) +__attribute__((__nonnull__(1, 2), __format__(__printf__, 2, 0))); /** - * Terminates execution if condition is `false`. - */ -#define panic_unless(condition) \ - __panic_unless((condition), __TRACE__, "`%s` is not met", stringify(condition)) - -/** - * @attention this function must be treated as opaque therefore should not be called directly. - */ -extern void __panic(const char *trace, const char *format, ...) -__attribute__((__noreturn__, __nonnull__(1, 2), __format__(__printf__, 2, 3))); - -/** - * @attention this function must be treated as opaque therefore should not be called directly. + * Reports the error and terminates execution of the current thread. + * Takes printf-like arguments. */ -extern void __vpanic(const char *trace, const char *format, va_list args) -__attribute__((__noreturn__, __nonnull__(1, 2), __format__(__printf__, 2, 0))); +#define panic(...) panic_abort(TRACE, __VA_ARGS__) /** - * @attention this function must be treated as opaque therefore should not be called directly. + * Panics when condition is not met specifying a custom trace. */ -extern void __panic_when(bool condition, const char *trace, const char *format, ...) -__attribute__((__nonnull__(2, 3), __format__(__printf__, 3, 4))); +#define panic_assertWith(trace, condition, ...) ((void) ((condition) ? 1 : (panic_abort((trace), __VA_ARGS__), 0))) /** - * @attention this function must be treated as opaque therefore should not be called directly. + * Panics when condition is not met. */ -extern void __panic_unless(bool condition, const char *trace, const char *format, ...) -__attribute__((__nonnull__(2, 3), __format__(__printf__, 3, 4))); +#define panic_assert(condition, ...) ((void) ((condition) ? 1 : (panic_abort(TRACE, __VA_ARGS__), 0))) #ifdef __cplusplus } diff --git a/tests/unit/build.cmake b/tests/unit/build.cmake index f4fd044..592e6b6 100644 --- a/tests/unit/build.cmake +++ b/tests/unit/build.cmake @@ -1,8 +1,15 @@ -add_library(features ${CMAKE_CURRENT_LIST_DIR}/features.h ${CMAKE_CURRENT_LIST_DIR}/features.c) +set(HERE ${CMAKE_CURRENT_LIST_DIR}) + +add_library(fixtures ${HERE}/fixtures.h ${HERE}/fixtures.c) +target_link_libraries(fixtures PRIVATE panic traits-unit) + +add_library(features ${HERE}/features.h ${HERE}/features.c) target_link_libraries(features PRIVATE panic traits-unit) -add_executable(describe ${CMAKE_CURRENT_LIST_DIR}/describe.c) -target_link_libraries(describe PRIVATE features) +add_executable(describe ${HERE}/describe.c) +target_link_libraries(describe PRIVATE fixtures features) add_test(describe describe) enable_testing() + +unset(HERE) diff --git a/tests/unit/describe.c b/tests/unit/describe.c index 98cabad..39c0321 100644 --- a/tests/unit/describe.c +++ b/tests/unit/describe.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -25,12 +25,14 @@ * OTHER DEALINGS IN THE SOFTWARE. */ +#include "fixtures.h" #include "features.h" #include Describe("Panic", - Trait("", - Run(panic), - Run(panic_when), - Run(panic_unless), - Run(handler))) + Trait("", + Run(panic_registerHandler, PanicFixture), + Run(panic_abort, PanicFixture), + Run(panic_assertWith, PanicFixture), + Run(panic_assert, PanicFixture), + Run(panic, PanicFixture))) diff --git a/tests/unit/features.c b/tests/unit/features.c index 7e6757f..66c6616 100644 --- a/tests/unit/features.c +++ b/tests/unit/features.c @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -29,41 +29,86 @@ #include #include "features.h" -static void panicHandler(void) {} +void *context = NULL; -Feature(panic) { - const size_t counter = traitsUnit_getWrappedSignalsCounter(); +static void panicHandler(void) { + assert_not_null(context); + int *const theAnswer = context; + *theAnswer = 42; +} + +Feature(panic_registerHandler) { + assert_null(panic_registerHandler(panicHandler)); + assert_equal(panicHandler, panic_registerHandler(NULL)); + assert_null(panic_registerHandler(panicHandler)); + + context = traitsUnit_getContext(); + panic("Panicking"); + + // unreachable + assert_that(false); +} - traitsUnit_wrap(SIGABRT) { - panic("%s", "A panic message"); - } +Feature(panic_abort) { + assert_null(panic_registerHandler(panicHandler)); + context = traitsUnit_getContext(); - assert_equal(traitsUnit_getWrappedSignalsCounter(), counter + 1); + panic_abort(TRACE, "Panicking"); + + // unreachable + assert_that(false); } -Feature(panic_when) { - const size_t counter = traitsUnit_getWrappedSignalsCounter(); +Feature(panic_assertWith) { + // Protect against double expansion. + // + // Note: + // Ensuring that no handler is registered so that + // if `panic_assertWith` would not work properly, + // assertions in PanicTeardown won't pass. + panic_registerHandler(NULL); + int i = 1; + panic_assertWith(TRACE, i--, "Unmet condition"); + assert_equal(0, i); + + // Registering the handler now, we are going to terminate the current thread. + assert_null(panic_registerHandler(panicHandler)); + context = traitsUnit_getContext(); - traitsUnit_wrap(SIGABRT) { - panic_when(true); - } + panic_assertWith(TRACE, false, "Unmet condition"); - assert_equal(traitsUnit_getWrappedSignalsCounter(), counter + 1); - panic_when(false); + // unreachable + assert_that(false); } -Feature(panic_unless) { - const size_t counter = traitsUnit_getWrappedSignalsCounter(); +Feature(panic_assert) { + // Protect against double expansion. + // + // Note: + // Ensuring that no handler is registered so that + // if `panic_assert` would not work properly, + // assertions in PanicTeardown won't pass. + panic_registerHandler(NULL); + int i = 1; + panic_assert(i--, "Unmet condition"); + assert_equal(0, i); + + // Registering the handler now, we are going to terminate the current thread. + assert_null(panic_registerHandler(panicHandler)); + context = traitsUnit_getContext(); - traitsUnit_wrap(SIGABRT) { - panic_unless(false); - } + panic_assert(false, "Unmet condition"); - assert_equal(traitsUnit_getWrappedSignalsCounter(), counter + 1); - panic_unless(true); + // unreachable + assert_that(false); } -Feature(handler) { - assert_null(panic_register(panicHandler)); - assert_equal(panicHandler, panic_register(NULL)); +Feature(panic) { + assert_null(panic_registerHandler(panicHandler)); + context = traitsUnit_getContext(); + + panic("Panicking"); + + // unreachable + assert_that(false); } diff --git a/tests/unit/features.h b/tests/unit/features.h index 9bccbb3..b39d02b 100644 --- a/tests/unit/features.h +++ b/tests/unit/features.h @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2019 Davide Di Carlo + * Copyright (c) 2020 Davide Di Carlo * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation @@ -33,10 +33,11 @@ extern "C" { #endif +Feature(panic_registerHandler); +Feature(panic_abort); +Feature(panic_assertWith); +Feature(panic_assert); Feature(panic); -Feature(panic_when); -Feature(panic_unless); -Feature(handler); #ifdef __cplusplus } diff --git a/tests/unit/fixtures.c b/tests/unit/fixtures.c new file mode 100644 index 0000000..07c4e6d --- /dev/null +++ b/tests/unit/fixtures.c @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Davide Di Carlo + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include "fixtures.h" + +static int theAnswer = 0; + +Setup(PanicSetup); +Teardown(PanicTeardown); + +FixtureImplements(PanicFixture, PanicSetup, PanicTeardown); + +Setup(PanicSetup) { + panic_registerHandler(NULL); + theAnswer = 0; + return &theAnswer; +} + +Teardown(PanicTeardown) { + assert_not_null(panic_registerHandler(NULL)); + assert_equal(&theAnswer, traitsUnit_getContext()); + assert_equal(42, theAnswer); +} diff --git a/tests/unit/fixtures.h b/tests/unit/fixtures.h new file mode 100644 index 0000000..ecdfd3c --- /dev/null +++ b/tests/unit/fixtures.h @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Davide Di Carlo + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +Fixture(PanicFixture); + +#ifdef __cplusplus +} +#endif