From fdd2dda1fab8a8bebf1ec0e7fedba88d064d62b4 Mon Sep 17 00:00:00 2001 From: Jeremy <51220084+jeremy-rifkin@users.noreply.github.com> Date: Fri, 31 May 2024 19:48:51 -0500 Subject: [PATCH] Update API and add documentation --- README.md | 44 +++++++++++++++++++++++++++++++++++- include/libassert/assert.hpp | 18 ++++++++------- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f705ea61..7c7d87f7 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - [Anatomy of Assertion Information](#anatomy-of-assertion-information) - [Stringification of Custom Objects](#stringification-of-custom-objects) - [Custom Failure Handlers](#custom-failure-handlers-1) + - [Breakpoints](#breakpoints) - [Other Donfigurations](#other-donfigurations) - [Integration with Test Libraries](#integration-with-test-libraries) - [Catch2](#catch2) @@ -99,12 +100,13 @@ You can enable the lowercase `debug_assert` and `assert` aliases with `-DLIBASSE - Syntax highlighting - Stack traces - `DEBUG_ASSERT_VAL` and `ASSERT_VAL` variants that return a value so they can be integrated seamlessly into code, e.g. - `FILE* f = ASSERT_VAL(fopen(path, "r") != nullptr)`. + `FILE* f = ASSERT_VAL(fopen(path, "r") != nullptr)` - Smart literal formatting - Stringification of user-defined types - Custom failure handlers - Catch2/Gtest integrations - {fmt} support +- Programatic breakpoints on assertion failures for more debugger-friendly assertions, more info below ## CMake FetchContent Usage @@ -727,6 +729,46 @@ all assertion types instead of aborting. > [!IMPORTANT] > Failure handlers must not return for `assert_type::panic` and `assert_type::unreachable`. +## Breakpoints + +Libassert supports programatic breakpoints on assertion failure to make assertions more debugger-friendly by breaking on +the assertion line as opposed to several layers deep in a callstack: + +![breakpoints](./screenshots/breakpoint.png) + +This functionality is currently opt-in and it can be enabled by defining `LIBASSERT_BREAK_ON_FAIL`. This is best done as +a compiler flag: `-DLIBASSERT_BREAK_ON_FAIL` or `/DLIBASSERT_BREAK_ON_FAIL`. + +Internally the library checks for the presense of a debugger before executing an instruction to breakpoint the debugger. +By default the check is only performed once on the first assertion failure. In some scenarios it may be desirable to +configure this check to always be performed, e.g. if you're using a custom assertion handler that throws an exception +instead of aborting and you may be able to recover from an assertion failure allowing additional failures later and you +only attach a debugger part-way through the run of your program. You can use `libassert::set_debugger_check_mode` to +control how this check is performed: + +```cpp +namespace libassert { + enum class debugger_check_mode { + check_once, + check_every_time, + }; + void set_debugger_check_mode(debugger_check_mode mode) noexcept; +} +``` + +The library also exposes its internal utilities for setting breakpoints and checking if the program is being debugged: + +```cpp +namespace libassert { + bool is_debugger_present() noexcept; +} +#define LIBASSERT_BREAKPOINT() <...internals...> +#define LIBASSERT_BREAKPOINT_IF_DEBUGGING() <...internals...> +``` + +This API mimics the API of [P2514](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2546r3.html), which has +been accepted to C++26. + ## Other Donfigurations **Defines:** diff --git a/include/libassert/assert.hpp b/include/libassert/assert.hpp index 0c7b062e..aca5153d 100644 --- a/include/libassert/assert.hpp +++ b/include/libassert/assert.hpp @@ -696,15 +696,17 @@ namespace libassert { #define LIBASSERT_IGNORE_UNUSED_VALUE #endif +#define LIBASSERT_BREAKPOINT_IF_DEBUGGING() \ + do \ + if(libassert::is_debugger_present()) { \ + LIBASSERT_BREAKPOINT(); \ + } \ + while(0) + #ifdef LIBASSERT_BREAK_ON_FAIL - #define LIBASSERT_BREAKPOINT_IF_DEBUGGING() \ - do \ - if(libassert::is_debugger_present()) { \ - LIBASSERT_BREAKPOINT(); \ - } \ - while(0) + #define LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL() LIBASSERT_BREAKPOINT_IF_DEBUGGING() #else - #define LIBASSERT_BREAKPOINT_IF_DEBUGGING() + #define LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL() #endif #define LIBASSERT_INVOKE(expr, name, type, failaction, ...) \ @@ -722,7 +724,7 @@ namespace libassert { LIBASSERT_WARNING_PRAGMA_POP_GCC \ if(LIBASSERT_STRONG_EXPECT(!static_cast(libassert_decomposer.get_value()), 0)) { \ libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \ - LIBASSERT_BREAKPOINT_IF_DEBUGGING(); \ + LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL(); \ failaction \ LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, #expr, __VA_ARGS__) \ if constexpr(sizeof libassert_decomposer > 32) { \