The most over-engineered C++ assertion library
-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)
The most over-engineered C++ assertion library
Library philosophy: Provide as much helpful diagnostic info as possible.
Some of the awesome things the library does:
#include
void zoog(const std::map& map) {
DEBUG_ASSERT(map.contains("foo"), "expected key not found", map);
}
ASSERT(vec.size() > min_items(), "vector doesn't have enough items", vec);
std::optional get_param();
float f = *ASSERT_VAL(get_param());
Types of assertions:
Conditional assertions:
DEBUG_ASSERT: Checked in debug but does nothing in release (analogous to the standard library's assert)ASSERT: Checked in both debug and releaseASSUME: Checked in debug and serves as an optimization hint in releaseUnconditional assertions:
PANIC: Triggers in both debug and releaseUNREACHABLE: Triggers in debug, marked as unreachable in releasePrefer lowecase assert?
You can enable the lowercase debug_assert and assert aliases with -DLIBASSERT_LOWERCASE.
Summary of features:
ASSERT_EQ etc.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)include(FetchContent)
FetchContent_Declare(
libassert
GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git
GIT_TAG v2.2.1 #
)
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
$
$
)
endif()
Be sure to configure with -DCMAKE_BUILD_TYPE=Debug or -DDCMAKE_BUILD_TYPE=RelWithDebInfo for symbols and line
information.
On macOS it is recommended to generate a .dSYM file, see Platform Logistics below.
For other ways to use the library, such as through package managers or a system-wide installation, see Usage below.
Fundamentally the role of assertions is to verify assumptions made in software and identify violations close to their sources. Assertion tooling should prioritize providing as much information and context to the developer as possible to allow for speedy triage. Unfortunately, existing language and library tooling provides very limited triage information.
For example with stdlib assertions an assertion such as assert(n 10); is automatically understood, as showcased above.
Values involved in assert expressions are displayed. Redundant diagnostics like 2 => 2 are avoided.
DEBUG_ASSERT(map.count(1) == 2);
Only the full assert expression is able to be extracted from a macro call. Showing which parts of the expression correspond to what values requires some basic expression parsing. C++ grammar is ambiguous but most expressions can be disambiguated.
All assertions in this library support optional diagnostic messages as well as arbitrary other diagnostic messages.
FILE* f = ASSERT_VAL(fopen(path, "r") != nullptr, "Internal error with foobars", errno, path);
Special handling is provided for errno, and strerror is automatically called.
Note: Extra diagnostics are only evaluated in the failure path of an assertion.
A lot of work has been put into generating pretty stack traces and formatting them as nicely as possible. Cpptrace is used as a portable and self-contained solution for stacktraces pre-C++23. Optional configurations can be found in the library's documentation.
One feature worth noting is that instead of always printing full paths, only the minimum number of directories needed to differentiate paths are printed.
Another feature worth pointing out is that the stack traces will fold traces with deep recursion:
The assertion handler applies syntax highlighting wherever appropriate, as seen in all the screenshots above. This is to help enhance readability.
Libassert can provide diff highlighting on output:
This is opt-in with libassert::set_diff_highlighting(true);
Libassert supports custom assertion failure handlers:
void handler(const assertion_info& info) {
throw std::runtime_error("Assertion failed:\n" + assertion.to_string());
}
int main() {
libassert::set_failure_handler(handler);
}
More details below.
A lot of care is given to producing debug stringifications of values as effectively as possible: Strings, characters, numbers, should all be printed as you'd expect. Additionally containers, tuples, std::optional, smart pointers, etc. are all stringified to show as much information as possible. If a user defined type overloads `operator struct libassert::stringifier { std::string stringify(const MyObject& type) { return ...; } };
## Smart literal formatting
Assertion values are printed in hex or binary as well as decimal if hex/binary are used on either
side of an assertion expression:
```cpp
ASSERT(get_mask() == 0b00001101);
Because expressions are already being automatically decomposed, you can opt into having signed-unsigned comparisons done
automatically done with sign safety with -DLIBASSERT_SAFE_COMPARISONS:
ASSERT(18446744073709551606ULL == -10);
…
cpp
void DEBUG_ASSERT (expression, [optional message], [optional extra diagnostics, ...]);
void ASSERT (expression, [optional message], [optional extra diagnostics, ...]);
void ASSUME (expression, [optional message], [optional extra diagnostics, ...]);
decltype(auto) DEBUG_ASSERT_VAL(expression, [optional message], [optional extra diagnostics, ...]);
decltype(auto) ASSERT_VAL (expression, [optional message], [optional extra diagnostics, ...]);
decltype(auto) ASSUME_VAL (expression, [optional message], [optional extra diagnostics, ...]);
void PANIC ([optional message], [optional extra diagnostics, ...]);
void UNREACHABLE([optional message], [optional extra diagnostics, ...]);
…
cpp
namespace libassert {
[[nodiscard]] std::string stacktrace(
int width = 0,
const color_scheme& scheme = get_color_scheme(),
std::size_t skip = 0
);
[[nodiscard]] std::string print_stacktrace(
const cpptrace::stacktrace& trace,
int width = 0,
const color_scheme& scheme = get_color_scheme(),
path_mode = get_path_mode()
);
template [[nodiscard]] std::string_view type_name() noexcept;
template [[nodiscard]] std::string pretty_type_name() noexcept;
template [[nodiscard]] std::string stringify(const T& value);
std::string highlight(
std::string_view expression,
const color_scheme& scheme = get_color_scheme()
);
template
[[nodiscard]] std::string highlight_stringify(
const T& value,
const color_scheme& scheme = get_color_scheme()
);
}
stacktrace: Generates a stack trace, formats to the given width (0 for no width formatting)print_stacktrace: Formats a provided stack trace with libassert's internal trace formattingtype_name: Returns the type name of Tpretty_type_name: Returns the prettified type name for Tstringify: Produces a debug stringification of a valuehighlight: Syntax-highlights a string using libassert's internal expression highlighterhighlight_stringify: Equivalent to libassert::highlight(libassert::stringify(value))namespace libassert {
void enable_virtual_terminal_processing_if_needed();
inline constexpr int stdin_fileno = 0;
inline constexpr int stdout_fileno = 1;
inline constexpr int stderr_fileno = 2;
bool isatty(int fd);
[[nodiscard]] int terminal_width(int fd);
}
enable_virtual_terminal_processing_if_needed: Enable ANSI escape sequences for terminals on windows, needed for
color output.isatty: Returns true if the file descriptor corresponds to a terminalterminal_width: Returns the width of the terminal represented by fd or 0 on errornamespace libassert {
// NOTE: string view underlying data should have static storage duration, or otherwise live as
// long as the scheme is in use
struct color_scheme {
std::string_view string, escape, keyword, named_literal, number, punctuation, operator_token,
call_identifier, scope_resolution_identifier, identifier, accent, unknown,
highlight_delete, highlight_insert, highlight_replace, reset;
static const color_scheme ansi_basic;
static const color_scheme ansi_rgb;
static const color_scheme blank;
};
void set_color_scheme(const color_scheme&);
const color_scheme& get_color_scheme();
}
By default color_scheme::ansi_rgb is used. To disable colors, use color_scheme::blank.
set_color_scheme: Sets the color scheme for the default assertion handler when stderr is a terminalDiff highlighting is opt-in with set_diff_highlighting:
namespace libassert {
void set_diff_highlighting(bool);
}
namespace libassert {
void set_separator(std::string_view separator);
}
set_separator: Sets the separator between expression and value in assertion diagnostic ouNo open issues yet, or sync has not completed.