libassert C++ Assertion Library

repository·main·Indexed 20 days ago

https://github.com/jeremy-rifkin/libassert

A high-diagnostic C++ assertion library providing automatic expression decomposition, stack traces, and rich formatting. It includes various assertion types such as DEBUG_ASSERT, ASSERT, ASSUME, PANIC, and UNREACHABLE, as well as value-returning variants like ASSERT_VAL for inline expressions. The library supports custom failure handlers, specialized stringification for custom objects, and configurable output options including diff highlighting and literal formatting modes.

Tokens
6.5K
Snippets
19
Records
29
Agent score
22%

What's inside libassert

  1. Compare libassert features with other languages

    main

    libassert provides a superior diagnostic experience compared to standard assertion implementations in C/C++, Rust, C#, Java, Python, and JavaScript. Key advantages include:

    • Automatic Expression Decomposition: Breaking down complex expressions into understandable parts.
    • Expression Strings: Displaying the actual code expression that failed, not just the resulting values.
    • Sub-expression Strings: Providing context for parts of a larger expression.
    • Extra Diagnostics: Providing additional debugging information beyond simple value comparisons.
    • Syntax Highlighting: Improved readability of error messages.
    • Inline Integration: Assertions return values, allowing them to be used within larger expressions.
  2. Use assertion value variants for in-line expressions

    main

    If you need to use the result of an expression within an assertion (e.g., assigning a pointer returned by fopen), use the _VAL variants. These return the value of the expression so they can be used in assignments.

    NameEffect
    DEBUG_ASSERT_VALChecked in debug; expression is evaluated in both debug and release.
    ASSERT_VALChecked in both debug and release.
    ASSUME_VALChecked in debug; in release, it is treated as if(!(expr)) { __builtin_unreachable(); }.

    Return Value Logic

    The returned value depends on the top-level operation:

    • No binary operation (e.g., ASSERT_VAL(foo())): Returns the expression value.
    • Comparison/Assignment (e.g., ==, !=, <, <=, >, >=, &&, ||, =, +=): Returns the left-hand operand.
    • Bitwise/Precedence-heavy (e.g., &, |, ^, <<, >>): Returns the result of the whole expression.
    // Example: Using ASSERT_VAL to assign a value while asserting success
    FILE* file = ASSERT_VAL(fopen(path, "r"), "Failed to open file");
    
    // Example: Returning the left-hand side of a comparison
    // If x > 2 is true, this returns x
    auto val = ASSERT_VAL(x > 2);
  3. Understand the different assertion types in libassert

    main

    Libassert provides several assertion macros categorized by their behavior in debug vs. release builds and whether they return a value.

    Standard Assertions

    NameEffect
    DEBUG_ASSERTChecked in debug; no code generated in release.
    ASSERTChecked in both debug and release builds.
    ASSUMEChecked in debug; in release, it is treated as if(!(expr)) { __builtin_unreachable(); }.

    Unconditional Assertions

    NameEffect
    PANICTriggers in both debug and release.
    UNREACHABLETriggers in debug; marked as unreachable in release.

    Note on ASSUME: Because ASSUME marks the failure path as unreachable in release, failing an assumption in a release build (-DNDEBUG) can lead to Undefined Behavior (UB). Use it only when the condition is a hard requirement for correctness that the optimizer can rely on.

  4. Quickstart with libassert

    main

    libassert is a C++ assertion library designed to provide rich diagnostic information (expression decomposition, stack traces, and extra diagnostics) upon failure.

    Assertion Types

    Conditional Assertions:

    • DEBUG_ASSERT: Checked in debug builds; no-op in release (similar to std::assert).
    • ASSERT: Checked in both debug and release builds.
    • ASSUME: Checked in debug; serves as an optimization hint in release.

    Unconditional Assertions:

    • PANIC: Triggers in both debug and release.
    • UNREACHABLE: Triggers in debug; marked as unreachable in release.

    Value-returning Assertions:

    • ASSERT_VAL and DEBUG_ASSERT_VAL: These return the value being asserted, allowing them to be used inline with assignments or function calls.

    Note: You can enable lowercase aliases (debug_assert, assert) by defining -DLIBASSERT_LOWERCASE during compilation.

    #include <libassert/assert.hpp>
    
    // Conditional assertion with extra diagnostics
    void zoog(const std::map<std::string, int>& map) {
        DEBUG_ASSERT(map.contains("foo"), "expected key not found", map);
    }
    
    // Value-returning assertion
    std::optional<float> get_param();
    float f = *ASSERT_VAL(get_param());
    
    // Unconditional assertion
    PANIC("This should never happen");
  5. Handle platform-specific logistics (Windows/macOS)

    main

    Windows: Copying .dll files

    If you are using dynamic linking on Windows, you must copy assert.dll to the same directory as your executable. You can automate this in CMake:

    if(WIN32)
      add_custom_command(
        TARGET your_target POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:libassert::assert>
        $<TARGET_FILE_DIR:your_target>
      )
    endif()

    macOS: Generating dSYM files

    On macOS, it is recommended to generate a .dSYM file for debug information.

    Using Xcode CMake:

    set_target_properties(your_target PROPERTIES XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym")

    Using dsymutil:

    if(APPLE)
      add_custom_command(
        TARGET your_target
        POST_BUILD
        COMMAND dsymutil $<TARGET_FILE:your_target>
      )
    endif()
  6. Use libassert with C++20 Modules

    main

    Libassert supports C++20 modules via import libassert;. This requires a modern toolchain. You must still #include the specific headers containing the macro definitions you need:

    • <libassert/assert-macros.hpp>: All library assertion macros
    • <libassert/assert-gtest-macros.hpp>: Macros for gtest integration
    • <libassert/assert-catch2-macros.hpp>: Macros for catch2 integration
  7. Install libassert for a local user

    main

    To install libassert to a custom prefix (e.g., for a local user) instead of system-wide, use the -DCMAKE_INSTALL_PREFIX flag during the CMake configuration step.

    git clone https://github.com/jeremy-rifkin/libassert.git
    git checkout v2.2.1
    mkdir libassert/build
    cd libassert/build
    cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
    make -j
    sudo make install

    Using with CMake:

    find_package(libassert REQUIRED PATHS $ENV{HOME}/wherever)
    target_link_libraries(<your target> libassert::assert)

    Manual Compilation:

    g++ main.cpp -o main -g -Wall -I$HOME/wherever/include -L$HOME/wherever/lib -lassert
  8. Use libassert without CMake

    main

    If you are not using CMake, you must manually specify include paths and link against the library. If linking statically, you must also define LIBASSERT_STATIC_DEFINE.

    ```text
    # Linux/macOS/Unix/MinGW
    -libassert -I[path] [cpptrace args]
    
    # MSVC (Windows)
    assert.lib /I[path] [cpptrace args]
    
    # Clang (Windows)
    -libassert -I[path] [cpptrace args]

    Replace [path] with the path to the folder containing libassert/assert.hpp.

  9. Replace <cassert> with libassert

    main

    Libassert is not a direct drop-in replacement for <cassert>. While you can use -DLIBASSERT_LOWERCASE to create lowercase aliases, note that libassert's ASSERT macro is still checked in release builds.

    To properly replace assert with a debug-only version, use a macro like this:

    #define assert(...) DEBUG_ASSERT(__VA_ARGS__)
  10. Enable programmatic breakpoints on assertion failure

    main

    To make assertions more debugger-friendly, you can enable programmatic breakpoints that trigger on assertion failure. This causes the debugger to break exactly on the assertion line rather than deep in the library's callstack.

    This feature is opt-in and should be enabled via a compiler flag.

    # For GCC/Clang
    -DLIBASSERT_BREAK_ON_FAIL
    
    # For MSVC
    /DLIBASSERT_BREAK_ON_FAIL
  11. Install libassert via CMake FetchContent

    main

    To integrate libassert into your CMake project, use FetchContent.

    Important Requirements:

    • Configure your build with -DCMAKE_BUILD_TYPE=Debug or -DDCMAKE_BUILD_TYPE=RelWithDebInfo to ensure symbols and line information are available for diagnostics.
    • Windows Users: You must copy libassert.dll to the same directory as your target executable.
    • macOS Users: It is recommended to generate a .dSYM file for proper stack traces.
    include(FetchContent)
    FetchContent_Declare(
      libassert
      GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git
      GIT_TAG        v2.2.1 # <HASH or TAG>
    )
    FetchContent_MakeAvailable(libassert)
    target_link_libraries(your_target libassert::assert)
    
    # On windows copy libassert.dll to the same directory as the executable for your_target
    if(WIN32)
      add_custom_command(
        TARGET your_target POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
        $<TARGET_FILE:libassert::assert>
        $<TARGET_FILE_DIR:your_target>
      )
    endif()
  12. Install libassert via Package Managers

    main

    Conan

    Add libassert/2.2.1 to your [requires] section and use CMakeDeps and CMakeToolchain generators.

    [requires]
    libassert/2.2.1
    [generators]
    CMakeDeps
    CMakeToolchain
    [layout]
    cmake_layout

    Vcpkg

    vcpkg install libassert