Finally got all the ssh stuff working
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 24s
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 24s
This commit is contained in:
parent
14e43855b4
commit
ed93fa1aaa
@ -44,9 +44,28 @@ target_include_directories(dropshell PRIVATE
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/utils
|
${CMAKE_CURRENT_SOURCE_DIR}/src/utils
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/contrib
|
${CMAKE_CURRENT_SOURCE_DIR}/src/contrib
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/contrib/libassert
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/src>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
libassert
|
||||||
|
GIT_REPOSITORY https://github.com/jeremy-rifkin/libassert.git
|
||||||
|
GIT_TAG v2.1.5 # <HASH or TAG>
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(libassert)
|
||||||
|
target_link_libraries(dropshell libassert::assert)
|
||||||
|
|
||||||
|
# On windows copy libassert.dll to the same directory as the executable for your_target
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_command(
|
||||||
|
TARGET dropshell POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
$<TARGET_FILE:libassert::assert>
|
||||||
|
$<TARGET_FILE_DIR:dropshell>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Link libraries
|
# Link libraries
|
||||||
target_link_libraries(dropshell PRIVATE
|
target_link_libraries(dropshell PRIVATE
|
||||||
)
|
)
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
#include "templates.hpp"
|
#include "templates.hpp"
|
||||||
#include "services.hpp"
|
#include "services.hpp"
|
||||||
#include "servers.hpp"
|
#include "servers.hpp"
|
||||||
#include "utils/assert.hpp"
|
|
||||||
|
|
||||||
|
#include <libassert/assert.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
#ifndef LIBASSERT_CATCH2_HPP
|
|
||||||
#define LIBASSERT_CATCH2_HPP
|
|
||||||
|
|
||||||
#define LIBASSERT_PREFIX_ASSERTIONS
|
|
||||||
#include <libassert/assert.hpp>
|
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/catch_version_macros.hpp>
|
|
||||||
|
|
||||||
#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL != 0
|
|
||||||
#error "Libassert integration does not work with MSVC's non-conformant preprocessor. /Zc:preprocessor must be used."
|
|
||||||
#endif
|
|
||||||
// TODO: CHECK/REQUIRE?
|
|
||||||
#define ASSERT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { FAIL(e.what()); } } while(false)
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
// catch line wrapping can't handle ansi sequences before 3.6 https://github.com/catchorg/Catch2/issues/2833
|
|
||||||
inline constexpr bool use_color = CATCH_VERSION_MAJOR > 3 || (CATCH_VERSION_MAJOR == 3 && CATCH_VERSION_MINOR >= 6);
|
|
||||||
|
|
||||||
inline void catch2_failure_handler(const assertion_info& info) {
|
|
||||||
if(use_color) {
|
|
||||||
enable_virtual_terminal_processing_if_needed();
|
|
||||||
}
|
|
||||||
auto scheme = use_color ? color_scheme::ansi_rgb : color_scheme::blank;
|
|
||||||
std::string message = std::string(info.action()) + " at " + info.location() + ":";
|
|
||||||
if(info.message) {
|
|
||||||
message += " " + *info.message;
|
|
||||||
}
|
|
||||||
message += "\n";
|
|
||||||
message += info.statement(scheme)
|
|
||||||
+ info.print_binary_diagnostics(CATCH_CONFIG_CONSOLE_WIDTH, scheme)
|
|
||||||
+ info.print_extra_diagnostics(CATCH_CONFIG_CONSOLE_WIDTH, scheme);
|
|
||||||
throw std::runtime_error(std::move(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto pre_main = [] () {
|
|
||||||
set_failure_handler(catch2_failure_handler);
|
|
||||||
return 1;
|
|
||||||
} ();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some testing utilities
|
|
||||||
|
|
||||||
#define REQUIRE_ASSERT(expr) \
|
|
||||||
do { \
|
|
||||||
auto handler = ::libassert::get_failure_handler(); \
|
|
||||||
::libassert::set_failure_handler([] (const ::libassert::assertion_info& info) { \
|
|
||||||
throw info; \
|
|
||||||
}); \
|
|
||||||
bool did_assert = false; \
|
|
||||||
try { \
|
|
||||||
(expr); \
|
|
||||||
} catch(const ::libassert::assertion_info& info) { \
|
|
||||||
did_assert = true; \
|
|
||||||
SUCCEED(); \
|
|
||||||
} \
|
|
||||||
if(!did_assert) { \
|
|
||||||
FAIL("Expected assertion failure from " #expr " however none happened"); \
|
|
||||||
} \
|
|
||||||
::libassert::set_failure_handler(handler); \
|
|
||||||
} while(false)
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,37 +0,0 @@
|
|||||||
#ifndef LIBASSERT_GTEST_HPP
|
|
||||||
#define LIBASSERT_GTEST_HPP
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#define LIBASSERT_PREFIX_ASSERTIONS
|
|
||||||
#include <libassert/assert.hpp>
|
|
||||||
|
|
||||||
#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL != 0
|
|
||||||
#error "Libassert integration does not work with MSVC's non-conformant preprocessor. /Zc:preprocessor must be used."
|
|
||||||
#endif
|
|
||||||
#define ASSERT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { FAIL() << e.what(); } } while(false)
|
|
||||||
#define EXPECT(...) do { try { LIBASSERT_ASSERT(__VA_ARGS__); SUCCEED(); } catch(std::exception& e) { ADD_FAILURE() << e.what(); } } while(false)
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
inline void gtest_failure_handler(const assertion_info& info) {
|
|
||||||
enable_virtual_terminal_processing_if_needed(); // for terminal colors on windows
|
|
||||||
auto width = terminal_width(stderr_fileno);
|
|
||||||
const auto& scheme = isatty(stderr_fileno) ? get_color_scheme() : color_scheme::blank;
|
|
||||||
std::string message = std::string(info.action()) + " at " + info.location() + ":";
|
|
||||||
if(info.message) {
|
|
||||||
message += " " + *info.message;
|
|
||||||
}
|
|
||||||
message += "\n";
|
|
||||||
message += info.statement()
|
|
||||||
+ info.print_binary_diagnostics(width, scheme)
|
|
||||||
+ info.print_extra_diagnostics(width, scheme);
|
|
||||||
throw std::runtime_error(std::move(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto pre_main = [] () {
|
|
||||||
set_failure_handler(gtest_failure_handler);
|
|
||||||
return 1;
|
|
||||||
} ();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,997 +0,0 @@
|
|||||||
#ifndef LIBASSERT_HPP
|
|
||||||
#define LIBASSERT_HPP
|
|
||||||
|
|
||||||
// Copyright (c) 2021-2024 Jeremy Rifkin under the MIT license
|
|
||||||
// https://github.com/jeremy-rifkin/libassert
|
|
||||||
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <new>
|
|
||||||
#include <optional>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string_view>
|
|
||||||
#include <string>
|
|
||||||
#include <system_error>
|
|
||||||
#include <tuple>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <libassert/platform.hpp>
|
|
||||||
#include <libassert/utilities.hpp>
|
|
||||||
#include <libassert/stringification.hpp>
|
|
||||||
#include <libassert/expression-decomposition.hpp>
|
|
||||||
|
|
||||||
#if defined(__has_include) && __has_include(<cpptrace/basic.hpp>)
|
|
||||||
#include <cpptrace/basic.hpp>
|
|
||||||
#else
|
|
||||||
#include <cpptrace/cpptrace.hpp>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cpp_lib_expected
|
|
||||||
#include <expected>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_MSVC
|
|
||||||
#pragma warning(push)
|
|
||||||
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for
|
|
||||||
// some reason
|
|
||||||
// 4275 is the same thing but for base classes
|
|
||||||
#pragma warning(disable: 4251; disable: 4275)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Libassert public interface ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert {
|
|
||||||
// returns the width of the terminal represented by fd, will be 0 on error
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT int terminal_width(int fd);
|
|
||||||
|
|
||||||
// Enable virtual terminal processing on windows terminals
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_EXPORT 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;
|
|
||||||
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_EXPORT bool isatty(int fd);
|
|
||||||
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_EXPORT bool is_debugger_present() noexcept;
|
|
||||||
enum class debugger_check_mode {
|
|
||||||
check_once,
|
|
||||||
check_every_time,
|
|
||||||
};
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_EXPORT void set_debugger_check_mode(debugger_check_mode mode) noexcept;
|
|
||||||
|
|
||||||
// returns the type name of T
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::string_view type_name() noexcept {
|
|
||||||
return detail::type_name<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the prettified type name for T
|
|
||||||
template<typename T> // TODO: Use this above....
|
|
||||||
[[nodiscard]] std::string pretty_type_name() noexcept {
|
|
||||||
return detail::prettify_type(std::string(detail::type_name<T>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a debug stringification of t
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::string stringify(const T& t) {
|
|
||||||
return detail::generate_stringification(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
std::string_view escape;
|
|
||||||
std::string_view keyword;
|
|
||||||
std::string_view named_literal;
|
|
||||||
std::string_view number;
|
|
||||||
std::string_view punctuation;
|
|
||||||
std::string_view operator_token;
|
|
||||||
std::string_view call_identifier;
|
|
||||||
std::string_view scope_resolution_identifier;
|
|
||||||
std::string_view identifier;
|
|
||||||
std::string_view accent;
|
|
||||||
std::string_view unknown;
|
|
||||||
std::string_view reset;
|
|
||||||
LIBASSERT_EXPORT const static color_scheme ansi_basic;
|
|
||||||
LIBASSERT_EXPORT const static color_scheme ansi_rgb;
|
|
||||||
LIBASSERT_EXPORT const static color_scheme blank;
|
|
||||||
};
|
|
||||||
|
|
||||||
LIBASSERT_EXPORT void set_color_scheme(const color_scheme&);
|
|
||||||
LIBASSERT_EXPORT const color_scheme& get_color_scheme();
|
|
||||||
|
|
||||||
// set separator used for diagnostics, by default it is "=>"
|
|
||||||
// note: not thread-safe
|
|
||||||
LIBASSERT_EXPORT void set_separator(std::string_view separator);
|
|
||||||
|
|
||||||
std::string highlight(std::string_view expression, const color_scheme& scheme = get_color_scheme());
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::string highlight_stringify(const T& t, const color_scheme& scheme = get_color_scheme()) {
|
|
||||||
return highlight(stringify(t), scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generates a stack trace, formats to the given width
|
|
||||||
[[nodiscard]] LIBASSERT_ATTR_NOINLINE LIBASSERT_EXPORT
|
|
||||||
std::string stacktrace(int width = 0, const color_scheme& scheme = get_color_scheme(), std::size_t skip = 0);
|
|
||||||
|
|
||||||
enum class literal_format : unsigned {
|
|
||||||
// integers and floats are decimal by default, chars are of course chars, and everything else only has one
|
|
||||||
// format that makes sense
|
|
||||||
default_format = 0,
|
|
||||||
integer_hex = 1,
|
|
||||||
integer_octal = 2,
|
|
||||||
integer_binary = 4,
|
|
||||||
integer_character = 8, // format integers as characters and characters as integers
|
|
||||||
float_hex = 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
[[nodiscard]] constexpr literal_format operator|(literal_format a, literal_format b) {
|
|
||||||
return static_cast<literal_format>(
|
|
||||||
static_cast<std::underlying_type<literal_format>::type>(a) |
|
|
||||||
static_cast<std::underlying_type<literal_format>::type>(b)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class literal_format_mode {
|
|
||||||
infer, // infer literal formats based on the assertion condition
|
|
||||||
no_variations, // don't do any literal format variations, just default
|
|
||||||
fixed_variations // use a fixed set of formats always; note the default format will always be used
|
|
||||||
};
|
|
||||||
|
|
||||||
// NOTE: Should not be called during handling of an assertion in the current thread
|
|
||||||
LIBASSERT_EXPORT void set_literal_format_mode(literal_format_mode);
|
|
||||||
|
|
||||||
// NOTE: Should not be called during handling of an assertion in the current thread
|
|
||||||
// set a fixed literal format configuration, automatically changes the literal_format_mode; note that the default
|
|
||||||
// format will always be used along with others
|
|
||||||
LIBASSERT_EXPORT void set_fixed_literal_format(literal_format);
|
|
||||||
|
|
||||||
enum class path_mode {
|
|
||||||
// full path is used
|
|
||||||
full,
|
|
||||||
// only enough folders needed to disambiguate are provided
|
|
||||||
disambiguated, // TODO: Maybe just a bad idea...?
|
|
||||||
// only the file name is used
|
|
||||||
basename,
|
|
||||||
};
|
|
||||||
LIBASSERT_EXPORT void set_path_mode(path_mode mode);
|
|
||||||
|
|
||||||
enum class assert_type {
|
|
||||||
debug_assertion,
|
|
||||||
assertion,
|
|
||||||
assumption,
|
|
||||||
panic,
|
|
||||||
unreachable
|
|
||||||
};
|
|
||||||
|
|
||||||
struct assertion_info;
|
|
||||||
|
|
||||||
[[noreturn]] LIBASSERT_EXPORT void default_failure_handler(const assertion_info& info);
|
|
||||||
|
|
||||||
using handler_ptr = void(*)(const assertion_info&);
|
|
||||||
LIBASSERT_EXPORT handler_ptr get_failure_handler();
|
|
||||||
LIBASSERT_EXPORT void set_failure_handler(handler_ptr handler);
|
|
||||||
|
|
||||||
struct LIBASSERT_EXPORT binary_diagnostics_descriptor {
|
|
||||||
std::string left_expression;
|
|
||||||
std::string right_expression;
|
|
||||||
std::string left_stringification;
|
|
||||||
std::string right_stringification;
|
|
||||||
bool multiple_formats;
|
|
||||||
binary_diagnostics_descriptor(); // = default; in the .cpp
|
|
||||||
binary_diagnostics_descriptor(
|
|
||||||
std::string_view left_expression,
|
|
||||||
std::string_view right_expression,
|
|
||||||
std::string&& left_stringification,
|
|
||||||
std::string&& right_stringification,
|
|
||||||
bool multiple_formats
|
|
||||||
);
|
|
||||||
~binary_diagnostics_descriptor(); // = default; in the .cpp
|
|
||||||
binary_diagnostics_descriptor(const binary_diagnostics_descriptor&);
|
|
||||||
binary_diagnostics_descriptor(binary_diagnostics_descriptor&&) noexcept;
|
|
||||||
binary_diagnostics_descriptor& operator=(const binary_diagnostics_descriptor&);
|
|
||||||
binary_diagnostics_descriptor& operator=(binary_diagnostics_descriptor&&) noexcept(LIBASSERT_GCC_ISNT_STUPID);
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
struct sv_span {
|
|
||||||
const std::string_view* data;
|
|
||||||
std::size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
// collection of assertion data that can be put in static storage and all passed by a single pointer
|
|
||||||
struct LIBASSERT_EXPORT assert_static_parameters {
|
|
||||||
std::string_view macro_name;
|
|
||||||
assert_type type;
|
|
||||||
std::string_view expr_str;
|
|
||||||
source_location location;
|
|
||||||
sv_span args_strings;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct extra_diagnostic {
|
|
||||||
std::string_view expression;
|
|
||||||
std::string stringification;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace detail {
|
|
||||||
class path_handler {
|
|
||||||
public:
|
|
||||||
virtual ~path_handler() = default;
|
|
||||||
virtual std::unique_ptr<detail::path_handler> clone() const = 0;
|
|
||||||
virtual std::string_view resolve_path(std::string_view) = 0;
|
|
||||||
virtual bool has_add_path() const;
|
|
||||||
virtual void add_path(std::string_view);
|
|
||||||
virtual void finalize();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LIBASSERT_EXPORT assertion_info {
|
|
||||||
std::string_view macro_name;
|
|
||||||
assert_type type;
|
|
||||||
std::string_view expression_string;
|
|
||||||
std::string_view file_name;
|
|
||||||
std::uint32_t line;
|
|
||||||
std::string_view function;
|
|
||||||
std::optional<std::string> message;
|
|
||||||
std::optional<binary_diagnostics_descriptor> binary_diagnostics;
|
|
||||||
std::vector<extra_diagnostic> extra_diagnostics;
|
|
||||||
size_t n_args;
|
|
||||||
private:
|
|
||||||
mutable std::variant<cpptrace::raw_trace, cpptrace::stacktrace> trace; // lazy, resolved when needed
|
|
||||||
mutable std::unique_ptr<detail::path_handler> path_handler;
|
|
||||||
detail::path_handler* get_path_handler() const; // will get and setup the path handler
|
|
||||||
public:
|
|
||||||
assertion_info() = delete;
|
|
||||||
assertion_info(
|
|
||||||
const detail::assert_static_parameters* static_params,
|
|
||||||
cpptrace::raw_trace&& raw_trace,
|
|
||||||
size_t n_args
|
|
||||||
);
|
|
||||||
~assertion_info();
|
|
||||||
assertion_info(const assertion_info&);
|
|
||||||
assertion_info(assertion_info&&);
|
|
||||||
assertion_info& operator=(const assertion_info&);
|
|
||||||
assertion_info& operator=(assertion_info&&);
|
|
||||||
|
|
||||||
std::string_view action() const;
|
|
||||||
|
|
||||||
const cpptrace::raw_trace& get_raw_trace() const;
|
|
||||||
const cpptrace::stacktrace& get_stacktrace() const;
|
|
||||||
|
|
||||||
[[nodiscard]] std::string header(int width = 0, const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
[[nodiscard]] std::string tagline(const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
[[nodiscard]] std::string location() const;
|
|
||||||
[[nodiscard]] std::string statement(const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
[[nodiscard]] std::string print_binary_diagnostics(int width = 0, const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
[[nodiscard]] std::string print_extra_diagnostics(int width = 0, const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
[[nodiscard]] std::string print_stacktrace(int width = 0, const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
|
|
||||||
[[nodiscard]] std::string to_string(int width = 0, const color_scheme& scheme = get_color_scheme()) const;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Library core ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
/*
|
|
||||||
* C++ syntax analysis and literal formatting
|
|
||||||
*/
|
|
||||||
|
|
||||||
// get current literal_format configuration for the thread
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT literal_format get_thread_current_literal_format();
|
|
||||||
|
|
||||||
// sets the current literal_format configuration for the thread
|
|
||||||
LIBASSERT_EXPORT void set_thread_current_literal_format(literal_format format);
|
|
||||||
|
|
||||||
LIBASSERT_EXPORT literal_format set_literal_format(
|
|
||||||
std::string_view left_expression,
|
|
||||||
std::string_view right_expression,
|
|
||||||
std::string_view op,
|
|
||||||
bool integer_character
|
|
||||||
);
|
|
||||||
LIBASSERT_EXPORT void restore_literal_format(literal_format);
|
|
||||||
// does the current literal format config have multiple formats
|
|
||||||
LIBASSERT_EXPORT bool has_multiple_formats();
|
|
||||||
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::pair<std::string, std::string> decompose_expression(
|
|
||||||
std::string_view expression,
|
|
||||||
std::string_view target_op
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* System wrappers
|
|
||||||
*/
|
|
||||||
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string strerror_wrapper(int err); // stupid C stuff, stupid microsoft stuff
|
|
||||||
|
|
||||||
/*
|
|
||||||
* assert diagnostics generation
|
|
||||||
*/
|
|
||||||
|
|
||||||
template<typename A, typename B>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
binary_diagnostics_descriptor generate_binary_diagnostic(
|
|
||||||
const A& left,
|
|
||||||
const B& right,
|
|
||||||
std::string_view left_str,
|
|
||||||
std::string_view right_str,
|
|
||||||
std::string_view op
|
|
||||||
) {
|
|
||||||
constexpr bool either_is_character = isa<A, char> || isa<B, char>;
|
|
||||||
constexpr bool either_is_arithmetic = is_arith_not_bool_char<A> || is_arith_not_bool_char<B>;
|
|
||||||
literal_format previous_format = set_literal_format(
|
|
||||||
left_str,
|
|
||||||
right_str,
|
|
||||||
op,
|
|
||||||
either_is_character && either_is_arithmetic
|
|
||||||
);
|
|
||||||
binary_diagnostics_descriptor descriptor(
|
|
||||||
left_str,
|
|
||||||
right_str,
|
|
||||||
generate_stringification(left),
|
|
||||||
generate_stringification(right),
|
|
||||||
has_multiple_formats()
|
|
||||||
);
|
|
||||||
restore_literal_format(previous_format);
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LIBASSERT_X(x) #x
|
|
||||||
#define LIBASSERT_Y(x) LIBASSERT_X(x)
|
|
||||||
constexpr const std::string_view errno_expansion = LIBASSERT_Y(errno);
|
|
||||||
#undef LIBASSERT_Y
|
|
||||||
#undef LIBASSERT_X
|
|
||||||
|
|
||||||
struct pretty_function_name_wrapper {
|
|
||||||
const char* pretty_function;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline void process_arg( // TODO: Don't inline
|
|
||||||
assertion_info& info,
|
|
||||||
size_t,
|
|
||||||
sv_span,
|
|
||||||
const pretty_function_name_wrapper& t
|
|
||||||
) {
|
|
||||||
info.function = t.pretty_function;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD
|
|
||||||
// TODO
|
|
||||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
|
||||||
void process_arg(assertion_info& info, size_t i, sv_span args_strings, const T& t) {
|
|
||||||
if constexpr(isa<T, strip<decltype(errno)>>) {
|
|
||||||
if(args_strings.data[i] == errno_expansion) {
|
|
||||||
info.extra_diagnostics.push_back({ "errno", bstringf("%2d \"%s\"", t, strerror_wrapper(t).c_str()) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if constexpr(is_string_type<T>) {
|
|
||||||
if(i == 0) {
|
|
||||||
if constexpr(std::is_pointer_v<T>) {
|
|
||||||
if(t == nullptr) {
|
|
||||||
info.message = "(nullptr)";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.message = t;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info.extra_diagnostics.push_back({ args_strings.data[i], generate_stringification(t) });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
LIBASSERT_ATTR_COLD
|
|
||||||
void process_args(assertion_info& info, sv_span args_strings, Args&... args) {
|
|
||||||
size_t i = 0;
|
|
||||||
(process_arg(info, i++, args_strings, args), ...);
|
|
||||||
(void)args_strings;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Actual top-level assertion processing
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
LIBASSERT_EXPORT void fail(const assertion_info& info);
|
|
||||||
|
|
||||||
template<typename A, typename B, typename C, typename... Args>
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_ATTR_NOINLINE
|
|
||||||
// TODO: Re-evaluate forwarding here.
|
|
||||||
void process_assert_fail(
|
|
||||||
expression_decomposer<A, B, C>& decomposer,
|
|
||||||
const assert_static_parameters* params,
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
|
|
||||||
Args&&... args
|
|
||||||
) {
|
|
||||||
const size_t sizeof_extra_diagnostics = sizeof...(args) - 1; // - 1 for pretty function signature
|
|
||||||
LIBASSERT_PRIMITIVE_DEBUG_ASSERT(sizeof...(args) <= params->args_strings.size);
|
|
||||||
assertion_info info(
|
|
||||||
params,
|
|
||||||
cpptrace::generate_raw_trace(),
|
|
||||||
sizeof_extra_diagnostics
|
|
||||||
);
|
|
||||||
// process_args fills in the message, extra_diagnostics, and pretty_function
|
|
||||||
process_args(info, params->args_strings, args...);
|
|
||||||
// generate binary diagnostics
|
|
||||||
if constexpr(is_nothing<C>) {
|
|
||||||
static_assert(is_nothing<B> && !is_nothing<A>);
|
|
||||||
if constexpr(isa<A, bool>) {
|
|
||||||
(void)decomposer; // suppress warning in msvc
|
|
||||||
} else {
|
|
||||||
info.binary_diagnostics = generate_binary_diagnostic(
|
|
||||||
decomposer.a,
|
|
||||||
true,
|
|
||||||
params->expr_str,
|
|
||||||
"true",
|
|
||||||
"=="
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
auto [left_expression, right_expression] = decompose_expression(params->expr_str, C::op_string);
|
|
||||||
info.binary_diagnostics = generate_binary_diagnostic(
|
|
||||||
decomposer.a,
|
|
||||||
decomposer.b,
|
|
||||||
left_expression,
|
|
||||||
right_expression,
|
|
||||||
C::op_string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// send off
|
|
||||||
fail(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
LIBASSERT_ATTR_COLD [[noreturn]] LIBASSERT_ATTR_NOINLINE
|
|
||||||
// TODO: Re-evaluate forwarding here.
|
|
||||||
void process_panic(
|
|
||||||
const assert_static_parameters* params,
|
|
||||||
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
|
|
||||||
Args&&... args
|
|
||||||
) {
|
|
||||||
const size_t sizeof_extra_diagnostics = sizeof...(args) - 1; // - 1 for pretty function signature
|
|
||||||
LIBASSERT_PRIMITIVE_DEBUG_ASSERT(sizeof...(args) <= params->args_strings.size);
|
|
||||||
assertion_info info(
|
|
||||||
params,
|
|
||||||
cpptrace::generate_raw_trace(),
|
|
||||||
sizeof_extra_diagnostics
|
|
||||||
);
|
|
||||||
// process_args fills in the message, extra_diagnostics, and pretty_function
|
|
||||||
process_args(info, params->args_strings, args...);
|
|
||||||
// send off
|
|
||||||
fail(info);
|
|
||||||
LIBASSERT_PRIMITIVE_PANIC("PANIC/UNREACHABLE failure handler returned");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Re-evaluate benefit of this at all in non-cold path code
|
|
||||||
template<typename A, typename B, typename C, typename... Args>
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_ATTR_NOINLINE [[nodiscard]]
|
|
||||||
expression_decomposer<A, B, C> process_assert_fail_m(
|
|
||||||
expression_decomposer<A, B, C> decomposer,
|
|
||||||
const assert_static_parameters* params,
|
|
||||||
Args&&... args
|
|
||||||
) {
|
|
||||||
process_assert_fail(decomposer, params, std::forward<Args>(args)...);
|
|
||||||
return decomposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename A, typename B, typename C, typename... Args>
|
|
||||||
LIBASSERT_ATTR_COLD LIBASSERT_ATTR_NOINLINE
|
|
||||||
void process_assert_fail_n(
|
|
||||||
expression_decomposer<A, B, C> decomposer,
|
|
||||||
const assert_static_parameters* params,
|
|
||||||
Args&&... args
|
|
||||||
) {
|
|
||||||
process_assert_fail(decomposer, params, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct assert_value_wrapper {
|
|
||||||
T value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<
|
|
||||||
bool R, bool ret_lhs, bool value_is_lval_ref,
|
|
||||||
typename T, typename A, typename B, typename C
|
|
||||||
>
|
|
||||||
constexpr auto get_expression_return_value(T& value, expression_decomposer<A, B, C>& decomposer) {
|
|
||||||
if constexpr(R) {
|
|
||||||
if constexpr(ret_lhs) {
|
|
||||||
if constexpr(std::is_lvalue_reference_v<A>) {
|
|
||||||
return assert_value_wrapper<A>{decomposer.take_lhs()};
|
|
||||||
} else {
|
|
||||||
return assert_value_wrapper<A>{std::move(decomposer.take_lhs())};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if constexpr(value_is_lval_ref) {
|
|
||||||
return assert_value_wrapper<T&>{value};
|
|
||||||
} else {
|
|
||||||
return assert_value_wrapper<T>{std::move(value)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_MSVC
|
|
||||||
#pragma warning(pop)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC || !LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR
|
|
||||||
// Macro mapping utility by William Swanson https://github.com/swansontec/map-macro/blob/master/map.h
|
|
||||||
#define LIBASSERT_EVAL0(...) __VA_ARGS__
|
|
||||||
#define LIBASSERT_EVAL1(...) LIBASSERT_EVAL0(LIBASSERT_EVAL0(LIBASSERT_EVAL0(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL2(...) LIBASSERT_EVAL1(LIBASSERT_EVAL1(LIBASSERT_EVAL1(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL3(...) LIBASSERT_EVAL2(LIBASSERT_EVAL2(LIBASSERT_EVAL2(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL4(...) LIBASSERT_EVAL3(LIBASSERT_EVAL3(LIBASSERT_EVAL3(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL(...) LIBASSERT_EVAL4(LIBASSERT_EVAL4(LIBASSERT_EVAL4(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_MAP_END(...)
|
|
||||||
#define LIBASSERT_MAP_OUT
|
|
||||||
#define LIBASSERT_MAP_COMMA ,
|
|
||||||
#define LIBASSERT_MAP_GET_END2() 0, LIBASSERT_MAP_END
|
|
||||||
#define LIBASSERT_MAP_GET_END1(...) LIBASSERT_MAP_GET_END2
|
|
||||||
#define LIBASSERT_MAP_GET_END(...) LIBASSERT_MAP_GET_END1
|
|
||||||
#define LIBASSERT_MAP_NEXT0(test, next, ...) next LIBASSERT_MAP_OUT
|
|
||||||
#define LIBASSERT_MAP_NEXT1(test, next) LIBASSERT_MAP_NEXT0(test, next, 0)
|
|
||||||
#define LIBASSERT_MAP_NEXT(test, next) LIBASSERT_MAP_NEXT1(LIBASSERT_MAP_GET_END test, next)
|
|
||||||
#define LIBASSERT_MAP0(f, x, peek, ...) f(x) LIBASSERT_MAP_NEXT(peek, LIBASSERT_MAP1)(f, peek, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP1(f, x, peek, ...) f(x) LIBASSERT_MAP_NEXT(peek, LIBASSERT_MAP0)(f, peek, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP_LIST_NEXT1(test, next) LIBASSERT_MAP_NEXT0(test, LIBASSERT_MAP_COMMA next, 0)
|
|
||||||
#define LIBASSERT_MAP_LIST_NEXT(test, next) LIBASSERT_MAP_LIST_NEXT1(LIBASSERT_MAP_GET_END test, next)
|
|
||||||
#define LIBASSERT_MAP_LIST0(f, x, peek, ...) \
|
|
||||||
f(x) LIBASSERT_MAP_LIST_NEXT(peek, LIBASSERT_MAP_LIST1)(f, peek, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP_LIST1(f, x, peek, ...) \
|
|
||||||
f(x) LIBASSERT_MAP_LIST_NEXT(peek, LIBASSERT_MAP_LIST0)(f, peek, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP(f, ...) LIBASSERT_EVAL(LIBASSERT_MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
|
|
||||||
#else
|
|
||||||
// https://stackoverflow.com/a/29474124/15675011
|
|
||||||
#define LIBASSERT_PLUS_TEXT_(x,y) x ## y
|
|
||||||
#define LIBASSERT_PLUS_TEXT(x, y) LIBASSERT_PLUS_TEXT_(x, y)
|
|
||||||
#define LIBASSERT_ARG_1(_1, ...) _1
|
|
||||||
#define LIBASSERT_ARG_2(_1, _2, ...) _2
|
|
||||||
#define LIBASSERT_ARG_3(_1, _2, _3, ...) _3
|
|
||||||
#define LIBASSERT_ARG_40( _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, \
|
|
||||||
_10, _11, _12, _13, _14, _15, _16, _17, _18, _19, \
|
|
||||||
_20, _21, _22, _23, _24, _25, _26, _27, _28, _29, \
|
|
||||||
_30, _31, _32, _33, _34, _35, _36, _37, _38, _39, \
|
|
||||||
...) _39
|
|
||||||
#define LIBASSERT_OTHER_1(_1, ...) __VA_ARGS__
|
|
||||||
#define LIBASSERT_OTHER_3(_1, _2, _3, ...) __VA_ARGS__
|
|
||||||
#define LIBASSERT_EVAL0(...) __VA_ARGS__
|
|
||||||
#define LIBASSERT_EVAL1(...) LIBASSERT_EVAL0(LIBASSERT_EVAL0(LIBASSERT_EVAL0(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL2(...) LIBASSERT_EVAL1(LIBASSERT_EVAL1(LIBASSERT_EVAL1(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL3(...) LIBASSERT_EVAL2(LIBASSERT_EVAL2(LIBASSERT_EVAL2(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL4(...) LIBASSERT_EVAL3(LIBASSERT_EVAL3(LIBASSERT_EVAL3(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EVAL(...) LIBASSERT_EVAL4(LIBASSERT_EVAL4(LIBASSERT_EVAL4(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_EXPAND(x) x
|
|
||||||
#define LIBASSERT_MAP_SWITCH(...)\
|
|
||||||
LIBASSERT_EXPAND(LIBASSERT_ARG_40(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2,\
|
|
||||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,\
|
|
||||||
2, 2, 2, 2, 2, 2, 2, 2, 2,\
|
|
||||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0))
|
|
||||||
#define LIBASSERT_MAP_A(...) LIBASSERT_PLUS_TEXT(LIBASSERT_MAP_NEXT_, \
|
|
||||||
LIBASSERT_MAP_SWITCH(0, __VA_ARGS__)) (LIBASSERT_MAP_B, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP_B(...) LIBASSERT_PLUS_TEXT(LIBASSERT_MAP_NEXT_, \
|
|
||||||
LIBASSERT_MAP_SWITCH(0, __VA_ARGS__)) (LIBASSERT_MAP_A, __VA_ARGS__)
|
|
||||||
#define LIBASSERT_MAP_CALL(fn, Value) LIBASSERT_EXPAND(fn(Value))
|
|
||||||
#define LIBASSERT_MAP_OUT
|
|
||||||
#define LIBASSERT_MAP_NEXT_2(...)\
|
|
||||||
LIBASSERT_MAP_CALL(LIBASSERT_EXPAND(LIBASSERT_ARG_2(__VA_ARGS__)), \
|
|
||||||
LIBASSERT_EXPAND(LIBASSERT_ARG_3(__VA_ARGS__))) \
|
|
||||||
LIBASSERT_EXPAND(LIBASSERT_ARG_1(__VA_ARGS__)) \
|
|
||||||
LIBASSERT_MAP_OUT \
|
|
||||||
(LIBASSERT_EXPAND(LIBASSERT_ARG_2(__VA_ARGS__)), LIBASSERT_EXPAND(LIBASSERT_OTHER_3(__VA_ARGS__)))
|
|
||||||
#define LIBASSERT_MAP_NEXT_0(...)
|
|
||||||
#define LIBASSERT_MAP(...) LIBASSERT_EVAL(LIBASSERT_MAP_A(__VA_ARGS__))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LIBASSERT_STRINGIFY(x) #x,
|
|
||||||
#define LIBASSERT_COMMA ,
|
|
||||||
|
|
||||||
// Church boolean
|
|
||||||
#define LIBASSERT_IF(b) LIBASSERT_IF_##b
|
|
||||||
#define LIBASSERT_IF_true(t,...) t
|
|
||||||
#define LIBASSERT_IF_false(t,f,...) f
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_GCC \
|
|
||||||
_Pragma("GCC diagnostic ignored \"-Wparentheses\"") \
|
|
||||||
_Pragma("GCC diagnostic ignored \"-Wuseless-cast\"") // #49
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC _Pragma("GCC diagnostic push")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC _Pragma("GCC diagnostic pop")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_CLANG \
|
|
||||||
_Pragma("GCC diagnostic ignored \"-Wparentheses\"") \
|
|
||||||
_Pragma("GCC diagnostic ignored \"-Woverloaded-shift-op-parentheses\"")
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG _Pragma("GCC diagnostic push")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG _Pragma("GCC diagnostic pop")
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_GCC
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_CLANG
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace libassert {
|
|
||||||
inline void ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT() {
|
|
||||||
// This non-constexpr method is called from an assertion in a constexpr context if a failure occurs. It is
|
|
||||||
// intentionally a no-op.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// __PRETTY_FUNCTION__ used because __builtin_FUNCTION() used in source_location (like __FUNCTION__) is just the method
|
|
||||||
// name, not signature
|
|
||||||
// The arg strings at the very least must be static constexpr. Unfortunately static constexpr variables are not allowed
|
|
||||||
// in constexpr functions pre-C++23.
|
|
||||||
// TODO: Try to do a hybrid in C++20 with std::is_constant_evaluated?
|
|
||||||
#if defined(__cpp_constexpr) && __cpp_constexpr >= 202211L
|
|
||||||
// Can just use static constexpr everywhere
|
|
||||||
#define LIBASSERT_STATIC_DATA(name, type, expr_str, ...) \
|
|
||||||
/* extra string here because of extra comma from map, also serves as terminator */ \
|
|
||||||
/* LIBASSERT_STRINGIFY LIBASSERT_VA_ARGS because msvc */ \
|
|
||||||
/* Trailing return type here to work around a gcc <= 9.2 bug */ \
|
|
||||||
/* Oddly only affecting builds under -DNDEBUG https://godbolt.org/z/5Treozc4q */ \
|
|
||||||
using libassert_params_t = libassert::detail::assert_static_parameters; \
|
|
||||||
/* NOLINTNEXTLINE(*-avoid-c-arrays) */ \
|
|
||||||
static constexpr std::string_view libassert_arg_strings[] = { \
|
|
||||||
LIBASSERT_MAP(LIBASSERT_STRINGIFY LIBASSERT_VA_ARGS(__VA_ARGS__)) "" \
|
|
||||||
}; \
|
|
||||||
static constexpr libassert_params_t _libassert_params = { \
|
|
||||||
name LIBASSERT_COMMA \
|
|
||||||
type LIBASSERT_COMMA \
|
|
||||||
expr_str LIBASSERT_COMMA \
|
|
||||||
{} LIBASSERT_COMMA \
|
|
||||||
{libassert_arg_strings, sizeof(libassert_arg_strings) / sizeof(std::string_view)} LIBASSERT_COMMA \
|
|
||||||
}; \
|
|
||||||
const libassert_params_t* libassert_params = &_libassert_params;
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_STATIC_DATA(name, type, expr_str, ...) \
|
|
||||||
using libassert_params_t = libassert::detail::assert_static_parameters; \
|
|
||||||
/* NOLINTNEXTLINE(*-avoid-c-arrays) */ \
|
|
||||||
const libassert_params_t* libassert_params = []() -> const libassert_params_t* { \
|
|
||||||
static constexpr std::string_view libassert_arg_strings[] = { \
|
|
||||||
LIBASSERT_MAP(LIBASSERT_STRINGIFY LIBASSERT_VA_ARGS(__VA_ARGS__)) "" \
|
|
||||||
}; \
|
|
||||||
static constexpr libassert_params_t _libassert_params = { \
|
|
||||||
name LIBASSERT_COMMA \
|
|
||||||
type LIBASSERT_COMMA \
|
|
||||||
expr_str LIBASSERT_COMMA \
|
|
||||||
{} LIBASSERT_COMMA \
|
|
||||||
{libassert_arg_strings, sizeof(libassert_arg_strings) / sizeof(std::string_view)} LIBASSERT_COMMA \
|
|
||||||
}; \
|
|
||||||
return &_libassert_params; \
|
|
||||||
}();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Note about statement expressions: These are needed for two reasons. The first is putting the arg string array and
|
|
||||||
// source location structure in .rodata rather than on the stack, the second is a _Pragma for warnings which isn't
|
|
||||||
// allowed in the middle of an expression by GCC. The semantics are similar to a function return:
|
|
||||||
// Given M m; in parent scope, ({ m; }) is an rvalue M&& rather than an lvalue
|
|
||||||
// ({ M m; m; }) doesn't move, it copies
|
|
||||||
// ({ M{}; }) does move
|
|
||||||
// Of relevance to this: in foo(__extension__ ({ M{1} + M{1}; })); the lifetimes of the M{1} objects end during the
|
|
||||||
// statement expression but the lifetime of the returned object is extend to the end of the full foo() expression.
|
|
||||||
// A wrapper struct is used here to return an lvalue reference from a gcc statement expression.
|
|
||||||
// Note: There is a current issue with tarnaries: auto x = assert(b ? y : y); must copy y. This can be fixed with
|
|
||||||
// lambdas but that's potentially very expensive compile-time wise. Need to investigate further.
|
|
||||||
// Note: libassert::detail::expression_decomposer(libassert::detail::expression_decomposer{} << expr) done for ternary
|
|
||||||
#if LIBASSERT_IS_MSVC
|
|
||||||
#define LIBASSERT_INVOKE_VAL_PRETTY_FUNCTION_ARG ,libassert::detail::pretty_function_name_wrapper{libassert_msvc_pfunc}
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_INVOKE_VAL_PRETTY_FUNCTION_ARG ,libassert::detail::pretty_function_name_wrapper{LIBASSERT_PFUNC}
|
|
||||||
#endif
|
|
||||||
#define LIBASSERT_PRETTY_FUNCTION_ARG ,libassert::detail::pretty_function_name_wrapper{LIBASSERT_PFUNC}
|
|
||||||
#if LIBASSERT_IS_CLANG // -Wall in clang
|
|
||||||
#define LIBASSERT_IGNORE_UNUSED_VALUE _Pragma("GCC diagnostic ignored \"-Wunused-value\"")
|
|
||||||
#else
|
|
||||||
#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_ON_FAIL() LIBASSERT_BREAKPOINT_IF_DEBUGGING()
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LIBASSERT_INVOKE(expr, name, type, failaction, ...) \
|
|
||||||
/* must push/pop out here due to nasty clang bug https://github.com/llvm/llvm-project/issues/63897 */ \
|
|
||||||
/* must do awful stuff to workaround differences in where gcc and clang allow these directives to go */ \
|
|
||||||
do { \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_PUSH_CLANG \
|
|
||||||
LIBASSERT_IGNORE_UNUSED_VALUE \
|
|
||||||
LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_CLANG \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_PUSH_GCC \
|
|
||||||
LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_GCC \
|
|
||||||
auto libassert_decomposer = libassert::detail::expression_decomposer( \
|
|
||||||
libassert::detail::expression_decomposer{} << expr \
|
|
||||||
); \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_POP_GCC \
|
|
||||||
if(LIBASSERT_STRONG_EXPECT(!static_cast<bool>(libassert_decomposer.get_value()), 0)) { \
|
|
||||||
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
|
|
||||||
LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL(); \
|
|
||||||
failaction \
|
|
||||||
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, #expr, __VA_ARGS__) \
|
|
||||||
if constexpr(sizeof libassert_decomposer > 32) { \
|
|
||||||
libassert::detail::process_assert_fail( \
|
|
||||||
libassert_decomposer, \
|
|
||||||
libassert_params \
|
|
||||||
LIBASSERT_VA_ARGS(__VA_ARGS__) LIBASSERT_PRETTY_FUNCTION_ARG \
|
|
||||||
); \
|
|
||||||
} else { \
|
|
||||||
/* std::move it to assert_fail_m, will be moved back to r */ \
|
|
||||||
libassert::detail::process_assert_fail_n( \
|
|
||||||
std::move(libassert_decomposer), \
|
|
||||||
libassert_params \
|
|
||||||
LIBASSERT_VA_ARGS(__VA_ARGS__) LIBASSERT_PRETTY_FUNCTION_ARG \
|
|
||||||
); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_POP_CLANG \
|
|
||||||
} while(false) \
|
|
||||||
|
|
||||||
#define LIBASSERT_INVOKE_PANIC(name, type, ...) \
|
|
||||||
do { \
|
|
||||||
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
|
|
||||||
LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL(); \
|
|
||||||
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, "", __VA_ARGS__) \
|
|
||||||
libassert::detail::process_panic( \
|
|
||||||
libassert_params \
|
|
||||||
LIBASSERT_VA_ARGS(__VA_ARGS__) LIBASSERT_PRETTY_FUNCTION_ARG \
|
|
||||||
); \
|
|
||||||
} while(false) \
|
|
||||||
|
|
||||||
// Workaround for gcc bug 105734 / libassert bug #24
|
|
||||||
#define LIBASSERT_DESTROY_DECOMPOSER libassert_decomposer.~expression_decomposer() /* NOLINT(bugprone-use-after-move,clang-analyzer-cplusplus.Move) */
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
#if __GNUC__ == 12 && __GNUC_MINOR__ == 1
|
|
||||||
namespace libassert::detail {
|
|
||||||
template<typename T> constexpr void destroy(T& t) {
|
|
||||||
t.~T();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#undef LIBASSERT_DESTROY_DECOMPOSER
|
|
||||||
#define LIBASSERT_DESTROY_DECOMPOSER libassert::detail::destroy(libassert_decomposer)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC
|
|
||||||
// Extra set of parentheses here because clang treats __extension__ as a low-precedence unary operator which interferes
|
|
||||||
// with decltype(auto) in an expression like decltype(auto) x = __extension__ ({...}).y;
|
|
||||||
#define LIBASSERT_STMTEXPR(B, R) (__extension__ ({ B R }))
|
|
||||||
#define LIBASSERT_STATIC_CAST_TO_BOOL(x) static_cast<bool>(x)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_STMTEXPR(B, R) [&](const char* libassert_msvc_pfunc) { B return R }(LIBASSERT_PFUNC)
|
|
||||||
// Workaround for msvc bug
|
|
||||||
#define LIBASSERT_STATIC_CAST_TO_BOOL(x) libassert::detail::static_cast_to_bool(x)
|
|
||||||
namespace libassert::detail {
|
|
||||||
template<typename T> constexpr bool static_cast_to_bool(T&& t) {
|
|
||||||
return static_cast<bool>(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#define LIBASSERT_INVOKE_VAL(expr, doreturn, check_expression, name, type, failaction, ...) \
|
|
||||||
/* must push/pop out here due to nasty clang bug https://github.com/llvm/llvm-project/issues/63897 */ \
|
|
||||||
/* must do awful stuff to workaround differences in where gcc and clang allow these directives to go */ \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_PUSH_CLANG \
|
|
||||||
LIBASSERT_IGNORE_UNUSED_VALUE \
|
|
||||||
LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_CLANG \
|
|
||||||
LIBASSERT_STMTEXPR( \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_PUSH_GCC \
|
|
||||||
LIBASSERT_EXPRESSION_DECOMP_WARNING_PRAGMA_GCC \
|
|
||||||
auto libassert_decomposer = libassert::detail::expression_decomposer( \
|
|
||||||
libassert::detail::expression_decomposer{} << expr \
|
|
||||||
); \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_POP_GCC \
|
|
||||||
decltype(auto) libassert_value = libassert_decomposer.get_value(); \
|
|
||||||
constexpr bool libassert_ret_lhs = libassert_decomposer.ret_lhs(); \
|
|
||||||
if constexpr(check_expression) { \
|
|
||||||
/* For *some* godforsaken reason static_cast<bool> causes an ICE in MSVC here. Something very specific */ \
|
|
||||||
/* about casting a decltype(auto) value inside a lambda. Workaround is to put it in a wrapper. */ \
|
|
||||||
/* https://godbolt.org/z/Kq8Wb6q5j https://godbolt.org/z/nMnqnsMYx */ \
|
|
||||||
if(LIBASSERT_STRONG_EXPECT(!LIBASSERT_STATIC_CAST_TO_BOOL(libassert_value), 0)) { \
|
|
||||||
libassert::ERROR_ASSERTION_FAILURE_IN_CONSTEXPR_CONTEXT(); \
|
|
||||||
LIBASSERT_BREAKPOINT_IF_DEBUGGING_ON_FAIL(); \
|
|
||||||
failaction \
|
|
||||||
LIBASSERT_STATIC_DATA(name, libassert::assert_type::type, #expr, __VA_ARGS__) \
|
|
||||||
if constexpr(sizeof libassert_decomposer > 32) { \
|
|
||||||
libassert::detail::process_assert_fail( \
|
|
||||||
libassert_decomposer, \
|
|
||||||
libassert_params \
|
|
||||||
LIBASSERT_VA_ARGS(__VA_ARGS__) LIBASSERT_INVOKE_VAL_PRETTY_FUNCTION_ARG \
|
|
||||||
); \
|
|
||||||
} else { \
|
|
||||||
/* std::move it to assert_fail_m, will be moved back to r */ \
|
|
||||||
auto libassert_r = libassert::detail::process_assert_fail_m( \
|
|
||||||
std::move(libassert_decomposer), \
|
|
||||||
libassert_params \
|
|
||||||
LIBASSERT_VA_ARGS(__VA_ARGS__) LIBASSERT_INVOKE_VAL_PRETTY_FUNCTION_ARG \
|
|
||||||
); \
|
|
||||||
/* can't move-assign back to decomposer if it holds reference members */ \
|
|
||||||
LIBASSERT_DESTROY_DECOMPOSER; \
|
|
||||||
new (&libassert_decomposer) libassert::detail::expression_decomposer(std::move(libassert_r)); \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
}, \
|
|
||||||
/* Note: std::launder needed in 17 in case of placement new / move shenanigans above */ \
|
|
||||||
/* https://timsong-cpp.github.io/cppwp/n4659/basic.life#8.3 */ \
|
|
||||||
/* Note: Somewhat relying on this call being inlined so inefficiency is eliminated */ \
|
|
||||||
libassert::detail::get_expression_return_value< \
|
|
||||||
doreturn LIBASSERT_COMMA \
|
|
||||||
libassert_ret_lhs LIBASSERT_COMMA \
|
|
||||||
std::is_lvalue_reference_v<decltype(libassert_value)> \
|
|
||||||
>(libassert_value, *std::launder(&libassert_decomposer)); \
|
|
||||||
) LIBASSERT_IF(doreturn)(.value,) \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_POP_CLANG
|
|
||||||
|
|
||||||
#ifdef NDEBUG
|
|
||||||
#define LIBASSERT_ASSUME_ACTION LIBASSERT_UNREACHABLE_CALL;
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_ASSUME_ACTION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// assertion macros
|
|
||||||
|
|
||||||
// Debug assert
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define LIBASSERT_DEBUG_ASSERT(expr, ...) LIBASSERT_INVOKE(expr, "DEBUG_ASSERT", debug_assertion, , __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_DEBUG_ASSERT(expr, ...) (void)0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
#define LIBASSERT_ASSERT(expr, ...) LIBASSERT_INVOKE(expr, "ASSERT", assertion, , __VA_ARGS__)
|
|
||||||
// lowercase version intentionally done outside of the include guard here
|
|
||||||
|
|
||||||
// Assume
|
|
||||||
#define LIBASSERT_ASSUME(expr, ...) LIBASSERT_INVOKE(expr, "ASSUME", assumption, LIBASSERT_ASSUME_ACTION, __VA_ARGS__)
|
|
||||||
|
|
||||||
// Panic
|
|
||||||
#define LIBASSERT_PANIC(...) LIBASSERT_INVOKE_PANIC("PANIC", panic, __VA_ARGS__)
|
|
||||||
|
|
||||||
// Unreachable
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define LIBASSERT_UNREACHABLE(...) LIBASSERT_INVOKE_PANIC("UNREACHABLE", unreachable, __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_UNREACHABLE(...) LIBASSERT_UNREACHABLE_CALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// value variants
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define LIBASSERT_DEBUG_ASSERT_VAL(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, true, "DEBUG_ASSERT_VAL", debug_assertion, , __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_DEBUG_ASSERT_VAL(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, false, "DEBUG_ASSERT_VAL", debug_assertion, , __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LIBASSERT_ASSUME_VAL(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, true, "ASSUME_VAL", assumption, LIBASSERT_ASSUME_ACTION, __VA_ARGS__)
|
|
||||||
|
|
||||||
#define LIBASSERT_ASSERT_VAL(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, true, "ASSERT_VAL", assertion, , __VA_ARGS__)
|
|
||||||
|
|
||||||
// non-prefixed versions
|
|
||||||
|
|
||||||
#ifndef LIBASSERT_PREFIX_ASSERTIONS
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC || !LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR
|
|
||||||
#define DEBUG_ASSERT(...) LIBASSERT_DEBUG_ASSERT(__VA_ARGS__)
|
|
||||||
#define ASSERT(...) LIBASSERT_ASSERT(__VA_ARGS__)
|
|
||||||
#define ASSUME(...) LIBASSERT_ASSUME(__VA_ARGS__)
|
|
||||||
#define PANIC(...) LIBASSERT_PANIC(__VA_ARGS__)
|
|
||||||
#define UNREACHABLE(...) LIBASSERT_UNREACHABLE(__VA_ARGS__)
|
|
||||||
#define DEBUG_ASSERT_VAL(...) LIBASSERT_DEBUG_ASSERT_VAL(__VA_ARGS__)
|
|
||||||
#define ASSUME_VAL(...) LIBASSERT_ASSUME_VAL(__VA_ARGS__)
|
|
||||||
#define ASSERT_VAL(...) LIBASSERT_ASSERT_VAL(__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
// because of course msvc
|
|
||||||
#define DEBUG_ASSERT LIBASSERT_DEBUG_ASSERT
|
|
||||||
#define ASSERT LIBASSERT_ASSERT
|
|
||||||
#define ASSUME LIBASSERT_ASSUME
|
|
||||||
#define PANIC LIBASSERT_PANIC
|
|
||||||
#define UNREACHABLE LIBASSERT_UNREACHABLE
|
|
||||||
#define DEBUG_ASSERT_VAL LIBASSERT_DEBUG_ASSERT_VAL
|
|
||||||
#define ASSUME_VAL LIBASSERT_ASSUME_VAL
|
|
||||||
#define ASSERT_VAL LIBASSERT_ASSERT_VAL
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Lowercase variants
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define debug_assert(expr, ...) LIBASSERT_INVOKE(expr, "debug_assert", debug_assertion, , __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define debug_assert(expr, ...) (void)0
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define debug_assert_val(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, true, "debug_assert_val", debug_assertion, , __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define debug_assert_val(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, false, "debug_assert_val", debug_assertion, , __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#define assert_val(expr, ...) LIBASSERT_INVOKE_VAL(expr, true, true, "assert_val", assertion, , __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Wrapper macro to allow support for C++26's user generated static_assert messages.
|
|
||||||
// The backup message version also allows for the user to provide a backup version that will
|
|
||||||
// be used if the compiler does not support user generated messages.
|
|
||||||
// More info on user generated static_assert's
|
|
||||||
// can be found here: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2741r1.pdf
|
|
||||||
//
|
|
||||||
// Currently the functionality works as such. If we are in a C++26 environment, the user generated message will be used.
|
|
||||||
// If we are not in a C++26 environment, then either the static_assert will be used without a message or the backup message.
|
|
||||||
// TODO: Maybe give these a better name? Ideally one that is shorter and more descriptive?
|
|
||||||
// TODO: Maybe add a helper to make passing user generated static_assert messages easier?
|
|
||||||
#if defined(__cpp_static_assert) && __cpp_static_assert >= 202306L
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#define libassert_user_static_assert(cond, constant) static_assert(cond, constant)
|
|
||||||
#define libassert_user_static_assert_backup_msg(cond, msg, constant) static_assert(cond, constant)
|
|
||||||
#define user_static_assert(cond, constant) static_assert(cond, constant)
|
|
||||||
#define user_static_assert_backup_msg(cond, msg, constant) static_assert(cond, constant)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_USER_STATIC_ASSERT(cond, constant) static_assert(cond, constant)
|
|
||||||
#define LIBASSERT_USER_STATIC_ASSERT_BACKUP_MSG(cond, msg, constant) static_assert(cond, constant)
|
|
||||||
#define USER_STATIC_ASSERT(cond, constant) static_assert(cond, constant)
|
|
||||||
#define USER_STATIC_ASSERT_BACKUP_MSG(cond, msg, constant) static_assert(cond, constant)
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#define libassert_user_static_assert(cond, constant) static_assert(cond)
|
|
||||||
#define libassert_user_static_assert_backup_msg(cond, msg, constant) static_assert(cond, msg)
|
|
||||||
#define user_static_assert(cond, constant) static_assert(cond)
|
|
||||||
#define user_static_assert_backup_msg(cond, msg, constant) static_assert(cond, msg)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_USER_STATIC_ASSERT(cond, constant) static_assert(cond)
|
|
||||||
#define LIBASSERT_USER_STATIC_ASSERT_BACKUP_MSG(cond, msg, constant) static_assert(cond, msg)
|
|
||||||
#define USER_STATIC_ASSERT(cond, constant) static_assert(cond)
|
|
||||||
#define USER_STATIC_ASSERT_BACKUP_MSG(cond, msg, constant) static_assert(cond, msg)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#endif // LIBASSERT_HPP
|
|
||||||
|
|
||||||
// Intentionally done outside the include guard. Libc++ leaks `assert` (among other things), so the include for
|
|
||||||
// assert.hpp should go after other includes when using -DLIBASSERT_LOWERCASE.
|
|
||||||
#ifdef LIBASSERT_LOWERCASE
|
|
||||||
#ifdef assert
|
|
||||||
#undef assert
|
|
||||||
#endif
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define assert(expr, ...) LIBASSERT_INVOKE(expr, "assert", assertion, , __VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define assert(expr, ...) LIBASSERT_INVOKE(expr, "assert", assertion, , __VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
#endif
|
|
@ -1,330 +0,0 @@
|
|||||||
#ifndef LIBASSERT_EXPRESSION_DECOMPOSITION_HPP
|
|
||||||
#define LIBASSERT_EXPRESSION_DECOMPOSITION_HPP
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <libassert/platform.hpp>
|
|
||||||
#include <libassert/utilities.hpp>
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Expression decomposition micro-library ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
// Lots of boilerplate
|
|
||||||
// Using int comparison functions here to support proper signed comparisons. Need to make sure
|
|
||||||
// assert(map.count(1) == 2) doesn't produce a warning. It wouldn't under normal circumstances
|
|
||||||
// but it would in this library due to the parameters being forwarded down a long chain.
|
|
||||||
// And we want to provide as much robustness as possible anyways.
|
|
||||||
// Copied and pasted from https://en.cppreference.com/w/cpp/utility/intcmp
|
|
||||||
// Not using std:: versions because library is targeting C++17
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_equal(T t, U u) {
|
|
||||||
using UT = std::make_unsigned_t<T>;
|
|
||||||
using UU = std::make_unsigned_t<U>;
|
|
||||||
if constexpr(std::is_signed_v<T> == std::is_signed_v<U>) {
|
|
||||||
return t == u;
|
|
||||||
} else if constexpr(std::is_signed_v<T>) {
|
|
||||||
return t >= 0 && UT(t) == u;
|
|
||||||
} else {
|
|
||||||
return u >= 0 && t == UU(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_not_equal(T t, U u) {
|
|
||||||
return !cmp_equal(t, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_less(T t, U u) {
|
|
||||||
using UT = std::make_unsigned_t<T>;
|
|
||||||
using UU = std::make_unsigned_t<U>;
|
|
||||||
if constexpr(std::is_signed_v<T> == std::is_signed_v<U>) {
|
|
||||||
return t < u;
|
|
||||||
} else if constexpr(std::is_signed_v<T>) {
|
|
||||||
return t < 0 || UT(t) < u;
|
|
||||||
} else {
|
|
||||||
return u >= 0 && t < UU(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_greater(T t, U u) {
|
|
||||||
return cmp_less(u, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_less_equal(T t, U u) {
|
|
||||||
return !cmp_less(u, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
[[nodiscard]] constexpr bool cmp_greater_equal(T t, U u) {
|
|
||||||
return !cmp_less(t, u);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lots of boilerplate
|
|
||||||
// std:: implementations don't allow two separate types for lhs/rhs
|
|
||||||
// Note: is this macro potentially bad when it comes to debugging(?)
|
|
||||||
namespace ops {
|
|
||||||
#define LIBASSERT_GEN_OP_BOILERPLATE(name, op) struct name { \
|
|
||||||
static constexpr std::string_view op_string = #op; \
|
|
||||||
template<typename A, typename B> \
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]] \
|
|
||||||
constexpr decltype(auto) operator()(A&& lhs, B&& rhs) const { /* no need to forward ints */ \
|
|
||||||
return std::forward<A>(lhs) op std::forward<B>(rhs); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
#define LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(name, op, cmp) struct name { \
|
|
||||||
static constexpr std::string_view op_string = #op; \
|
|
||||||
template<typename A, typename B> \
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]] \
|
|
||||||
constexpr decltype(auto) operator()(A&& lhs, B&& rhs) const { /* no need to forward ints */ \
|
|
||||||
if constexpr(is_integral_and_not_bool<A> && is_integral_and_not_bool<B>) return cmp(lhs, rhs); \
|
|
||||||
else return std::forward<A>(lhs) op std::forward<B>(rhs); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(shl, <<);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(shr, >>);
|
|
||||||
#if __cplusplus >= 202002L
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(spaceship, <=>);
|
|
||||||
#endif
|
|
||||||
#ifdef LIBASSERT_SAFE_COMPARISONS
|
|
||||||
// TODO: Make a SAFE() wrapper...
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(eq, ==, cmp_equal);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(neq, !=, cmp_not_equal);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(gt, >, cmp_greater);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(lt, <, cmp_less);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(gteq, >=, cmp_greater_equal);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL(lteq, <=, cmp_less_equal);
|
|
||||||
#else
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(eq, ==);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(neq, !=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(gt, >);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(lt, <);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(gteq, >=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(lteq, <=);
|
|
||||||
#endif
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(band, &);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(bxor, ^);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(bor, |);
|
|
||||||
#ifdef LIBASSERT_DECOMPOSE_BINARY_LOGICAL
|
|
||||||
struct land {
|
|
||||||
static constexpr std::string_view op_string = "&&";
|
|
||||||
template<typename A, typename B>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
constexpr decltype(auto) operator()(A&& lhs, B&& rhs) const {
|
|
||||||
// Go out of the way to support old-style ASSERT(foo && "Message")
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
if constexpr(is_string_literal<B>) {
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wnonnull-compare"
|
|
||||||
return std::forward<A>(lhs) && std::forward<B>(rhs);
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
} else {
|
|
||||||
return std::forward<A>(lhs) && std::forward<B>(rhs);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
return std::forward<A>(lhs) && std::forward<B>(rhs);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
};
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(lor, ||);
|
|
||||||
#endif
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(assign, =);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(add_assign, +=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(sub_assign, -=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(mul_assign, *=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(div_assign, /=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(mod_assign, %=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(shl_assign, <<=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(shr_assign, >>=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(band_assign, &=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(bxor_assign, ^=);
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(bor_assign, |=);
|
|
||||||
#undef LIBASSERT_GEN_OP_BOILERPLATE
|
|
||||||
#undef LIBASSERT_GEN_OP_BOILERPLATE_SPECIAL
|
|
||||||
}
|
|
||||||
|
|
||||||
// I learned this automatic expression decomposition trick from lest:
|
|
||||||
// https://github.com/martinmoene/lest/blob/master/include/lest/lest.hpp#L829-L853
|
|
||||||
//
|
|
||||||
// I have improved upon the trick by supporting more operators and generally improving
|
|
||||||
// functionality.
|
|
||||||
//
|
|
||||||
// Some cases to test and consider:
|
|
||||||
//
|
|
||||||
// Expression Parsed as
|
|
||||||
// Basic:
|
|
||||||
// false << false
|
|
||||||
// a == b (<< a) == b
|
|
||||||
//
|
|
||||||
// Equal precedence following:
|
|
||||||
// a << b (<< a) << b
|
|
||||||
// a << b << c ((<< a) << b) << c
|
|
||||||
// a << b + c (<< a) << (b + c)
|
|
||||||
// a << b < c ((<< a) << b) < c // edge case
|
|
||||||
//
|
|
||||||
// Higher precedence following:
|
|
||||||
// a + b << (a + b)
|
|
||||||
// a + b + c << ((a + b) + c)
|
|
||||||
// a + b * c << (a + (b * c))
|
|
||||||
// a + b < c (<< (a + b)) < c
|
|
||||||
//
|
|
||||||
// Lower precedence following:
|
|
||||||
// a < b (<< a) < b
|
|
||||||
// a < b < c ((<< a) < b) < c
|
|
||||||
// a < b + c (<< a) < (b + c)
|
|
||||||
// a < b == c ((<< a) < b) == c // edge case
|
|
||||||
|
|
||||||
template<typename A = nothing, typename B = nothing, typename C = nothing>
|
|
||||||
struct expression_decomposer {
|
|
||||||
A a;
|
|
||||||
B b;
|
|
||||||
explicit constexpr expression_decomposer() = default;
|
|
||||||
~expression_decomposer() = default;
|
|
||||||
// not copyable
|
|
||||||
constexpr expression_decomposer(const expression_decomposer&) = delete;
|
|
||||||
constexpr expression_decomposer& operator=(const expression_decomposer&) = delete;
|
|
||||||
// allow move construction
|
|
||||||
constexpr expression_decomposer(expression_decomposer&&)
|
|
||||||
#if !LIBASSERT_IS_GCC || __GNUC__ >= 10 // gcc 9 has some issue with the move constructor being noexcept
|
|
||||||
noexcept
|
|
||||||
#endif
|
|
||||||
= default;
|
|
||||||
constexpr expression_decomposer& operator=(expression_decomposer&&) = delete;
|
|
||||||
// value constructors
|
|
||||||
template<typename U, typename std::enable_if_t<!isa<U, expression_decomposer>, int> = 0>
|
|
||||||
// NOLINTNEXTLINE(bugprone-forwarding-reference-overload) // TODO
|
|
||||||
explicit constexpr expression_decomposer(U&& _a) : a(std::forward<U>(_a)) {}
|
|
||||||
template<typename U, typename V>
|
|
||||||
explicit constexpr expression_decomposer(U&& _a, V&& _b) : a(std::forward<U>(_a)), b(std::forward<V>(_b)) {}
|
|
||||||
/* Ownership logic:
|
|
||||||
* One of two things can happen to this class
|
|
||||||
* - Either it is composed with another operation
|
|
||||||
* + The value of the subexpression represented by this is computed (either get_value()
|
|
||||||
* or operator bool), either A& or C()(a, b)
|
|
||||||
* + Or, just the lhs is moved B is nothing
|
|
||||||
* - Or this class represents the whole expression
|
|
||||||
* + The value is computed (either A& or C()(a, b))
|
|
||||||
* + a and b are referenced freely
|
|
||||||
* + Either the value is taken or a is moved out
|
|
||||||
* Ensuring the value is only computed once is left to the assert handler
|
|
||||||
*/
|
|
||||||
[[nodiscard]]
|
|
||||||
constexpr decltype(auto) get_value() {
|
|
||||||
if constexpr(is_nothing<C>) {
|
|
||||||
static_assert(is_nothing<B> && !is_nothing<A>);
|
|
||||||
return (a);
|
|
||||||
} else {
|
|
||||||
return C()(a, b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[[nodiscard]]
|
|
||||||
constexpr operator bool() { // for ternary support
|
|
||||||
return (bool)get_value();
|
|
||||||
}
|
|
||||||
// return true if the lhs should be returned, false if full computed value should be
|
|
||||||
[[nodiscard]]
|
|
||||||
constexpr bool ret_lhs() {
|
|
||||||
static_assert(!is_nothing<A>);
|
|
||||||
static_assert(is_nothing<B> == is_nothing<C>);
|
|
||||||
if constexpr(is_nothing<C>) {
|
|
||||||
// if there is no top-level binary operation, A is the only thing that can be returned
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// return LHS for the following types;
|
|
||||||
return C::op_string == "=="
|
|
||||||
|| C::op_string == "!="
|
|
||||||
|| C::op_string == "<"
|
|
||||||
|| C::op_string == ">"
|
|
||||||
|| C::op_string == "<="
|
|
||||||
|| C::op_string == ">="
|
|
||||||
|| C::op_string == "&&"
|
|
||||||
|| C::op_string == "||";
|
|
||||||
// don't return LHS for << >> & ^ | and all assignments
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[[nodiscard]]
|
|
||||||
constexpr A take_lhs() { // should only be called if ret_lhs() == true
|
|
||||||
if constexpr(std::is_lvalue_reference_v<A>) {
|
|
||||||
return ((((a))));
|
|
||||||
} else {
|
|
||||||
return std::move(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Need overloads for operators with precedence <= bitshift
|
|
||||||
// TODO: spaceship operator?
|
|
||||||
// Note: Could decompose more than just comparison and boolean operators, but it would take
|
|
||||||
// a lot of work and I don't think it's beneficial for this library.
|
|
||||||
template<typename O> [[nodiscard]] constexpr auto operator<<(O&& operand) && {
|
|
||||||
using Q = std::conditional_t<std::is_rvalue_reference_v<O>, std::remove_reference_t<O>, O>;
|
|
||||||
if constexpr(is_nothing<A>) {
|
|
||||||
static_assert(is_nothing<B> && is_nothing<C>);
|
|
||||||
return expression_decomposer<Q, nothing, nothing>(std::forward<O>(operand));
|
|
||||||
} else if constexpr(is_nothing<B>) {
|
|
||||||
static_assert(is_nothing<C>);
|
|
||||||
return expression_decomposer<A, Q, ops::shl>(std::forward<A>(a), std::forward<O>(operand));
|
|
||||||
} else {
|
|
||||||
static_assert(!is_nothing<C>);
|
|
||||||
return expression_decomposer<decltype(get_value()), O, ops::shl>(
|
|
||||||
std::forward<A>(get_value()),
|
|
||||||
std::forward<O>(operand)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#define LIBASSERT_GEN_OP_BOILERPLATE(functor, op) \
|
|
||||||
template<typename O> [[nodiscard]] constexpr auto operator op(O&& operand) && { \
|
|
||||||
static_assert(!is_nothing<A>); \
|
|
||||||
using Q = std::conditional_t<std::is_rvalue_reference_v<O>, std::remove_reference_t<O>, O>; \
|
|
||||||
if constexpr(is_nothing<B>) { \
|
|
||||||
static_assert(is_nothing<C>); \
|
|
||||||
return expression_decomposer<A, Q, functor>(std::forward<A>(a), std::forward<O>(operand)); \
|
|
||||||
} else { \
|
|
||||||
static_assert(!is_nothing<C>); \
|
|
||||||
using V = decltype(get_value()); \
|
|
||||||
return expression_decomposer<V, Q, functor>(std::forward<V>(get_value()), std::forward<O>(operand)); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::shr, >>)
|
|
||||||
#if __cplusplus >= 202002L
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::spaceship, <=>)
|
|
||||||
#endif
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::eq, ==)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::neq, !=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::gt, >)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::lt, <)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::gteq, >=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::lteq, <=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::band, &)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::bxor, ^)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::bor, |)
|
|
||||||
#ifdef LIBASSERT_DECOMPOSE_BINARY_LOGICAL
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::land, &&)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::lor, ||)
|
|
||||||
#endif
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::assign, =) // NOLINT(cppcoreguidelines-c-copy-assignment-signature, misc-unconventional-assign-operator)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::add_assign, +=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::sub_assign, -=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::mul_assign, *=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::div_assign, /=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::mod_assign, %=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::shl_assign, <<=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::shr_assign, >>=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::band_assign, &=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::bxor_assign, ^=)
|
|
||||||
LIBASSERT_GEN_OP_BOILERPLATE(ops::bor_assign, |=)
|
|
||||||
#undef LIBASSERT_GEN_OP_BOILERPLATE
|
|
||||||
};
|
|
||||||
|
|
||||||
// for ternary support
|
|
||||||
template<typename U>
|
|
||||||
expression_decomposer(U&&) -> expression_decomposer<
|
|
||||||
std::conditional_t<std::is_rvalue_reference_v<U>, std::remove_reference_t<U>, U>
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,302 +0,0 @@
|
|||||||
#ifndef LIBASSERT_PLATFORM_HPP
|
|
||||||
#define LIBASSERT_PLATFORM_HPP
|
|
||||||
|
|
||||||
// Copyright (c) 2021-2024 Jeremy Rifkin under the MIT license
|
|
||||||
// https://github.com/jeremy-rifkin/libassert
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Preprocessor stuff ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
// Set the C++ version number based on if we are on a dumb compiler like MSVC or not.
|
|
||||||
#ifdef _MSVC_LANG
|
|
||||||
#define LIBASSERT_CPLUSPLUS _MSVC_LANG
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_CPLUSPLUS __cplusplus
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_CPLUSPLUS >= 202302L
|
|
||||||
#define LIBASSERT_STD_VER 23
|
|
||||||
#elif LIBASSERT_CPLUSPLUS >= 202002L
|
|
||||||
#define LIBASSERT_STD_VER 20
|
|
||||||
#elif LIBASSERT_CPLUSPLUS >= 201703L
|
|
||||||
#define LIBASSERT_STD_VER 17
|
|
||||||
#else
|
|
||||||
#error "libassert requires C++17 or newer"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Detect compiler versions.
|
|
||||||
///
|
|
||||||
|
|
||||||
#if defined(__clang__) && !defined(__ibmxl__)
|
|
||||||
#define LIBASSERT_IS_CLANG 1
|
|
||||||
#define LIBASSERT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IS_CLANG 0
|
|
||||||
#define LIBASSERT_CLANG_VERSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (defined(__GNUC__) || defined(__GNUG__)) && !defined(__clang__) && !defined(__INTEL_COMPILER)
|
|
||||||
#define LIBASSERT_IS_GCC 1
|
|
||||||
#define LIBASSERT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IS_GCC 0
|
|
||||||
#define LIBASSERT_GCC_VERSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(__clang__) // clang on windows defines _MSC_VER
|
|
||||||
#define LIBASSERT_IS_MSVC 1
|
|
||||||
#define LIBASSERT_MSVC_VERSION _MSC_VER
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IS_MSVC 0
|
|
||||||
#define LIBASSERT_MSVC_VERSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __INTEL_COMPILER
|
|
||||||
#define LIBASSERT_IS_ICC 1
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IS_ICC 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __INTEL_LLVM_COMPILER
|
|
||||||
#define LIBASSERT_IS_ICX 1
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IS_ICX 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Detect standard library versions.
|
|
||||||
///
|
|
||||||
|
|
||||||
// libstdc++
|
|
||||||
#ifdef _GLIBCXX_RELEASE
|
|
||||||
#define LIBASSERT_GLIBCXX_RELEASE _GLIBCXX_RELEASE
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_GLIBCXX_RELEASE 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _LIBCPP_VERSION
|
|
||||||
#define LIBASSERT_LIBCPP_VERSION _LIBCPP_VERSION
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_LIBCPP_VERSION 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Helper macros for compiler attributes.
|
|
||||||
///
|
|
||||||
|
|
||||||
#ifdef __has_cpp_attribute
|
|
||||||
#define LIBASSERT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_HAS_CPP_ATTRIBUTE(x) 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// Compiler attribute support.
|
|
||||||
///
|
|
||||||
|
|
||||||
#if LIBASSERT_HAS_CPP_ATTRIBUTE(nodiscard) && LIBASSERT_STD_VER >= 20
|
|
||||||
#define LIBASSERT_ATTR_NODISCARD_MSG(msg) [[nodiscard(msg)]]
|
|
||||||
#else // Assume we have normal C++17 nodiscard support.
|
|
||||||
#define LIBASSERT_ATTR_NODISCARD_MSG(msg) [[nodiscard]]
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if LIBASSERT_HAS_CPP_ATTRIBUTE(no_unique_address)
|
|
||||||
#define LIBASSERT_ATTR_NO_UNIQUE_ADDRESS [[no_unique_address]]
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_ATTR_NO_UNIQUE_ADDRESS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// General project macros
|
|
||||||
///
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define LIBASSERT_EXPORT_ATTR __declspec(dllexport)
|
|
||||||
#define LIBASSERT_IMPORT_ATTR __declspec(dllimport)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_EXPORT_ATTR __attribute__((visibility("default")))
|
|
||||||
#define LIBASSERT_IMPORT_ATTR __attribute__((visibility("default")))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_STATIC_DEFINE
|
|
||||||
#define LIBASSERT_EXPORT
|
|
||||||
#define LIBASSERT_NO_EXPORT
|
|
||||||
#else
|
|
||||||
#ifndef LIBASSERT_EXPORT
|
|
||||||
#ifdef libassert_lib_EXPORTS
|
|
||||||
/* We are building this library */
|
|
||||||
#define LIBASSERT_EXPORT LIBASSERT_EXPORT_ATTR
|
|
||||||
#else
|
|
||||||
/* We are using this library */
|
|
||||||
#define LIBASSERT_EXPORT LIBASSERT_IMPORT_ATTR
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC
|
|
||||||
#define LIBASSERT_PFUNC __extension__ __PRETTY_FUNCTION__
|
|
||||||
#define LIBASSERT_ATTR_COLD [[gnu::cold]]
|
|
||||||
#define LIBASSERT_ATTR_NOINLINE [[gnu::noinline]]
|
|
||||||
#define LIBASSERT_UNREACHABLE_CALL __builtin_unreachable()
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_PFUNC __FUNCSIG__
|
|
||||||
#define LIBASSERT_ATTR_COLD
|
|
||||||
#define LIBASSERT_ATTR_NOINLINE __declspec(noinline)
|
|
||||||
#define LIBASSERT_UNREACHABLE_CALL __assume(false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_MSVC
|
|
||||||
#define LIBASSERT_STRONG_EXPECT(expr, value) (expr)
|
|
||||||
#elif (defined(__clang__) && __clang_major__ >= 11) || __GNUC__ >= 9
|
|
||||||
#define LIBASSERT_STRONG_EXPECT(expr, value) __builtin_expect_with_probability((expr), (value), 1)
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_STRONG_EXPECT(expr, value) __builtin_expect((expr), (value))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// deal with gcc shenanigans
|
|
||||||
// at one point their std::string's move assignment was not noexcept even in c++17
|
|
||||||
// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
|
|
||||||
#if defined(_GLIBCXX_USE_CXX11_ABI)
|
|
||||||
// NOLINTNEXTLINE(misc-include-cleaner)
|
|
||||||
#define LIBASSERT_GCC_ISNT_STUPID _GLIBCXX_USE_CXX11_ABI
|
|
||||||
#else
|
|
||||||
// assume others target new abi by default - homework
|
|
||||||
#define LIBASSERT_GCC_ISNT_STUPID 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL
|
|
||||||
#define LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR true
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR false
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if (LIBASSERT_IS_GCC || LIBASSERT_STD_VER >= 20) && !LIBASSERT_NON_CONFORMANT_MSVC_PREPROCESSOR
|
|
||||||
// __VA_OPT__ needed for GCC, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=44317
|
|
||||||
#define LIBASSERT_VA_ARGS(...) __VA_OPT__(,) __VA_ARGS__
|
|
||||||
#else
|
|
||||||
// clang properly eats the comma with ##__VA_ARGS__
|
|
||||||
#define LIBASSERT_VA_ARGS(...) , ##__VA_ARGS__
|
|
||||||
#endif
|
|
||||||
|
|
||||||
///
|
|
||||||
/// C++20 functionality wrappers.
|
|
||||||
///
|
|
||||||
|
|
||||||
// Check if we can use std::is_constant_evaluated.
|
|
||||||
#ifdef __has_include
|
|
||||||
#if __has_include(<version>)
|
|
||||||
#include <version>
|
|
||||||
#ifdef __cpp_lib_is_constant_evaluated
|
|
||||||
#include <type_traits>
|
|
||||||
#define LIBASSERT_HAS_IS_CONSTANT_EVALUATED
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check if we have the builtin __builtin_is_constant_evaluated.
|
|
||||||
#ifdef __has_builtin
|
|
||||||
#if __has_builtin(__builtin_is_constant_evaluated)
|
|
||||||
#define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// GCC 9.1+ and later has __builtin_is_constant_evaluated
|
|
||||||
#if defined(__GNUC__) && (__GNUC__ >= 9) && !defined(LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED)
|
|
||||||
#define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Visual Studio 2019 (19.25) and later supports __builtin_is_constant_evaluated
|
|
||||||
#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 192528326)
|
|
||||||
#define LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
// Note: Works with >=C++20 and with C++17 for GCC 9.1+, Clang 9+, and MSVC 19.25+.
|
|
||||||
constexpr bool is_constant_evaluated() noexcept {
|
|
||||||
#if defined(LIBASSERT_HAS_IS_CONSTANT_EVALUATED)
|
|
||||||
return std::is_constant_evaluated();
|
|
||||||
#elif defined(LIBASSERT_HAS_BUILTIN_IS_CONSTANT_EVALUATED)
|
|
||||||
return __builtin_is_constant_evaluated();
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_GCC
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC _Pragma("GCC diagnostic push")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC _Pragma("GCC diagnostic pop")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG _Pragma("GCC diagnostic push")
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG _Pragma("GCC diagnostic pop")
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_CLANG
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_PUSH_GCC
|
|
||||||
#define LIBASSERT_WARNING_PRAGMA_POP_GCC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_IS_CLANG || LIBASSERT_IS_ICX
|
|
||||||
// clang and icx support this as far back as this library could care
|
|
||||||
#define LIBASSERT_BREAKPOINT() __builtin_debugtrap()
|
|
||||||
#elif LIBASSERT_IS_MSVC || LIBASSERT_IS_ICC
|
|
||||||
// msvc and icc support this as far back as this library could care
|
|
||||||
#define LIBASSERT_BREAKPOINT() __debugbreak()
|
|
||||||
#elif LIBASSERT_IS_GCC
|
|
||||||
#if LIBASSERT_GCC_VERSION >= 1200
|
|
||||||
#define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING _Pragma("GCC diagnostic ignored \"-Wc++20-extensions\"")
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING
|
|
||||||
#endif
|
|
||||||
#define LIBASSERT_ASM_BREAKPOINT(instruction) \
|
|
||||||
do { \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_PUSH_GCC \
|
|
||||||
LIBASSERT_IGNORE_CPP20_EXTENSION_WARNING \
|
|
||||||
__asm__ __volatile__(instruction) \
|
|
||||||
; \
|
|
||||||
LIBASSERT_WARNING_PRAGMA_POP_GCC \
|
|
||||||
} while(0)
|
|
||||||
// precedence for these come from llvm's __builtin_debugtrap() implementation
|
|
||||||
// arm: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrInfo.td#L2393-L2394
|
|
||||||
// def : Pat<(debugtrap), (BKPT 0)>, Requires<[IsARM, HasV5T]>;
|
|
||||||
// def : Pat<(debugtrap), (UDF 254)>, Requires<[IsARM, NoV5T]>;
|
|
||||||
// thumb: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/ARM/ARMInstrThumb.td#L1444-L1445
|
|
||||||
// def : Pat<(debugtrap), (tBKPT 0)>, Requires<[IsThumb, HasV5T]>;
|
|
||||||
// def : Pat<(debugtrap), (tUDF 254)>, Requires<[IsThumb, NoV5T]>;
|
|
||||||
// aarch64: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/AArch64/AArch64FastISel.cpp#L3615-L3618
|
|
||||||
// case Intrinsic::debugtrap:
|
|
||||||
// BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, TII.get(AArch64::BRK))
|
|
||||||
// .addImm(0xF000);
|
|
||||||
// return true;
|
|
||||||
// x86: https://github.com/llvm/llvm-project/blob/e9954ec087d640809082f46d1c7e5ac1767b798d/llvm/lib/Target/X86/X86InstrSystem.td#L81-L84
|
|
||||||
// def : Pat<(debugtrap),
|
|
||||||
// (INT3)>, Requires<[NotPS]>;
|
|
||||||
#if defined(__i386__) || defined(__x86_64__)
|
|
||||||
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("int3")
|
|
||||||
#elif defined(__arm__) || defined(__thumb__)
|
|
||||||
#if __ARM_ARCH >= 5
|
|
||||||
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("bkpt #0")
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("udf #0xfe")
|
|
||||||
#endif
|
|
||||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
|
||||||
#define LIBASSERT_BREAKPOINT() LIBASSERT_ASM_BREAKPOINT("brk #0xf000")
|
|
||||||
#else
|
|
||||||
// some architecture we aren't prepared for
|
|
||||||
#define LIBASSERT_BREAKPOINT()
|
|
||||||
#endif
|
|
||||||
#else
|
|
||||||
// some compiler we aren't prepared for
|
|
||||||
#define LIBASSERT_BREAKPOINT()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,508 +0,0 @@
|
|||||||
#ifndef LIBASSERT_STRINGIFICATION_HPP
|
|
||||||
#define LIBASSERT_STRINGIFICATION_HPP
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <system_error>
|
|
||||||
|
|
||||||
#include <libassert/platform.hpp>
|
|
||||||
#include <libassert/utilities.hpp>
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_USE_MAGIC_ENUM
|
|
||||||
// relative include so that multiple library versions don't clash
|
|
||||||
// e.g. if both libA and libB have different versions of libassert as a public
|
|
||||||
// dependency, then any library that consumes both will have both sets of include
|
|
||||||
// paths. this isn't an issue for #include <assert.hpp> but becomes an issue
|
|
||||||
// for includes within the library (libA might include from libB)
|
|
||||||
#if defined(__has_include) && __has_include(<magic_enum/magic_enum.hpp>)
|
|
||||||
#include <magic_enum/magic_enum.hpp>
|
|
||||||
#else
|
|
||||||
#include <magic_enum.hpp>
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_USE_FMT
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LIBASSERT_STD_VER >= 20
|
|
||||||
#include <compare>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Stringification micro-library ||
|
|
||||||
// || Note: There is some stateful stuff behind the scenes related to literal format configuration ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert {
|
|
||||||
// customization point
|
|
||||||
template<typename T> struct stringifier /*{
|
|
||||||
std::convertible_to<std::string> stringify(const T&);
|
|
||||||
}*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
// What can be stringified
|
|
||||||
// Base types:
|
|
||||||
// - anything string-like
|
|
||||||
// - arithmetic types
|
|
||||||
// - pointers
|
|
||||||
// - smart pointers
|
|
||||||
// - nullptr_t
|
|
||||||
// - std::error_code/std::error_condition
|
|
||||||
// - orderings
|
|
||||||
// - enum values
|
|
||||||
// User-provided stringifications:
|
|
||||||
// - std::ostream<< overloads
|
|
||||||
// - std format TODO
|
|
||||||
// - fmt TODO
|
|
||||||
// - libassert::stringifier
|
|
||||||
// Containers:
|
|
||||||
// - std::optional
|
|
||||||
// - std::variant TODO
|
|
||||||
// - std::expected TODO
|
|
||||||
// - tuples and tuple-likes
|
|
||||||
// - anything container-like (std::vector, std::array, std::unordered_map, C arrays, .....)
|
|
||||||
// Priorities:
|
|
||||||
// - libassert::stringifier
|
|
||||||
// - default formatters
|
|
||||||
// - fmt
|
|
||||||
// - std fmt TODO
|
|
||||||
// - osteam format
|
|
||||||
// - instance of catch all
|
|
||||||
// Other logistics:
|
|
||||||
// - Containers are only stringified if their value_type is stringifiable
|
|
||||||
// TODO Weak pointers?
|
|
||||||
|
|
||||||
template<typename Test, template<typename...> class Ref>
|
|
||||||
struct is_specialization : std::false_type {};
|
|
||||||
|
|
||||||
template<template<typename...> class Ref, typename... Args>
|
|
||||||
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string do_stringify(const T& v);
|
|
||||||
|
|
||||||
namespace stringification {
|
|
||||||
//
|
|
||||||
// General traits
|
|
||||||
//
|
|
||||||
template<typename T, typename = void> class is_tuple_like : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class is_tuple_like<
|
|
||||||
T,
|
|
||||||
std::void_t<
|
|
||||||
typename std::tuple_size<T>::type, // TODO: decltype(std::tuple_size_v<T>) ?
|
|
||||||
decltype(std::get<0>(std::declval<T>()))
|
|
||||||
>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
namespace adl {
|
|
||||||
using std::begin, std::end; // ADL
|
|
||||||
template<typename T, typename = void> class is_container : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class is_container<
|
|
||||||
T,
|
|
||||||
std::void_t<
|
|
||||||
decltype(begin(decllval<T>())),
|
|
||||||
decltype(end(decllval<T>()))
|
|
||||||
>
|
|
||||||
> : public std::true_type {};
|
|
||||||
template<typename T, typename = void> class is_begin_deref : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class is_begin_deref<
|
|
||||||
T,
|
|
||||||
std::void_t<
|
|
||||||
decltype(*begin(decllval<T>()))
|
|
||||||
>
|
|
||||||
> : public std::true_type {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename = void> class is_deref : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class is_deref<
|
|
||||||
T,
|
|
||||||
std::void_t<
|
|
||||||
decltype(*decllval<T>())
|
|
||||||
>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
template<typename T, typename = void> class has_ostream_overload : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class has_ostream_overload<
|
|
||||||
T,
|
|
||||||
std::void_t<decltype(std::declval<std::ostream>() << std::declval<T>())>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
template<typename T, typename = void> class has_stringifier : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class has_stringifier<
|
|
||||||
T,
|
|
||||||
std::void_t<decltype(stringifier<strip<T>>{}.stringify(std::declval<T>()))>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Catch all
|
|
||||||
//
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] std::string stringify_unknown() {
|
|
||||||
return bstringf("<instance of %s>", prettify_type(std::string(type_name<T>())).c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Basic types
|
|
||||||
//
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::string_view);
|
|
||||||
// without nullptr_t overload msvc (without /permissive-) will call stringify(bool) and mingw
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::nullptr_t);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(char);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(bool);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(short);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(int);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(long);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(long long);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(unsigned short);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(unsigned int);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(unsigned long);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(unsigned long long);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(float);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(double);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(long double);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::error_code ec);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::error_condition ec);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(const std::filesystem::path& path);
|
|
||||||
#if __cplusplus >= 202002L
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::strong_ordering);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::weak_ordering);
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string stringify(std::partial_ordering);
|
|
||||||
#endif
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT
|
|
||||||
std::string stringify_pointer_value(const void*);
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify_smart_ptr(const T& t) {
|
|
||||||
if(t) {
|
|
||||||
return do_stringify(*t);
|
|
||||||
} else {
|
|
||||||
return "nullptr";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify_by_ostream(const T& t) {
|
|
||||||
// clang-tidy bug here
|
|
||||||
// NOLINTNEXTLINE(misc-const-correctness)
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss<<t;
|
|
||||||
return std::move(oss).str();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef LIBASSERT_USE_MAGIC_ENUM
|
|
||||||
template<typename T, typename std::enable_if_t<std::is_enum_v<strip<T>>, int> = 0>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]] std::string stringify_enum(const T& t) {
|
|
||||||
std::string_view name = magic_enum::enum_name(t);
|
|
||||||
if(!name.empty()) {
|
|
||||||
return std::string(name);
|
|
||||||
} else {
|
|
||||||
return bstringf(
|
|
||||||
"enum %s: %s",
|
|
||||||
prettify_type(std::string(type_name<T>())).c_str(),
|
|
||||||
stringify(static_cast<typename std::underlying_type<T>::type>(t)).c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
template<typename T, typename std::enable_if_t<std::is_enum_v<strip<T>>, int> = 0>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]] std::string stringify_enum(const T& t) {
|
|
||||||
return bstringf(
|
|
||||||
"enum %s: %s",
|
|
||||||
prettify_type(std::string(type_name<T>())).c_str(),
|
|
||||||
stringify(static_cast<typename std::underlying_type_t<T>>(t)).c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
|
||||||
// Compositions of other types
|
|
||||||
//
|
|
||||||
// #ifdef __cpp_lib_expected
|
|
||||||
// template<typename E>
|
|
||||||
// [[nodiscard]] std::string stringify(const std::unexpected<E>& x) {
|
|
||||||
// return "unexpected " + stringify(x.error());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// template<typename T, typename E>
|
|
||||||
// [[nodiscard]] std::string stringify(const std::expected<T, E>& x) {
|
|
||||||
// if(x.has_value()) {
|
|
||||||
// if constexpr(std::is_void_v<T>) {
|
|
||||||
// return "expected void";
|
|
||||||
// } else {
|
|
||||||
// return "expected " + stringify(*x);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// return "unexpected " + stringify(x.error());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify(const std::optional<T>& t) {
|
|
||||||
if(t) {
|
|
||||||
return do_stringify(t.value());
|
|
||||||
} else {
|
|
||||||
return "nullopt";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline constexpr std::size_t max_container_print_items = 1000;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify_container(const T& container) {
|
|
||||||
using std::begin, std::end; // ADL
|
|
||||||
std::string str = "[";
|
|
||||||
const auto begin_it = begin(container);
|
|
||||||
std::size_t count = 0;
|
|
||||||
for(auto it = begin_it; it != end(container); it++) {
|
|
||||||
if(it != begin_it) {
|
|
||||||
str += ", ";
|
|
||||||
}
|
|
||||||
str += do_stringify(*it);
|
|
||||||
if(++count == max_container_print_items) {
|
|
||||||
str += ", ...";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str += "]";
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// I'm going to assume at least one index because is_tuple_like requires index 0 to exist
|
|
||||||
template<typename T, size_t... I>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify_tuple_like_impl(const T& t, std::index_sequence<I...>) {
|
|
||||||
return "[" + (do_stringify(std::get<0>(t)) + ... + (", " + do_stringify(std::get<I + 1>(t)))) + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string stringify_tuple_like(const T& t) {
|
|
||||||
return stringify_tuple_like_impl(t, std::make_index_sequence<std::tuple_size<T>::value - 1>{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename = void>
|
|
||||||
class has_value_type : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class has_value_type<
|
|
||||||
T,
|
|
||||||
std::void_t<typename T::value_type>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
template<typename T> inline constexpr bool is_smart_pointer =
|
|
||||||
is_specialization<T, std::unique_ptr>::value
|
|
||||||
|| is_specialization<T, std::shared_ptr>::value; // TODO: Handle weak_ptr too?
|
|
||||||
|
|
||||||
template<typename T, typename = void>
|
|
||||||
class can_basic_stringify : public std::false_type {};
|
|
||||||
template<typename T>
|
|
||||||
class can_basic_stringify<
|
|
||||||
T,
|
|
||||||
std::void_t<decltype(stringification::stringify(std::declval<T>()))>
|
|
||||||
> : public std::true_type {};
|
|
||||||
|
|
||||||
template<typename T> constexpr bool stringifiable_container();
|
|
||||||
|
|
||||||
template<typename T> inline constexpr bool stringifiable =
|
|
||||||
stringification::has_stringifier<T>::value
|
|
||||||
|| std::is_convertible_v<T, std::string_view>
|
|
||||||
|| (std::is_pointer_v<T> || std::is_function_v<T>)
|
|
||||||
|| std::is_enum_v<T>
|
|
||||||
|| (stringification::is_tuple_like<T>::value && stringifiable_container<T>())
|
|
||||||
|| (stringification::adl::is_container<T>::value && stringifiable_container<T>())
|
|
||||||
|| can_basic_stringify<T>::value
|
|
||||||
|| stringification::has_ostream_overload<T>::value
|
|
||||||
#ifdef LIBASSERT_USE_FMT
|
|
||||||
|| fmt::is_formattable<T>::value
|
|
||||||
#endif
|
|
||||||
|| stringifiable_container<T>();
|
|
||||||
|
|
||||||
template<typename T, size_t... I> constexpr bool tuple_has_stringifiable_args_core(std::index_sequence<I...>) {
|
|
||||||
return (
|
|
||||||
stringifiable<decltype(std::get<0>(std::declval<T>()))>
|
|
||||||
|| ...
|
|
||||||
|| stringifiable<decltype(std::get<I>(std::declval<T>()))>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> inline constexpr bool tuple_has_stringifiable_args =
|
|
||||||
tuple_has_stringifiable_args_core<T>(std::make_index_sequence<std::tuple_size<T>::value - 1>{});
|
|
||||||
|
|
||||||
template<typename T> constexpr bool stringifiable_container() {
|
|
||||||
// TODO: Guard against std::expected....?
|
|
||||||
if constexpr(has_value_type<T>::value) {
|
|
||||||
if constexpr(std::is_same_v<typename T::value_type, T>) { // TODO: Reconsider
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return stringifiable<typename T::value_type>;
|
|
||||||
}
|
|
||||||
} else if constexpr(std::is_array_v<typename std::remove_reference_t<T>>) { // C arrays
|
|
||||||
return stringifiable<decltype(std::declval<T>()[0])>;
|
|
||||||
} else if constexpr(stringification::is_tuple_like<T>::value) {
|
|
||||||
return tuple_has_stringifiable_args<T>;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stringification of some types can result in infinite recursion and subsequent stack-overflow. An example would be
|
|
||||||
// a container/range-like type T whose iterator's value_type is also T (such as std::filesystem::path). This is easy
|
|
||||||
// enough to detect at compile time, however, there are pathological types in the general case which would be much
|
|
||||||
// more difficult to check at compile time.
|
|
||||||
// For example, let T be a container whose iterator's value_type = std::vector<T>. This would cause infinite
|
|
||||||
// recursion via:
|
|
||||||
// do_stringify<T>
|
|
||||||
// -> stringify_container<T>
|
|
||||||
// -> do_stringify<std::vector<T>>
|
|
||||||
// -> stringify_container<std::vector<T>>
|
|
||||||
// -> do_stringify<T>
|
|
||||||
// -> ...
|
|
||||||
// Instead of compile-time checks this class and a RAII helper is used at the do_stringify<T> level to detect
|
|
||||||
// stringification recursion at runtime.
|
|
||||||
class recursion_flag {
|
|
||||||
bool the_flag = false;
|
|
||||||
public:
|
|
||||||
recursion_flag() = default;
|
|
||||||
recursion_flag(const recursion_flag&) = delete;
|
|
||||||
recursion_flag(recursion_flag&&) = delete;
|
|
||||||
recursion_flag& operator=(const recursion_flag&) = delete;
|
|
||||||
recursion_flag& operator=(recursion_flag&&) = delete;
|
|
||||||
|
|
||||||
class recursion_canary {
|
|
||||||
bool& flag_ref;
|
|
||||||
public:
|
|
||||||
recursion_canary(bool& flag) : flag_ref(flag) {}
|
|
||||||
~recursion_canary() {
|
|
||||||
flag_ref = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
recursion_canary set() {
|
|
||||||
the_flag = true;
|
|
||||||
return recursion_canary(the_flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool test() const {
|
|
||||||
return the_flag;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string do_stringify(const T& v) {
|
|
||||||
// TODO: This is overkill to do for every instantiation of do_stringify (e.g. primitive types could omit this)
|
|
||||||
thread_local recursion_flag flag;
|
|
||||||
if(flag.test()) { // pathological case detected, fall back to unknown
|
|
||||||
return stringification::stringify_unknown<T>();
|
|
||||||
}
|
|
||||||
auto canary = flag.set();
|
|
||||||
// Ordering notes
|
|
||||||
// - stringifier first
|
|
||||||
// - nullptr before string_view (char*)
|
|
||||||
// - other pointers before basic stringify
|
|
||||||
// - enum before basic stringify
|
|
||||||
// - container before basic stringify (c arrays and decay etc)
|
|
||||||
// - needs to exclude std::filesystem::path
|
|
||||||
if constexpr(stringification::has_stringifier<T>::value) {
|
|
||||||
return stringifier<strip<T>>{}.stringify(v);
|
|
||||||
} else if constexpr(std::is_same_v<T, std::nullptr_t>) {
|
|
||||||
return "nullptr";
|
|
||||||
} else if constexpr(std::is_convertible_v<T, std::string_view>) {
|
|
||||||
if constexpr(std::is_pointer_v<T>) {
|
|
||||||
if(v == nullptr) {
|
|
||||||
return "nullptr";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wnonnull"
|
|
||||||
#endif
|
|
||||||
return stringification::stringify(std::string_view(v));
|
|
||||||
#if LIBASSERT_IS_GCC
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
#endif
|
|
||||||
} else if constexpr(std::is_pointer_v<T> || std::is_function_v<T>) {
|
|
||||||
return stringification::stringify_pointer_value(reinterpret_cast<const void*>(v));
|
|
||||||
} else if constexpr(is_smart_pointer<T>) {
|
|
||||||
#ifndef LIBASSERT_NO_STRINGIFY_SMART_POINTER_OBJECTS
|
|
||||||
if(stringifiable<typename T::element_type>) {
|
|
||||||
#else
|
|
||||||
if(false) {
|
|
||||||
#endif
|
|
||||||
return stringification::stringify_smart_ptr(v);
|
|
||||||
} else {
|
|
||||||
return stringification::stringify_pointer_value(v.get());
|
|
||||||
}
|
|
||||||
} else if constexpr(std::is_enum_v<T>) {
|
|
||||||
return stringification::stringify_enum(v);
|
|
||||||
} else if constexpr(stringification::is_tuple_like<T>::value) {
|
|
||||||
if constexpr(stringifiable_container<T>()) {
|
|
||||||
return stringification::stringify_tuple_like(v);
|
|
||||||
} else {
|
|
||||||
return stringification::stringify_unknown<T>();
|
|
||||||
}
|
|
||||||
} else if constexpr(
|
|
||||||
stringification::adl::is_container<T>::value
|
|
||||||
&& !std::is_same_v<strip<T>, std::filesystem::path>
|
|
||||||
) {
|
|
||||||
if constexpr(stringifiable_container<T>()) {
|
|
||||||
return stringification::stringify_container(v);
|
|
||||||
} else {
|
|
||||||
return stringification::stringify_unknown<T>();
|
|
||||||
}
|
|
||||||
} else if constexpr(can_basic_stringify<T>::value) {
|
|
||||||
return stringification::stringify(v);
|
|
||||||
} else if constexpr(stringification::has_ostream_overload<T>::value) {
|
|
||||||
return stringification::stringify_by_ostream(v);
|
|
||||||
}
|
|
||||||
#ifdef LIBASSERT_USE_FMT
|
|
||||||
else if constexpr(fmt::is_formattable<T>::value) {
|
|
||||||
return fmt::format("{}", v);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else {
|
|
||||||
return stringification::stringify_unknown<T>();
|
|
||||||
}
|
|
||||||
// TODO std fmt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level stringify utility
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string generate_stringification(const T& v) {
|
|
||||||
if constexpr(
|
|
||||||
stringification::adl::is_container<T>::value
|
|
||||||
&& !is_string_type<T>
|
|
||||||
&& !std::is_same_v<strip<T>, std::filesystem::path>
|
|
||||||
&& stringifiable_container<T>()
|
|
||||||
) {
|
|
||||||
return prettify_type(std::string(type_name<T>())) + ": " + do_stringify(v);
|
|
||||||
} else if constexpr(stringification::is_tuple_like<T>::value && stringifiable_container<T>()) {
|
|
||||||
return prettify_type(std::string(type_name<T>())) + ": " + do_stringify(v);
|
|
||||||
} else if constexpr((std::is_pointer_v<T> && !is_string_type<T>) || is_smart_pointer<T>) {
|
|
||||||
return prettify_type(std::string(type_name<T>())) + ": " + do_stringify(v);
|
|
||||||
} else if constexpr(is_specialization<T, std::optional>::value) {
|
|
||||||
return prettify_type(std::string(type_name<T>())) + ": " + do_stringify(v);
|
|
||||||
} else {
|
|
||||||
return do_stringify(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,143 +0,0 @@
|
|||||||
#ifndef LIBASSERT_UTILITIES_HPP
|
|
||||||
#define LIBASSERT_UTILITIES_HPP
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <libassert/platform.hpp>
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Core utilities ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert {
|
|
||||||
// Lightweight helper, eventually may use C++20 std::source_location if this library no longer
|
|
||||||
// targets C++17. Note: __builtin_FUNCTION only returns the name, so __PRETTY_FUNCTION__ is
|
|
||||||
// still needed.
|
|
||||||
struct source_location {
|
|
||||||
const char* file;
|
|
||||||
//const char* function; // disabled for now due to static constexpr restrictions
|
|
||||||
int line;
|
|
||||||
constexpr source_location(
|
|
||||||
//const char* _function /*= __builtin_FUNCTION()*/,
|
|
||||||
const char* _file = __builtin_FILE(),
|
|
||||||
int _line = __builtin_LINE()
|
|
||||||
) : file(_file), /*function(_function),*/ line(_line) {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
// bootstrap with primitive implementations
|
|
||||||
LIBASSERT_EXPORT void primitive_assert_impl(
|
|
||||||
bool condition,
|
|
||||||
bool normal_assert,
|
|
||||||
const char* expression,
|
|
||||||
const char* signature,
|
|
||||||
source_location location,
|
|
||||||
const char* message = nullptr
|
|
||||||
);
|
|
||||||
|
|
||||||
[[noreturn]] LIBASSERT_EXPORT void primitive_panic_impl (
|
|
||||||
const char* signature,
|
|
||||||
source_location location,
|
|
||||||
const char* message
|
|
||||||
);
|
|
||||||
|
|
||||||
// always_false is just convenient to use here
|
|
||||||
#define LIBASSERT_PHONY_USE(E) ((void)::libassert::detail::always_false<decltype(E)>)
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define LIBASSERT_PRIMITIVE_DEBUG_ASSERT(c, ...) \
|
|
||||||
libassert::detail::primitive_assert_impl(c, false, #c, LIBASSERT_PFUNC, {} LIBASSERT_VA_ARGS(__VA_ARGS__))
|
|
||||||
#else
|
|
||||||
#define LIBASSERT_PRIMITIVE_DEBUG_ASSERT(c, ...) LIBASSERT_PHONY_USE(c)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define LIBASSERT_PRIMITIVE_PANIC(message) ::libassert::detail::primitive_panic_impl(LIBASSERT_PFUNC, {}, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Basic formatting and type tools ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string bstringf(const char* format, ...);
|
|
||||||
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
constexpr inline std::string_view substring_bounded_by(
|
|
||||||
std::string_view sig,
|
|
||||||
std::string_view l,
|
|
||||||
std::string_view r
|
|
||||||
) noexcept {
|
|
||||||
auto i = sig.find(l) + l.length();
|
|
||||||
return sig.substr(i, sig.rfind(r) - i);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
LIBASSERT_ATTR_COLD [[nodiscard]]
|
|
||||||
std::string_view type_name() noexcept {
|
|
||||||
// Cases to handle:
|
|
||||||
// gcc: constexpr std::string_view ns::type_name() [with T = int; std::string_view = std::basic_string_view<char>]
|
|
||||||
// clang: std::string_view ns::type_name() [T = int]
|
|
||||||
// msvc: class std::basic_string_view<char,struct std::char_traits<char> > __cdecl ns::type_name<int>(void)
|
|
||||||
#if LIBASSERT_IS_CLANG
|
|
||||||
return substring_bounded_by(LIBASSERT_PFUNC, "[T = ", "]");
|
|
||||||
#elif LIBASSERT_IS_GCC
|
|
||||||
return substring_bounded_by(LIBASSERT_PFUNC, "[with T = ", "; std::string_view = ");
|
|
||||||
#elif LIBASSERT_IS_MSVC
|
|
||||||
return substring_bounded_by(LIBASSERT_PFUNC, "type_name<", ">(void)");
|
|
||||||
#else
|
|
||||||
return LIBASSERT_PFUNC;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] LIBASSERT_EXPORT std::string prettify_type(std::string type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// =====================================================================================================================
|
|
||||||
// || Metaprogramming utilities ||
|
|
||||||
// =====================================================================================================================
|
|
||||||
|
|
||||||
namespace libassert::detail {
|
|
||||||
struct nothing {};
|
|
||||||
|
|
||||||
template<typename T> inline constexpr bool is_nothing = std::is_same_v<T, nothing>;
|
|
||||||
|
|
||||||
// Hack to get around static_assert(false); being evaluated before any instantiation, even under
|
|
||||||
// an if-constexpr branch
|
|
||||||
// Also used for PHONY_USE
|
|
||||||
template<typename T> inline constexpr bool always_false = false;
|
|
||||||
|
|
||||||
template<typename T> using strip = std::remove_cv_t<std::remove_reference_t<T>>;
|
|
||||||
|
|
||||||
// intentionally not stripping B
|
|
||||||
template<typename A, typename B> inline constexpr bool isa = std::is_same_v<strip<A>, B>;
|
|
||||||
|
|
||||||
// Is integral but not boolean
|
|
||||||
template<typename T> inline constexpr bool is_integral_and_not_bool = std::is_integral_v<strip<T>> && !isa<T, bool>;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline constexpr bool is_arith_not_bool_char = std::is_arithmetic_v<strip<T>> && !isa<T, bool> && !isa<T, char>;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline constexpr bool is_c_string =
|
|
||||||
isa<std::decay_t<strip<T>>, char*> // <- covers literals (i.e. const char(&)[N]) too
|
|
||||||
|| isa<std::decay_t<strip<T>>, const char*>;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline constexpr bool is_string_type =
|
|
||||||
isa<T, std::string>
|
|
||||||
|| isa<T, std::string_view>
|
|
||||||
|| is_c_string<T>;
|
|
||||||
|
|
||||||
// char(&)[20], const char(&)[20], const char(&)[]
|
|
||||||
template<typename T> inline constexpr bool is_string_literal =
|
|
||||||
std::is_lvalue_reference_v<T>
|
|
||||||
&& std::is_array_v<typename std::remove_reference_t<T>>
|
|
||||||
&& isa<typename std::remove_extent_t<typename std::remove_reference_t<T>>, char>;
|
|
||||||
|
|
||||||
template<typename T> typename std::add_lvalue_reference_t<T> decllval() noexcept;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -15,7 +15,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <libassert/assert.hpp>
|
||||||
|
|
||||||
namespace dropshell {
|
namespace dropshell {
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ int main(int argc, char* argv[]) {
|
|||||||
HAPPYEXIT("makesafecmd", std::cout<<makesafecmd(safearg(argc,argv,2))<<std::endl)
|
HAPPYEXIT("makesafecmd", std::cout<<makesafecmd(safearg(argc,argv,2))<<std::endl)
|
||||||
HAPPYEXIT("version", printversion())
|
HAPPYEXIT("version", printversion())
|
||||||
BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
|
BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2)))
|
||||||
ASSERT_MSG(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
|
ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// silently attempt to load the config file and templates.
|
// silently attempt to load the config file and templates.
|
||||||
@ -209,7 +209,7 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd == "edit" && argc < 4) {
|
if (cmd == "edit" && argc < 4) {
|
||||||
ASSERT_MSG(argc>=3, "Error: logic error!");
|
ASSERT(argc>=3, "Error: logic error!");
|
||||||
service_runner::edit_server(safearg(argc,argv,2));
|
service_runner::edit_server(safearg(argc,argv,2));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ bool server_env_manager::run_remote_template_command(const std::string &service_
|
|||||||
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
|
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
|
||||||
if (scommand.get_command_to_run().empty())
|
if (scommand.get_command_to_run().empty())
|
||||||
return false;
|
return false;
|
||||||
cMode mode = (command=="ssh") ? cMode::Interactive : cMode::Silent;
|
cMode mode = (command=="ssh") ? (cMode::Interactive | cMode::RawCommand) : cMode::Silent;
|
||||||
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
|
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
#include "config.hpp"
|
|
||||||
#include "service_runner.hpp"
|
|
||||||
#include "server_env_manager.hpp"
|
|
||||||
#include "templates.hpp"
|
|
||||||
#include "services.hpp"
|
|
||||||
#include "utils/directories.hpp"
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@ -13,6 +7,16 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <libassert/assert.hpp>
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "service_runner.hpp"
|
||||||
|
#include "server_env_manager.hpp"
|
||||||
|
#include "templates.hpp"
|
||||||
|
#include "services.hpp"
|
||||||
|
#include "utils/directories.hpp"
|
||||||
|
#include "utils/utils.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
@ -413,7 +417,7 @@ bool service_runner::interactive_ssh(const std::string & server_name, const std:
|
|||||||
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
|
std::cerr << "Error: Invalid server environment file: " << server_name << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive);
|
return execute_ssh_command(env.get_SSH_INFO(), scommand, cMode::Interactive | cMode::RawCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
void service_runner::edit_server(const std::string &server_name)
|
void service_runner::edit_server(const std::string &server_name)
|
||||||
@ -461,7 +465,7 @@ bool service_runner::edit_file(const std::string &file_path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Editing file: " << file_path << std::endl;
|
std::cout << "Editing file: " << file_path << std::endl;
|
||||||
return execute_local_command(editor_cmd, cMode::Interactive);
|
return execute_local_command(editor_cmd, cMode::Interactive | cMode::RawCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool service_runner::interactive_ssh_service()
|
bool service_runner::interactive_ssh_service()
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
#include "server_env_manager.hpp"
|
#include "server_env_manager.hpp"
|
||||||
#include "services.hpp"
|
#include "services.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "utils/assert.hpp"
|
|
||||||
#include "utils/hash.hpp"
|
#include "utils/hash.hpp"
|
||||||
|
|
||||||
namespace dropshell {
|
namespace dropshell {
|
||||||
|
@ -6,13 +6,13 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <libassert/assert.hpp>
|
||||||
|
|
||||||
#include "utils/envmanager.hpp"
|
#include "utils/envmanager.hpp"
|
||||||
#include "utils/directories.hpp"
|
#include "utils/directories.hpp"
|
||||||
#include "utils/utils.hpp"
|
#include "utils/utils.hpp"
|
||||||
#include "templates.hpp"
|
#include "templates.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "utils/assert.hpp"
|
|
||||||
|
|
||||||
namespace dropshell {
|
namespace dropshell {
|
||||||
|
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
#ifndef DROPSHELL_ASSERT_HPP
|
|
||||||
#define DROPSHELL_ASSERT_HPP
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string_view>
|
|
||||||
#include <cstdlib> // For std::exit and EXIT_FAILURE
|
|
||||||
#include <execinfo.h>
|
|
||||||
#include <cxxabi.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <memory>
|
|
||||||
#include <sstream>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace ds {
|
|
||||||
|
|
||||||
struct SourceLocation {
|
|
||||||
const char* file_name;
|
|
||||||
int line;
|
|
||||||
const char* function_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper macro to create a SourceLocation with current context
|
|
||||||
#define DS_CURRENT_LOCATION ds::SourceLocation{__FILE__, __LINE__, __func__}
|
|
||||||
|
|
||||||
inline uintptr_t get_base_address(const char* exe_path) {
|
|
||||||
// Open /proc/self/maps
|
|
||||||
FILE* maps = fopen("/proc/self/maps", "r");
|
|
||||||
if (!maps) return 0;
|
|
||||||
char line[512];
|
|
||||||
uintptr_t base_addr = 0;
|
|
||||||
while (fgets(line, sizeof(line), maps)) {
|
|
||||||
// Look for a line containing the exe path and 'r-xp'
|
|
||||||
if (strstr(line, exe_path) && strstr(line, "r-xp")) {
|
|
||||||
// Parse the start address
|
|
||||||
char* endptr = nullptr;
|
|
||||||
base_addr = strtoull(line, &endptr, 16);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(maps);
|
|
||||||
return base_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void print_stacktrace() {
|
|
||||||
constexpr int max_frames = 64;
|
|
||||||
void* addrlist[max_frames];
|
|
||||||
int addrlen = backtrace(addrlist, max_frames);
|
|
||||||
|
|
||||||
if (addrlen == 0) {
|
|
||||||
std::cerr << " <empty, possibly corrupt>\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the program name
|
|
||||||
char exe_path[1024];
|
|
||||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
|
||||||
if (len == -1) {
|
|
||||||
std::cerr << " <could not read exe path>\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
exe_path[len] = '\0';
|
|
||||||
|
|
||||||
uintptr_t base_addr = get_base_address(exe_path);
|
|
||||||
|
|
||||||
int frame_num = 1;
|
|
||||||
for (int i = 0; i < addrlen; i++) {
|
|
||||||
uintptr_t addr = (uintptr_t)addrlist[i];
|
|
||||||
uintptr_t offset = base_addr ? (addr - base_addr) : addr;
|
|
||||||
std::ostringstream cmd;
|
|
||||||
cmd << "addr2line -e " << exe_path << " -f -C 0x" << std::hex << offset;
|
|
||||||
std::unique_ptr<FILE, int(*)(FILE*)> pipe(popen(cmd.str().c_str(), "r"), pclose);
|
|
||||||
if (!pipe) {
|
|
||||||
std::cerr << " <addr2line failed>\n\n";
|
|
||||||
frame_num++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// addr2line -f prints two lines per address: function name, then file:line
|
|
||||||
char func[256] = {0};
|
|
||||||
char fileline[256] = {0};
|
|
||||||
if (fgets(func, sizeof(func), pipe.get()) && fgets(fileline, sizeof(fileline), pipe.get())) {
|
|
||||||
// Remove trailing newlines
|
|
||||||
size_t len1 = strlen(func);
|
|
||||||
if (len1 > 0 && func[len1-1] == '\n') func[len1-1] = 0;
|
|
||||||
size_t len2 = strlen(fileline);
|
|
||||||
if (len2 > 0 && fileline[len2-1] == '\n') fileline[len2-1] = 0;
|
|
||||||
// Remove argument list from function name (truncate at first '(')
|
|
||||||
char* paren = strchr(func, '(');
|
|
||||||
if (paren) *paren = 0;
|
|
||||||
std::cerr << " [" << frame_num << "] " << func << " at " << fileline << "\n\n";
|
|
||||||
}
|
|
||||||
frame_num++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] inline void assert_fail(
|
|
||||||
const char* expression,
|
|
||||||
const SourceLocation& location,
|
|
||||||
const char* message = nullptr) {
|
|
||||||
|
|
||||||
std::cerr << "\033[1;31mAssertion failed!\033[0m\n"
|
|
||||||
<< "Expression: \033[1;33m" << expression << "\033[0m\n"
|
|
||||||
<< "Location: \033[1;36m" << location.file_name << ":"
|
|
||||||
<< location.line << "\033[0m\n"
|
|
||||||
<< "Function: \033[1;36m" << location.function_name << "\033[0m\n";
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
std::cerr << "Message: \033[1;37m" << message << "\033[0m\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cerr << std::endl;
|
|
||||||
|
|
||||||
std::cerr << "Stack trace:" << std::endl;
|
|
||||||
print_stacktrace();
|
|
||||||
std::cerr << "----------------------------------------" << std::endl;
|
|
||||||
|
|
||||||
// Exit the program without creating a core dump
|
|
||||||
std::exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ds
|
|
||||||
|
|
||||||
// Standard assertion
|
|
||||||
#define ASSERT(condition) \
|
|
||||||
do { \
|
|
||||||
if (!(condition)) { \
|
|
||||||
ds::assert_fail(#condition, DS_CURRENT_LOCATION); \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
// Assertion with custom message
|
|
||||||
#define ASSERT_MSG(condition, message) \
|
|
||||||
do { \
|
|
||||||
if (!(condition)) { \
|
|
||||||
ds::assert_fail(#condition, DS_CURRENT_LOCATION, message); \
|
|
||||||
} \
|
|
||||||
} while (false)
|
|
||||||
|
|
||||||
#endif // DROPSHELL_ASSERT_HPP
|
|
@ -1,8 +1,3 @@
|
|||||||
#include "execute.hpp"
|
|
||||||
#include "assert.hpp"
|
|
||||||
#include "contrib/base64.hpp"
|
|
||||||
#include "utils/utils.hpp"
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -11,6 +6,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <libassert/assert.hpp>
|
||||||
|
|
||||||
|
#include "execute.hpp"
|
||||||
|
#include "contrib/base64.hpp"
|
||||||
|
#include "utils/utils.hpp"
|
||||||
|
|
||||||
|
|
||||||
bool EXITSTATUSCHECK(int ret) {
|
bool EXITSTATUSCHECK(int ret) {
|
||||||
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
|
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
|
||||||
@ -55,9 +56,9 @@ bool execute_local_command_interactive(const sCommand &command, bool silent)
|
|||||||
|
|
||||||
bool execute_local_command_and_capture_output(const sCommand& command, std::string * output, cMode mode)
|
bool execute_local_command_and_capture_output(const sCommand& command, std::string * output, cMode mode)
|
||||||
{
|
{
|
||||||
ASSERT_MSG(output != nullptr, "Output string must be provided");
|
ASSERT(output != nullptr, "Output string must be provided");
|
||||||
ASSERT_MSG(is_raw(mode), "Capture output mode requires raw command mode");
|
ASSERT(is_raw(mode), "Capture output mode requires raw command mode");
|
||||||
ASSERT_MSG(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
|
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
|
||||||
if (command.get_command_to_run().empty())
|
if (command.get_command_to_run().empty())
|
||||||
return false;
|
return false;
|
||||||
cStyle style = getStyle(mode);
|
cStyle style = getStyle(mode);
|
||||||
@ -79,18 +80,18 @@ bool execute_local_command_and_capture_output(const sCommand& command, std::stri
|
|||||||
bool execute_local_command(const sCommand & command, cMode mode, std::string * output /* = nullptr */)
|
bool execute_local_command(const sCommand & command, cMode mode, std::string * output /* = nullptr */)
|
||||||
{
|
{
|
||||||
if (hasFlag(mode, cMode::Interactive)) {
|
if (hasFlag(mode, cMode::Interactive)) {
|
||||||
ASSERT_MSG(! hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
|
ASSERT(! hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
|
||||||
ASSERT_MSG(output == nullptr, "Interactive mode and an output string cannot be used together");
|
ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together");
|
||||||
ASSERT_MSG(is_raw(mode), "Interactive mode requires raw command mode");
|
ASSERT(is_raw(mode), "Interactive mode requires raw command mode");
|
||||||
|
|
||||||
return execute_local_command_interactive(command, hasFlag(mode, cMode::Silent));
|
return execute_local_command_interactive(command, hasFlag(mode, cMode::Silent));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasFlag(mode, cMode::CaptureOutput)) {
|
if (hasFlag(mode, cMode::CaptureOutput)) {
|
||||||
ASSERT_MSG(output != nullptr, "Capture output mode requires an output string to be provided");
|
ASSERT(output != nullptr, "Capture output mode requires an output string to be provided");
|
||||||
ASSERT_MSG(is_raw(mode), "Capture output mode requires raw command mode");
|
ASSERT(is_raw(mode), "Capture output mode requires raw command mode");
|
||||||
ASSERT_MSG(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
|
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
|
||||||
|
|
||||||
return execute_local_command_and_capture_output(command, output, mode);
|
return execute_local_command_and_capture_output(command, output, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,8 +114,8 @@ bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &command, cMod
|
|||||||
if (command.get_command_to_run().empty())
|
if (command.get_command_to_run().empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
ASSERT_MSG(!(hasFlag(mode, cMode::Interactive) && !is_raw(mode)), "Interactive mode requires raw command mode");
|
ASSERT(!(hasFlag(mode, cMode::Interactive) && !is_raw(mode)), "Interactive mode requires raw command mode");
|
||||||
ASSERT_MSG(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
|
ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
|
||||||
|
|
||||||
std::stringstream ssh_cmd;
|
std::stringstream ssh_cmd;
|
||||||
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
|
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
|
||||||
@ -126,9 +127,8 @@ bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &command, cMod
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::string raw_cmd = command.construct_cmd(cStyle::Raw);
|
std::string raw_cmd = command.construct_cmd(cStyle::Raw);
|
||||||
ASSERT_MSG(raw_cmd.find("'") == std::string::npos, "Raw command must not contain single quotes");
|
ASSERT(raw_cmd.find("'") == std::string::npos, "Raw command must not contain single quotes");
|
||||||
ASSERT_MSG(raw_cmd.find("\"") == std::string::npos, "Raw command must not contain double quotes");
|
cmdstr = "bash -c "+ halfquote(raw_cmd);
|
||||||
cmdstr = halfquote("bash -c " + quote(raw_cmd));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sCommand ssh_command(ssh_cmd.str() + " " + cmdstr);
|
sCommand ssh_command(ssh_cmd.str() + " " + cmdstr);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user