// ====================================================================== // == DO NOT MODIFY THIS FILE BY HAND - IT IS AUTO GENERATED BY CMAKE! == // ====================================================================== #pragma once #define ZEROERR_VERSION_MAJOR 0 #define ZEROERR_VERSION_MINOR 3 #define ZEROERR_VERSION_PATCH 0 #define ZEROERR_VERSION \ (ZEROERR_VERSION_MAJOR * 10000 + ZEROERR_VERSION_MINOR * 100 + ZEROERR_VERSION_PATCH) #define ZEROERR_STR(x) #x #define ZEROERR_VERSION_STR_BUILDER(a, b, c) ZEROERR_STR(a) "." ZEROERR_STR(b) "." ZEROERR_STR(c) #define ZEROERR_VERSION_STR \ ZEROERR_VERSION_STR_BUILDER(ZEROERR_VERSION_MAJOR, ZEROERR_VERSION_MINOR, ZEROERR_VERSION_PATCH) // If you just wish to use the color without dynamic // enable or disable it, you can uncomment the following line // #define ZEROERR_ALWAYS_COLORFUL // #define ZEROERR_DISABLE_COLORFUL // If you wish to use the whole library without thread safety, uncomment the following line // #define ZEROERR_NO_THREAD_SAFE // If you wish to disable auto initialization of the system // #define ZEROERR_DISABLE_AUTO_INIT // If you didn't wish override operator<< for ostream, we can disable it // #define ZEROERR_DISABLE_OSTREAM_OVERRIDE // If you wish to disable AND, OR macro // #define ZEROERR_DISABLE_COMPLEX_AND_OR // If you wish ot disable BDD style macros // #define ZEROERR_DISABLE_BDD // Detect C++ standard with a cross-platform way #ifdef _MSC_VER #define ZEROERR_CPLUSPLUS _MSVC_LANG #else #define ZEROERR_CPLUSPLUS __cplusplus #endif #if ZEROERR_CPLUSPLUS >= 202300L #define ZEROERR_CXX_STANDARD 23 #elif ZEROERR_CPLUSPLUS >= 202002L #define ZEROERR_CXX_STANDARD 20 #elif ZEROERR_CPLUSPLUS >= 201703L #define ZEROERR_CXX_STANDARD 17 #elif ZEROERR_CPLUSPLUS >= 201402L #define ZEROERR_CXX_STANDARD 14 #elif ZEROERR_CPLUSPLUS >= 201103L #define ZEROERR_CXX_STANDARD 11 #else #error "Unsupported C++ standard detected. ZeroErr requires C++11 or later." #endif #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) #define ZEROERR_OS_UNIX #if defined(__linux__) #define ZEROERR_OS_LINUX #endif #elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) #define ZEROERR_OS_WINDOWS #else #define ZEROERR_OS_UNKNOWN #endif #if defined(NDEBUG) && !defined(ZEROERR_ALWAYS_ASSERT) // FIXME: we should safely remove the assert in IF statement // #define ZEROERR_NO_ASSERT #endif // This is used for generating a unique name based on the file name and line number #define ZEROERR_CAT_IMPL(s1, s2) s1##s2 #define ZEROERR_CAT(x, s) ZEROERR_CAT_IMPL(x, s) // The following macros are used to check the arguments is empty or not // from: https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/ #define ZEROERR_ARG16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15 #define ZEROERR_HAS_COMMA(...) \ ZEROERR_ARG16(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0) #define ZEROERR_TRIGGER_PARENTHESIS_(...) , #define ZEROERR_ISEMPTY(...) \ _ZEROERR_ISEMPTY(/* test if there is just one argument, eventually an empty \ one */ \ ZEROERR_HAS_COMMA(__VA_ARGS__), /* test if ZEROERR_TRIGGER_PARENTHESIS_ \ together with the argument adds a comma */ \ ZEROERR_HAS_COMMA(ZEROERR_TRIGGER_PARENTHESIS_ \ __VA_ARGS__), /* test if the argument together with \ a parenthesis adds a comma */ \ ZEROERR_HAS_COMMA(__VA_ARGS__( \ /*empty*/)), /* test if placing it between ZEROERR_TRIGGER_PARENTHESIS_ \ and the parenthesis adds a comma */ \ ZEROERR_HAS_COMMA(ZEROERR_TRIGGER_PARENTHESIS_ __VA_ARGS__(/*empty*/))) #define ZEROERR_PASTE5(_0, _1, _2, _3, _4) _0##_1##_2##_3##_4 #define _ZEROERR_ISEMPTY(_0, _1, _2, _3) \ ZEROERR_HAS_COMMA(ZEROERR_PASTE5(_IS_EMPTY_CASE_, _0, _1, _2, _3)) #define _IS_EMPTY_CASE_0001 , // The counter is used to generate a unique name #ifdef __COUNTER__ #define ZEROERR_NAMEGEN(x) ZEROERR_CAT(x, __COUNTER__) #else // __COUNTER__ #define ZEROERR_NAMEGEN(x) ZEROERR_CAT(x, __LINE__) #endif // __COUNTER__ #ifdef ZEROERR_OS_LINUX #define ZEROERR_PERF #endif #ifdef ZEROERR_DISABLE_ASSERTS_RETURN_VALUES #define ZEROERR_FUNC_SCOPE_BEGIN do #define ZEROERR_FUNC_SCOPE_END while (0) #define ZEROERR_FUNC_SCOPE_RET(v) (void)0 #else #define ZEROERR_FUNC_SCOPE_BEGIN [&] #define ZEROERR_FUNC_SCOPE_END () #define ZEROERR_FUNC_SCOPE_RET(v) return v #endif #ifndef ZEROERR_NO_SHORT_LOG_MACRO #define ZEROERR_USE_SHORT_LOG_MACRO #endif #define ZEROERR_EXPAND(x) x // ================================================================================================= // == COMPILER Detector ============================================================================ // ================================================================================================= #define ZEROERR_COMPILER(MAJOR, MINOR, PATCH) ((MAJOR) * 10000000 + (MINOR) * 100000 + (PATCH)) // GCC/Clang and GCC/MSVC are mutually exclusive, but Clang/MSVC are not because of clang-cl... #if defined(_MSC_VER) && defined(_MSC_FULL_VER) #if _MSC_VER == _MSC_FULL_VER / 10000 #define ZEROERR_MSVC ZEROERR_COMPILER(_MSC_VER / 100, _MSC_VER % 100, _MSC_FULL_VER % 10000) #else // MSVC #define ZEROERR_MSVC \ ZEROERR_COMPILER(_MSC_VER / 100, (_MSC_FULL_VER / 100000) % 100, _MSC_FULL_VER % 100000) #endif // MSVC #endif // MSVC #if defined(__clang__) && defined(__clang_minor__) && defined(__clang_patchlevel__) #define ZEROERR_CLANG ZEROERR_COMPILER(__clang_major__, __clang_minor__, __clang_patchlevel__) #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && \ !defined(__INTEL_COMPILER) #define ZEROERR_GCC ZEROERR_COMPILER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) #endif // GCC #if defined(__INTEL_COMPILER) #define ZEROERR_ICC ZEROERR_COMPILER(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif // ICC #ifndef ZEROERR_MSVC #define ZEROERR_MSVC 0 #endif // ZEROERR_MSVC #ifndef ZEROERR_CLANG #define ZEROERR_CLANG 0 #endif // ZEROERR_CLANG #ifndef ZEROERR_GCC #define ZEROERR_GCC 0 #endif // ZEROERR_GCC #ifndef ZEROERR_ICC #define ZEROERR_ICC 0 #endif // ZEROERR_ICC // ================================================================================================= // == COMPILER WARNINGS HELPERS ==================================================================== // ================================================================================================= #if ZEROERR_CLANG && !ZEROERR_ICC #define ZEROERR_PRAGMA_TO_STR(x) _Pragma(#x) #define ZEROERR_CLANG_SUPPRESS_WARNING_PUSH _Pragma("clang diagnostic push") #define ZEROERR_CLANG_SUPPRESS_WARNING(w) ZEROERR_PRAGMA_TO_STR(clang diagnostic ignored w) #define ZEROERR_CLANG_SUPPRESS_WARNING_POP _Pragma("clang diagnostic pop") #define ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) \ ZEROERR_CLANG_SUPPRESS_WARNING_PUSH ZEROERR_CLANG_SUPPRESS_WARNING(w) #else // ZEROERR_CLANG #define ZEROERR_CLANG_SUPPRESS_WARNING_PUSH #define ZEROERR_CLANG_SUPPRESS_WARNING(w) #define ZEROERR_CLANG_SUPPRESS_WARNING_POP #define ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH(w) #endif // ZEROERR_CLANG #if ZEROERR_GCC #define ZEROERR_PRAGMA_TO_STR(x) _Pragma(#x) #define ZEROERR_GCC_SUPPRESS_WARNING_PUSH _Pragma("GCC diagnostic push") #define ZEROERR_GCC_SUPPRESS_WARNING(w) ZEROERR_PRAGMA_TO_STR(GCC diagnostic ignored w) #define ZEROERR_GCC_SUPPRESS_WARNING_POP _Pragma("GCC diagnostic pop") #define ZEROERR_GCC_SUPPRESS_WARNING_WITH_PUSH(w) \ ZEROERR_GCC_SUPPRESS_WARNING_PUSH ZEROERR_GCC_SUPPRESS_WARNING(w) #else // ZEROERR_GCC #define ZEROERR_GCC_SUPPRESS_WARNING_PUSH #define ZEROERR_GCC_SUPPRESS_WARNING(w) #define ZEROERR_GCC_SUPPRESS_WARNING_POP #define ZEROERR_GCC_SUPPRESS_WARNING_WITH_PUSH(w) #endif // ZEROERR_GCC #if ZEROERR_MSVC #define ZEROERR_MSVC_SUPPRESS_WARNING_PUSH __pragma(warning(push)) #define ZEROERR_MSVC_SUPPRESS_WARNING(w) __pragma(warning(disable : w)) #define ZEROERR_MSVC_SUPPRESS_WARNING_POP __pragma(warning(pop)) #define ZEROERR_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) \ ZEROERR_MSVC_SUPPRESS_WARNING_PUSH ZEROERR_MSVC_SUPPRESS_WARNING(w) #else // ZEROERR_MSVC #define ZEROERR_MSVC_SUPPRESS_WARNING_PUSH #define ZEROERR_MSVC_SUPPRESS_WARNING(w) #define ZEROERR_MSVC_SUPPRESS_WARNING_POP #define ZEROERR_MSVC_SUPPRESS_WARNING_WITH_PUSH(w) #endif // ZEROERR_MSVC // ================================================================================================= // == COMPILER WARNINGS ============================================================================ // ================================================================================================= // both the header and the implementation suppress all of these, // so it only makes sense to aggregate them like so #define ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH \ ZEROERR_CLANG_SUPPRESS_WARNING_PUSH \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wunknown-pragmas") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wweak-vtables") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wpadded") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wmissing-prototypes") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") \ \ ZEROERR_GCC_SUPPRESS_WARNING_PUSH \ ZEROERR_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wpragmas") \ ZEROERR_GCC_SUPPRESS_WARNING("-Weffc++") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wstrict-overflow") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wstrict-aliasing") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wmissing-declarations") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wuseless-cast") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wnoexcept") \ \ ZEROERR_MSVC_SUPPRESS_WARNING_PUSH \ /* these 4 also disabled globally via cmake: */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4514) /* unreferenced inline function has been removed */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4571) /* SEH related */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4710) /* function not inlined */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4711) /* function selected for inline expansion*/ \ /* common ones */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4616) /* invalid compiler warning */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4619) /* invalid compiler warning */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4996) /* The compiler encountered a deprecated declaration */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4706) /* assignment within conditional expression */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4512) /* 'class' : assignment operator could not be generated */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4127) /* conditional expression is constant */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4640) /* construction of local static object not thread-safe */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5264) /* 'variable-name': 'const' variable is not used */ \ /* static analysis */ \ ZEROERR_MSVC_SUPPRESS_WARNING(26439) /* Function may not throw. Declare it 'noexcept' */ \ ZEROERR_MSVC_SUPPRESS_WARNING(26495) /* Always initialize a member variable */ \ ZEROERR_MSVC_SUPPRESS_WARNING(26451) /* Arithmetic overflow ... */ \ ZEROERR_MSVC_SUPPRESS_WARNING(26444) /* Avoid unnamed objects with custom ctor and dtor... */ \ ZEROERR_MSVC_SUPPRESS_WARNING(26812) /* Prefer 'enum class' over 'enum' */ #define ZEROERR_SUPPRESS_COMMON_WARNINGS_POP \ ZEROERR_CLANG_SUPPRESS_WARNING_POP \ ZEROERR_GCC_SUPPRESS_WARNING_POP \ ZEROERR_MSVC_SUPPRESS_WARNING_POP #define ZEROERR_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN \ ZEROERR_MSVC_SUPPRESS_WARNING_PUSH \ ZEROERR_MSVC_SUPPRESS_WARNING(4548) /* before comma no effect; expected side - effect */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4265) /* virtual functions, but destructor is not virtual */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4986) /* exception specification does not match previous */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4350) /* 'member1' called instead of 'member2' */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4668) /* not defined as a preprocessor macro */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4365) /* signed/unsigned mismatch */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4774) /* format string not a string literal */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4820) /* padding */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4625) /* copy constructor was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4626) /* assignment operator was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5027) /* move assignment operator implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5026) /* move constructor was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4623) /* default constructor was implicitly deleted */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5039) /* pointer to pot. throwing function passed to extern C */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5045) /* Spectre mitigation for memory load */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5105) /* macro producing 'defined' has undefined behavior */ \ ZEROERR_MSVC_SUPPRESS_WARNING(4738) /* storing float result in memory, loss of performance */ \ ZEROERR_MSVC_SUPPRESS_WARNING(5262) /* implicit fall-through */ #define ZEROERR_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END ZEROERR_MSVC_SUPPRESS_WARNING_POP #define ZEROERR_SUPPRESS_VARIADIC_MACRO \ ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments") #define ZEROERR_SUPPRESS_VARIADIC_MACRO_POP ZEROERR_CLANG_SUPPRESS_WARNING_POP #define ZEROERR_SUPPRESS_COMPARE \ ZEROERR_CLANG_SUPPRESS_WARNING_PUSH \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wsign-conversion") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wsign-compare") \ ZEROERR_CLANG_SUPPRESS_WARNING("-Wgnu-zero-variadic-macro-arguments") \ ZEROERR_GCC_SUPPRESS_WARNING_PUSH \ ZEROERR_GCC_SUPPRESS_WARNING("-Wsign-conversion") \ ZEROERR_GCC_SUPPRESS_WARNING("-Wsign-compare") \ ZEROERR_MSVC_SUPPRESS_WARNING_PUSH \ ZEROERR_MSVC_SUPPRESS_WARNING(4388) \ ZEROERR_MSVC_SUPPRESS_WARNING(4389) \ ZEROERR_MSVC_SUPPRESS_WARNING(4018) #define ZEROERR_SUPPRESS_COMPARE_POP \ ZEROERR_CLANG_SUPPRESS_WARNING_POP ZEROERR_GCC_SUPPRESS_WARNING_POP \ ZEROERR_MSVC_SUPPRESS_WARNING_POP /** * Macro to suppress unused variable/parameter warnings * * This macro can be used to mark variables or parameters as intentionally unused * while maintaining cross-compiler compatibility. It handles different compiler-specific * attributes and warning suppressions: * * - For Clang/GCC: Uses __attribute__((unused)) * - For LCLINT: Uses @unused@ comment annotation * - For MSVC: Suppresses warning C4100 (unreferenced formal parameter) * - For other compilers: No special handling * * Usage example: * void foo(ZEROERR_UNUSED(int x)) { * // x is marked as intentionally unused * } */ #if ZEROERR_CLANG || ZEROERR_GCC #define ZEROERR_UNUSED(x) x __attribute__((unused)) #elif defined(__LCLINT__) #define ZEROERR_UNUSED(x) /*@unused@*/ x #elif ZEROERR_MSVC #define ZEROERR_UNUSED(x) \ ZEROERR_MSVC_SUPPRESS_WARNING_WITH_PUSH(4100) x ZEROERR_MSVC_SUPPRESS_WARNING_POP #else #define ZEROERR_UNUSED(x) x #endif #pragma once namespace zeroerr { #ifdef ZEROERR_ALWAYS_COLORFUL constexpr const char* Reset = "\x1b[0m"; constexpr const char* Bright = "\x1b[1m"; constexpr const char* Dim = "\x1b[2m"; constexpr const char* Underscore = "\x1b[4m"; constexpr const char* Blink = "\x1b[5m"; constexpr const char* Reverse = "\x1b[7m"; constexpr const char* Hidden = "\x1b[8m"; constexpr const char* FgBlack = "\x1b[30m"; constexpr const char* FgRed = "\x1b[31m"; constexpr const char* FgGreen = "\x1b[32m"; constexpr const char* FgYellow = "\x1b[33m"; constexpr const char* FgBlue = "\x1b[34m"; constexpr const char* FgMagenta = "\x1b[35m"; constexpr const char* FgCyan = "\x1b[36m"; constexpr const char* FgWhite = "\x1b[37m"; constexpr const char* BgBlack = "\x1b[40m"; constexpr const char* BgRed = "\x1b[41m"; constexpr const char* BgGreen = "\x1b[42m"; constexpr const char* BgYellow = "\x1b[43m"; constexpr const char* BgBlue = "\x1b[44m"; constexpr const char* BgMagenta = "\x1b[45m"; constexpr const char* BgCyan = "\x1b[46m"; constexpr const char* BgWhite = "\x1b[47m"; #elif defined(ZEROERR_DISABLE_COLORFUL) constexpr const char* Reset = ""; constexpr const char* Bright = ""; constexpr const char* Dim = ""; constexpr const char* Underscore = ""; constexpr const char* Blink = ""; constexpr const char* Reverse = ""; constexpr const char* Hidden = ""; constexpr const char* FgBlack = ""; constexpr const char* FgRed = ""; constexpr const char* FgGreen = ""; constexpr const char* FgYellow = ""; constexpr const char* FgBlue = ""; constexpr const char* FgMagenta = ""; constexpr const char* FgCyan = ""; constexpr const char* FgWhite = ""; constexpr const char* BgBlack = ""; constexpr const char* BgRed = ""; constexpr const char* BgGreen = ""; constexpr const char* BgYellow = ""; constexpr const char* BgBlue = ""; constexpr const char* BgMagenta = ""; constexpr const char* BgCyan = ""; constexpr const char* BgWhite = ""; #else extern const char* Reset; extern const char* Bright; extern const char* Dim; extern const char* Underscore; extern const char* Blink; extern const char* Reverse; extern const char* Hidden; extern const char* FgBlack; extern const char* FgRed; extern const char* FgGreen; extern const char* FgYellow; extern const char* FgBlue; extern const char* FgMagenta; extern const char* FgCyan; extern const char* FgWhite; extern const char* BgBlack; extern const char* BgRed; extern const char* BgGreen; extern const char* BgYellow; extern const char* BgBlue; extern const char* BgMagenta; extern const char* BgCyan; extern const char* BgWhite; /** * @brief Global function to disable colorful output. */ extern void disableColorOutput(); /** * @brief Global function to enable colorful output. */ extern void enableColorOutput(); #endif } // namespace zeroerr #pragma once namespace zeroerr { enum OutputStream { STDOUT, STDERR }; extern bool isTerminalOutput(OutputStream stream); struct TerminalSize { int width; int height; }; TerminalSize getTerminalSize(); } // namespace zeroerr /* Copyright (c) 2011-2021, Scott Tsai * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef DEBUG_BREAK_H #define DEBUG_BREAK_H #ifdef _MSC_VER #define debug_break __debugbreak #else #ifdef __cplusplus extern "C" { #endif #define DEBUG_BREAK_USE_TRAP_INSTRUCTION 1 #define DEBUG_BREAK_USE_BULTIN_TRAP 2 #define DEBUG_BREAK_USE_SIGTRAP 3 #if defined(__i386__) || defined(__x86_64__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __inline__ static void trap_instruction(void) { __asm__ volatile("int $0x03"); } #elif defined(__thumb__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION /* FIXME: handle __THUMB_INTERWORK__ */ __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'arm-linux-tdep.c' in GDB source. * Both instruction sequences below work. */ #if 1 /* 'eabi_linux_thumb_le_breakpoint' */ __asm__ volatile(".inst 0xde01"); #else /* 'eabi_linux_thumb2_le_breakpoint' */ __asm__ volatile(".inst.w 0xf7f0a000"); #endif /* Known problem: * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. * 'step' would keep getting stuck on the same instruction. * * Workaround: use the new GDB commands 'debugbreak-step' and * 'debugbreak-continue' that become available * after you source the script from GDB: * * $ gdb -x debugbreak-gdb.py <... USUAL ARGUMENTS ...> * * 'debugbreak-step' would jump over the breakpoint instruction with * roughly equivalent of: * (gdb) set $instruction_len = 2 * (gdb) tbreak *($pc + $instruction_len) * (gdb) jump *($pc + $instruction_len) */ } #elif defined(__arm__) && !defined(__thumb__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'arm-linux-tdep.c' in GDB source, * 'eabi_linux_arm_le_breakpoint' */ __asm__ volatile(".inst 0xe7f001f0"); /* Known problem: * Same problem and workaround as Thumb mode */ } #elif defined(__aarch64__) && defined(__APPLE__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_BULTIN_DEBUGTRAP #elif defined(__aarch64__) #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'aarch64-tdep.c' in GDB source, * 'aarch64_default_breakpoint' */ __asm__ volatile(".inst 0xd4200000"); } #elif defined(__powerpc__) /* PPC 32 or 64-bit, big or little endian */ #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'rs6000-tdep.c' in GDB source, * 'rs6000_breakpoint' */ __asm__ volatile(".4byte 0x7d821008"); /* Known problem: * After a breakpoint hit, can't 'stepi', 'step', or 'continue' in GDB. * 'step' stuck on the same instruction ("twge r2,r2"). * * The workaround is the same as ARM Thumb mode: use debugbreak-gdb.py * or manually jump over the instruction. */ } #elif defined(__riscv) /* RISC-V 32 or 64-bit, whether the "C" extension * for compressed, 16-bit instructions are supported or not */ #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void trap_instruction(void) { /* See 'riscv-tdep.c' in GDB source, * 'riscv_sw_breakpoint_from_kind' */ __asm__ volatile(".4byte 0x00100073"); } #else #define DEBUG_BREAK_IMPL DEBUG_BREAK_USE_SIGTRAP #endif #ifndef DEBUG_BREAK_IMPL #error "debugbreak.h is not supported on this target" #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_TRAP_INSTRUCTION __attribute__((always_inline)) __inline__ static void debug_break(void) { trap_instruction(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_DEBUGTRAP __attribute__((always_inline)) __inline__ static void debug_break(void) { __builtin_debugtrap(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_BULTIN_TRAP __attribute__((always_inline)) __inline__ static void debug_break(void) { __builtin_trap(); } #elif DEBUG_BREAK_IMPL == DEBUG_BREAK_USE_SIGTRAP #include <signal.h> __attribute__((always_inline)) __inline__ static void debug_break(void) { raise(SIGTRAP); } #else #error "invalid DEBUG_BREAK_IMPL value" #endif #ifdef __cplusplus } #endif #endif /* ifdef _MSC_VER */ // Here is for checking the debugger is running #include <fstream> #ifdef IS_DEBUGGER_ACTIVE __attribute__((always_inline)) __inline__ static bool isDebuggerActive() { return IS_DEBUGGER_ACTIVE(); } #else // IS_DEBUGGER_ACTIVE #ifdef __linux__ class ErrnoGuard { public: ErrnoGuard() : m_oldErrno(errno) {} ~ErrnoGuard() { errno = m_oldErrno; } private: int m_oldErrno; }; // See the comments in Catch2 for the reasoning behind this implementation: // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 __attribute__((always_inline)) __inline__ static bool isDebuggerActive() { ErrnoGuard guard; std::ifstream in("/proc/self/status"); for (std::string line; std::getline(in, line);) { static const int PREFIX_LEN = 11; if (line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; } } return false; } #elif defined(__APPLE__) #include <sys/sysctl.h> #include <unistd.h> #include <iostream> // The following function is taken directly from the following technical note: // https://developer.apple.com/library/archive/qa/qa1361/_index.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). __attribute__((always_inline)) __inline__ static bool isDebuggerActive() { int mib[4]; kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if (sysctl(mib, (sizeof(mib) / sizeof(*mib)), &info, &size, 0, 0) != 0) { std::cerr << "\nCall to sysctl failed - unable to determine if debugger is active **\n"; return false; } // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); } #elif defined(__MINGW32__) || defined(__MINGW64__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); __attribute__((always_inline)) __inline__ static bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); inline __forceinline static bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } #else __attribute__((always_inline)) __inline__ static bool isDebuggerActive() { return false; } #endif #endif // IS_DEBUGGER_ACTIVE #endif /* ifndef DEBUG_BREAK_H */ #pragma once /** * @brief Thread safety support * This header provides thread-safe support for zeroerr. * * It defines macros for mutexes, locks, and atomic operations. * The macros are conditionally defined based on the ZEROERR_NO_THREAD_SAFE flag. */ #ifdef ZEROERR_NO_THREAD_SAFE #define ZEROERR_MUTEX(x) #define ZEROERR_LOCK(x) #define ZEROERR_ATOMIC(x) x #define ZEROERR_LOAD(x) x #else #define ZEROERR_MUTEX(x) static std::mutex x; #define ZEROERR_LOCK(x) std::lock_guard<std::mutex> lock(x); #define ZEROERR_ATOMIC(x) std::atomic<x> #define ZEROERR_LOAD(x) x.load() #include <atomic> #include <mutex> #endif #pragma once #include <ostream> #include <sstream> #include <string> #include <tuple> // this should be removed #include <type_traits> #include <complex> #include <memory> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief rank is a helper class for Printer to define the priority of overloaded functions. * @tparam N the priority of the rule. 0 is the lowest priority. The maximum priority is max_rank. * * You can define a rule by adding it as a function parameter with rank<N> where N is the priority. * For example: * template<typename T> * void Foo(T v, rank<0>); // lowest priority * void Foo(int v, rank<1>); // higher priority * * Even though in the first rule, type T can be an int, the second function will still be called due * to the priority. */ template <unsigned N> struct rank : rank<N - 1> {}; template <> struct rank<0> {}; namespace detail { // C++11 void_t template <typename... Ts> using void_t = void; // Type dependent "true"/"false". // Useful for SFINAE and static_asserts where we need a type dependent // expression that happens to be constant. template <typename T> struct always_false { static std::false_type value; }; template <typename T> struct always_true { static std::true_type value; }; // Generate sequence of integers from 0 to N-1 // Usage: detail::gen_seq<N> then use <size_t... I> to match it template <unsigned...> struct seq {}; template <unsigned N, unsigned... Is> struct gen_seq : gen_seq<N - 1, N - 1, Is...> {}; template <unsigned... Is> struct gen_seq<0, Is...> : seq<Is...> {}; // Some utility structs to check template specialization 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 {}; // Check if a type is stream writable, i.e., std::cout << foo; // Usage: is_streamable<std::ostream, int>::value template <typename S, typename T> using has_stream_operator = void_t<decltype(std::declval<S&>() << std::declval<T>())>; template <typename S, typename T, typename = void> struct is_streamable : std::false_type {}; template <typename S, typename T> struct is_streamable<S, T, has_stream_operator<S, T>> : std::true_type {}; // Check if a type is a container type // Usage: is_container<std::vector<int>>::value template <typename T> using has_begin_end = void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>; template <typename T, typename = void> struct is_container : std::false_type {}; template <typename T> struct is_container<T, has_begin_end<T>> : std::true_type {}; template <typename T> using has_begin_end_find_insert = void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end()), decltype(std::declval<T>().find(std::declval<typename T::key_type>())), decltype(std::declval<T>().insert(std::declval<typename T::value_type>()))>; template <typename T, typename = void> struct is_associative_container : std::false_type {}; template <typename T> struct is_associative_container<T, has_begin_end_find_insert<T>> : std::true_type {}; #if ZEROERR_CXX_STANDARD >= 17 #define ZEROERR_STRING_VIEW std::is_same<T, std::string_view>::value #else #define ZEROERR_STRING_VIEW 0 #endif // Check if a type is a string type template <class T> struct is_string : std::integral_constant<bool, std::is_same<T, std::string>::value || std::is_same<T, const char*>::value || ZEROERR_STRING_VIEW> { }; // Check if a type can use arr[0] like an array template <typename T, typename = void> struct is_array : std::false_type {}; template <typename T> struct is_array<T, void_t<decltype(std::declval<T>()[0])>> : std::true_type {}; template <typename T, typename = void> struct is_modifiable : std::false_type {}; template <typename T> struct is_modifiable<T, void_t<decltype( // Iterable T().begin(), T().end(), T().size(), // Values are mutable // This rejects associative containers, for example // *T().begin() = std::declval<value_type_t<T>>(), // Can insert and erase elements T().insert(T().end(), std::declval<typename T::value_type>()), T().erase(T().begin()), (void)0)>> : std::true_type {}; // Check if a type has the element type as std::pair template <typename T> using has_pair_type = void_t<typename T::value_type, decltype(std::declval<typename T::value_type>().first), decltype(std::declval<typename T::value_type>().second)>; template <typename T, typename = void> struct ele_type_is_pair : std::false_type {}; template <typename T> struct ele_type_is_pair<T, has_pair_type<T>> : std::true_type {}; template <typename T, typename V = void> struct to_store_type { using type = T; }; template <> struct to_store_type<const char*> { using type = std::string; }; template <> struct to_store_type<const char (&)[]> { using type = std::string; }; template <typename T> using is_not_array = typename std::enable_if<!std::is_array<T>::value>::type; template <typename T> struct to_store_type<T&, is_not_array<T>> { using type = T; }; template <typename T> struct to_store_type<T&&> { using type = T; }; template <typename T> using to_store_type_t = typename to_store_type<T>::type; template <size_t I> struct visit_impl { template <typename T, typename F> static void visit(T& tup, size_t idx, F&& fun) { if (idx == I - 1) fun(std::get<I - 1>(tup)); else visit_impl<I - 1>::visit(tup, idx, std::forward<F>(fun)); } }; template <> struct visit_impl<0> { template <typename T, typename F> static void visit(T&, size_t, F&&) {} }; template <typename F, typename... Ts> void visit_at(const std::tuple<Ts...>& tup, size_t idx, F&& fun) { visit_impl<sizeof...(Ts)>::visit(tup, idx, std::forward<F>(fun)); } template <typename F, typename... Ts> void visit_at(std::tuple<Ts...>& tup, size_t idx, F&& fun) { visit_impl<sizeof...(Ts)>::visit(tup, idx, std::forward<F>(fun)); } template <size_t I> struct visit2_impl { template <typename T1, typename T2, typename F> static void visit(T1 tup1, T2 tup2, size_t idx, F&& fun) { if (idx == I - 1) fun(std::get<I - 1>(tup1), std::get<I - 1>(tup2)); else visit2_impl<I - 1>::visit(tup1, tup2, idx, std::forward<F>(fun)); } }; template <> struct visit2_impl<0> { template <typename T1, typename T2, typename F> static void visit(T1&, T2&, size_t, F&&) {} }; template <typename F, typename... Ts, typename... T2s> void visit2_at(const std::tuple<Ts...>& tup1, const std::tuple<T2s...>& tup2, size_t idx, F&& fun) { visit2_impl<sizeof...(Ts)>::visit(tup1, tup2, idx, std::forward<F>(fun)); } template <typename F, typename... Ts, typename... T2s> void visit2_at(std::tuple<Ts...>& tup1, std::tuple<T2s...>& tup2, size_t idx, F&& fun) { visit2_impl<sizeof...(Ts)>::visit(tup1, tup2, idx, std::forward<F>(fun)); } template <typename F, typename... Ts, typename... T2s> void visit2_at(const std::tuple<Ts...>& tup1, std::tuple<T2s...>& tup2, size_t idx, F&& fun) { visit2_impl<sizeof...(Ts)>::visit(tup1, tup2, idx, std::forward<F>(fun)); } template <typename F, typename... Ts, typename... T2s> void visit2_at(std::tuple<Ts...>& tup1, const std::tuple<T2s...>& tup2, size_t idx, F&& fun) { visit2_impl<sizeof...(Ts)>::visit(tup1, tup2, idx, std::forward<F>(fun)); } #define ZEROERR_ENABLE_IF(x) \ template <typename T> \ typename std::enable_if<x, void>::type #define ZEROERR_IS_INT std::is_integral<T>::value #define ZEROERR_IS_FLOAT std::is_floating_point<T>::value #define ZEROERR_IS_CONTAINER detail::is_container<T>::value #define ZEROERR_IS_STRING detail::is_string<T>::value #define ZEROERR_IS_POINTER std::is_pointer<T>::value #define ZEROERR_IS_CHAR std::is_same<T, char>::value #define ZEROERR_IS_WCHAR std::is_same<T, wchar_t>::value #define ZEROERR_IS_CLASS std::is_class<T>::value #define ZEROERR_IS_STREAMABLE detail::is_streamable<std::ostream, T>::value #define ZEROERR_IS_ARRAY detail::is_array<T>::value #define ZEROERR_IS_COMPLEX detail::is_specialization<T, std::complex>::value #define ZEROERR_IS_BOOL std::is_same<T, bool>::value #define ZEROERR_IS_AUTOPTR \ (detail::is_specialization<T, std::unique_ptr>::value || \ detail::is_specialization<T, std::shared_ptr>::value || \ detail::is_specialization<T, std::weak_ptr>::value) #define ZEROERR_IS_MAP detail::ele_type_is_pair<T>::value #define ZEROERR_IS_POD std::is_standard_layout<T>::value #define ZEROERR_IS_EXT detail::has_extension<T>::value } // namespace detail } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <cstdint> #include <cstring> #include <string> #include <type_traits> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief Interface for serializable objects * * IRObject (Intermediate Representation Object) provides a low-level interface for serializing * data types into a common format. It uses a union to store different data types and provides * type-safe access through templated getter methods. * * The object can store: * - Integers (int64_t) * - Floating point numbers (double) * - Strings (char* for long strings, char[8] for short strings) * - Nested objects (IRObject*) * * Memory management: * - The object takes ownership of allocated strings and nested objects * - Copy/move operations perform deep copies/moves * - The destructor frees any owned memory * * Usage example: * @code * IRObject obj; * obj.type = IRObject::Int; * obj.i = 42; * int value = obj.GetScalar<int>(); // value = 42 * @endcode */ struct IRObject { IRObject() { std::memset(this, 0, sizeof(IRObject)); } ~IRObject() {} IRObject(const IRObject& other) { *this = other; } IRObject(IRObject&& other) { *this = std::move(other); } IRObject& operator=(const IRObject& other) { std::memcpy(this, &other, sizeof(IRObject)); return *this; } IRObject& operator=(IRObject&& other) { std::memcpy(this, &other, sizeof(IRObject)); std::memset(&other, 0, sizeof(IRObject)); return *this; } enum Type { Undefined, Int, Float, String, ShortString, Object }; union { int64_t i; double f; char* s; char ss[8]; IRObject* o; // first must be the number of elements }; char others[7]; unsigned type; template <typename T> typename std::enable_if<std::is_integral<T>::value, T>::type GetScalar() { return static_cast<T>(i); } template <typename T> typename std::enable_if<std::is_floating_point<T>::value, T>::type GetScalar() { return static_cast<T>(f); } template <typename T> typename std::enable_if<std::is_enum<T>::value, T>::type GetScalar() { return GetScalar<typename std::underlying_type<T>::type>(); } template <typename T> typename std::enable_if<std::is_same<T, std::string>::value, T>::type GetScalar() { if (type == Type::String) return std::string(s); else if (type == Type::ShortString) return std::string(ss); return ""; } template <typename T> typename std::enable_if<std::is_integral<T>::value, void>::type SetScalar(T val) { i = static_cast<int64_t>(val); type = Type::Int; } template <typename T> typename std::enable_if<std::is_floating_point<T>::value, void>::type SetScalar(T val) { f = static_cast<double>(val); type = Type::Float; } template <typename T> typename std::enable_if<std::is_enum<T>::value, void>::type SetScalar(T val) { SetScalar<typename std::underlying_type<T>::type>(val); } template <typename T> typename std::enable_if<std::is_same<T, std::string>::value, void>::type SetScalar(T val) { size_t size = val.size(); if (size > 14) { s = alloc_str(size); strcpy(s, val.c_str()); type = Type::String; } else { strcpy(ss, val.c_str()); ss[size] = 0; type = Type::ShortString; } } void SetScalar(const IRObject& obj) { *this = obj; } struct Childrens { int64_t size; IRObject* children; }; Childrens GetChildren() { return {o->i, o + 1}; } void SetChildren(IRObject* children) { o = children - 1; type = Type::Object; } // ================================================================ template <typename T> static typename std::enable_if<std::is_integral<T>::value || std::is_floating_point<T>::value || std::is_same<T, std::string>::value || std::is_enum<T>::value, IRObject>::type FromCorpus(T val) { IRObject obj; obj.SetScalar(val); return obj; } template <typename T> static typename std::enable_if< detail::is_container<T>::value && !std::is_same<T, std::string>::value, IRObject>::type FromCorpus(const T& val) { IRObject* children = alloc(val.size()); IRObject obj; obj.SetChildren(children); for (const auto& elem : val) { *children++ = IRObject::FromCorpus(elem); } return obj; } template <class TupType, unsigned... I> inline static void handle_tuple(const TupType& _tup, IRObject* children, detail::seq<I...>) { int _[] = {((children + I)->SetScalar(FromCorpus(std::get<I>(_tup))), 0)...}; (void)_; } template <typename... Args> static IRObject FromCorpus(const std::tuple<Args...>& val) { unsigned size = sizeof...(Args); IRObject obj; IRObject* children = alloc(size); obj.SetChildren(children); handle_tuple(val, children, detail::gen_seq<sizeof...(Args)>{}); return obj; } template <typename T1, typename T2> static IRObject FromCorpus(const std::pair<T1, T2>& val) { IRObject obj; IRObject* children = alloc(2); obj.SetChildren(children); children[0] = IRObject::FromCorpus(val.first); children[1] = IRObject::FromCorpus(val.second); return obj; } template <typename T> static typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value, T>::type ToCorpus(IRObject obj) { if (obj.type != IRObject::Int) { throw std::runtime_error("Invalid type conversion."); } return obj.GetScalar<T>(); } template <typename T> static typename std::enable_if<std::is_floating_point<T>::value, T>::type ToCorpus( IRObject obj) { if (obj.type != IRObject::Float) { throw std::runtime_error("Invalid type conversion."); } return obj.GetScalar<T>(); } template <typename T> static typename std::enable_if<std::is_same<T, std::string>::value, T>::type ToCorpus( IRObject obj) { if (obj.type != IRObject::String && obj.type != IRObject::ShortString) { throw std::runtime_error("Invalid type conversion."); } return obj.GetScalar<T>(); } template <typename TupType, unsigned... I> static TupType parse_tuple(IRObject* children, detail::seq<I...>) { return std::make_tuple( ToCorpus<typename std::tuple_element<I, TupType>::type>(*(children + I))...); } template <typename T> static typename std::enable_if< detail::is_container<T>::value && !std::is_same<T, std::string>::value, T>::type ToCorpus(IRObject obj) { if (obj.type != IRObject::Object) { throw std::runtime_error("Invalid type conversion."); } auto c = obj.GetChildren(); T val; for (int i = 0; i < c.size; i++) { val.insert(val.end(), ToCorpus<typename T::value_type>(c.children[i])); } return val; } template <typename T> static typename std::enable_if<detail::is_specialization<T, std::tuple>::value, T>::type ToCorpus(IRObject obj) { if (obj.type != IRObject::Object || obj.GetChildren().size != std::tuple_size<T>::value) { throw std::runtime_error("Invalid type conversion."); } return parse_tuple<T>(obj.o + 1, detail::gen_seq<std::tuple_size<T>::value>{}); } template <typename T> static typename std::enable_if<detail::is_specialization<T, std::pair>::value, T>::type ToCorpus(IRObject obj) { if (obj.type != IRObject::Object || obj.GetChildren().size != 2) { throw std::runtime_error("Invalid type conversion."); } return std::make_pair(ToCorpus<typename T::first_type>(obj.GetChildren().children[0]), ToCorpus<typename T::second_type>(obj.GetChildren().children[1])); } static char* alloc_str(size_t size); static IRObject* alloc(size_t size); // Serialize the object as a string. static std::string ToString(IRObject obj); static IRObject FromString(std::string str); static std::vector<uint8_t> ToBinary(IRObject obj); static IRObject FromBinary(std::vector<uint8_t> bin); }; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #ifdef __GNUG__ #include <cxxabi.h> #endif #if defined(ZEROERR_ENABLE_PFR) && (ZEROERR_CXX_STANDARD >= 14) #include "pfr.hpp" #endif #if defined(ZEROERR_ENABLE_MAGIC_ENUM) && (ZEROERR_CXX_STANDARD >= 17) #include "magic_enum.hpp" #endif #if defined(ZEROERR_ENABLE_DSVIZ) #include "dsviz.h" #endif ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { constexpr unsigned max_rank = 5; struct Printer; template <typename T> void PrinterExt(Printer&, T, unsigned, const char*, rank<0>); namespace detail { /** * @brief has_extension is a type trait to check if user defined PrinterExt for a type */ template <typename T, typename = void> struct has_extension : std::false_type {}; template <typename T> using has_printer_ext = void_t<decltype(zeroerr::PrinterExt(std::declval<zeroerr::Printer&>(), std::declval<T&>(), 0, nullptr, zeroerr::rank<zeroerr::max_rank>()))>; template <typename T> struct has_extension<T, has_printer_ext<T>> : std::true_type {}; } // namespace detail /** * @brief A functor class Printer for printing a value of any type. * * This class can print values with all basic types, pointers, STL containers, tuple, optional, and * variant values. Any class that is streamable can be printed. POD structs can be supported using * third-party library Boost.PFR and enum can be supported using magic_enum. */ struct Printer { template <typename... Args> Printer& operator()(Args&&... args) { check_stream(); call(std::forward<Args>(args)...); return *this; } template <typename T> Printer& operator()(std::initializer_list<T>&& value) { check_stream(); call(std::forward<decltype(value)>(value)); return *this; } void check_stream() { if (use_stringstream && clear_stream_before_printing) { auto& ss = static_cast<std::stringstream&>(os); ss.str(std::string()); ss.clear(); } } template <typename T, typename... V> void call(T value, V... others) { PrinterExt(*this, std::forward<T>(value), 0, " ", rank<max_rank>{}); call(std::forward<V>(others)...); } template <typename T> void call(T value) { PrinterExt(*this, std::forward<T>(value), 0, "", rank<max_rank>{}); os << line_break; os.flush(); } Printer(std::ostream& os) : os(os) {} Printer() : os(*new std::stringstream()) { use_stringstream = true; } ~Printer() { if (use_stringstream) delete &os; } bool isColorful = true; // colorful output bool isCompact = false; // compact mode bool isQuoted = true; // string is quoted int indent = 2; const char* line_break = "\n"; std::ostream& os; bool use_stringstream = false; bool clear_stream_before_printing = true; template <class T> static std::string type(const T& t) { return demangle(typeid(t).name()); } ZEROERR_ENABLE_IF(ZEROERR_IS_INT || ZEROERR_IS_FLOAT) print(T value, unsigned level, const char* lb, rank<0>) { os << tab(level) << value << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_POINTER) print(T value, unsigned level, const char* lb, rank<0>) { if (value == nullptr) os << tab(level) << "nullptr" << lb; else os << tab(level) << "<" << type(value) << " at " << value << ">" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS) print(T value, unsigned level, const char* lb, rank<0>) { os << tab(level) << type(value) << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CHAR || ZEROERR_IS_WCHAR) print(T value, unsigned level, const char* lb, rank<1>) { os << tab(level) << '\'' << value << '\'' << lb; } #if defined(ZEROERR_ENABLE_PFR) && (ZEROERR_CXX_STANDARD >= 14) template <class StructType, unsigned... I> void print_struct(const StructType& s, unsigned, const char*, detail::seq<I...>) { int _[] = {(os << (I == 0 ? "" : ", ") << pfr::get<I>(s), 0)...}; (void)_; } ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_POD) print(const T& value, unsigned level, const char* lb, rank<1>) { os << tab(level) << "{"; print_struct(value, level, isCompact ? " " : line_break, detail::gen_seq<pfr::tuple_size<T>::value>{}); os << tab(level) << "}" << lb; } #endif ZEROERR_ENABLE_IF(ZEROERR_IS_BOOL) print(T value, unsigned level, const char* lb, rank<2>) { os << tab(level) << (value ? "true" : "false") << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CLASS && ZEROERR_IS_STREAMABLE) print(T value, unsigned level, const char* lb, rank<2>) { os << tab(level) << value << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER) print(const T& value, unsigned level, const char* lb, rank<2>) { os << tab(level) << "{" << (isCompact ? "" : line_break); bool last = false; for (auto iter = value.begin(); iter != value.end(); ++iter) { if (std::next(iter) == value.end()) last = true; print(*iter, level + 1, isCompact ? (last ? "" : ", ") : line_break, rank<max_rank>{}); } os << tab(level) << "}" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_ARRAY) print(const T& value, unsigned level, const char* lb, rank<3>) { os << tab(level) << "["; bool last = false; for (auto iter = value.begin(); iter != value.end(); ++iter) { if (std::next(iter) == value.end()) last = true; print(*iter, 0, last ? "" : ", ", rank<max_rank>{}); } os << tab(level) << "]" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_AUTOPTR) print(T value, unsigned level, const char* lb, rank<3>) { if (value.get() == nullptr) os << tab(level) << "nullptr" << lb; else os << tab(level) << "<" << type(value) << " at " << value.get() << ">" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER && ZEROERR_IS_MAP) print(const T& value, unsigned level, const char* lb, rank<4>) { os << tab(level) << "{" << (isCompact ? "" : line_break); bool last = false; for (auto iter = value.begin(); iter != value.end(); ++iter) { if (std::next(iter) == value.end()) last = true; print(iter->first, level + 1, " : ", rank<max_rank>{}); print(iter->second, level + 1, isCompact ? (last ? "" : ", ") : line_break, rank<max_rank>{}); } os << tab(level) << "}" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_COMPLEX) print(T value, unsigned level, const char* lb, rank<4>) { os << tab(level) << "(" << value.real() << "+" << value.imag() << "i)" << lb; } ZEROERR_ENABLE_IF(ZEROERR_IS_STRING) print(T value, unsigned level, const char* lb, rank<4>) { os << tab(level) << quote() << value << quote() << lb; } template <class TupType> inline void print_tuple(const TupType&, unsigned, const char*, detail::seq<>) {} template <class TupType, unsigned... I> inline void print_tuple(const TupType& _tup, unsigned level, const char*, detail::seq<I...>) { int _[] = {(os << (I == 0 ? "" : ", "), print(std::get<I>(_tup), level + 1, "", rank<max_rank>{}), 0)...}; (void)_; } template <class... Args> void print(const std::tuple<Args...>& value, unsigned level, const char* lb, rank<3>) { os << tab(level) << "("; print_tuple(value, level, isCompact ? " " : line_break, detail::gen_seq<sizeof...(Args)>{}); os << ")" << lb; } std::string tab(unsigned level) { return std::string((isCompact ? 0 : level * indent), ' '); } const char* quote() { return isQuoted ? "\"" : ""; } static std::string demangle(const char* name) { #ifdef __GNUG__ int status = -4; char* res = abi::__cxa_demangle(name, NULL, NULL, &status); std::string ret = (status == 0) ? res : name; std::free(res); return ret; #else return name; #endif } std::string str() const { if (use_stringstream == false) throw std::runtime_error("Printer is not using stringstream"); return static_cast<std::stringstream&>(os).str(); } operator std::string() const { return str(); } friend std::ostream& operator<<(std::ostream& os, const Printer& P) { if (P.use_stringstream) os << P.str(); return os; } }; /** * @brief PrinterExt is an extension of Printer that allows user to write custom rules for printing. * @details * User can use SFINAE to extend PrinterExt, e.g.: * ``` * template<typename T> * typename std::enable_if<std::is_base_of<llvm::Function, T>::value, void>::type * PrinterExt(Printer& P, T* s, unsigned level, const char* lb, rank<3>); * ``` * @tparam T the type of the object to be printed. * @param P Printer class * @param v the object to be printed. * @param level the indentation level. * @param lb the line break. * @param r the rank of the rule. 0 is lowest priority. */ template <class T> void PrinterExt(Printer& P, T v, unsigned level, const char* lb, rank<0>) { P.print(std::forward<T>(v), level, lb, rank<max_rank>{}); } extern Printer& getStdoutPrinter(); extern Printer& getStderrPrinter(); } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH ZEROERR_SUPPRESS_COMPARE #ifndef ZEROERR_DISABLE_COMPLEX_AND_OR /** * AND and OR are used to combine expressions in a more readable way. * For example: * CHECK(1 == 1 AND 2 == 2); * * In its implementation, the expression is decomposed to * ExpressionDecomposer() << 1 == 1 && ExpressionDecomposer() << 2 == 2 */ #define AND &&zeroerr::ExpressionDecomposer() << #define OR || zeroerr::ExpressionDecomposer() << #endif namespace zeroerr { // SFINAE helper used to check L op R is supported, but the result type is `ret` #define ZEROERR_SFINAE_OP(ret, op) \ typename std::decay<decltype(std::declval<L>() op std::declval<R>(), std::declval<ret>())>::type template <typename T> struct deferred_false { static const bool value = false; }; #define ZEROERR_EXPRESSION_COMPARISON(op, op_name) \ template <typename R> \ ZEROERR_SFINAE_OP(Expression<R>, op) \ operator op(R && rhs) { \ std::stringstream ss; \ Printer print(ss); \ print.isCompact = true; \ print.line_break = ""; \ if (decomp.empty()) { \ print(lhs); \ res = true; \ } else \ ss << decomp; \ ss << " " #op " "; \ print(rhs); \ return Expression<R>(static_cast<R&&>(rhs), res && (lhs op rhs), ss.str()); \ } \ template <typename R, \ typename std::enable_if<!std::is_rvalue_reference<R>::value, void>::type* = nullptr> \ ZEROERR_SFINAE_OP(Expression<const R&>, op) \ operator op(const R & rhs) { \ std::stringstream ss; \ Printer print(ss); \ print.isCompact = true; \ print.line_break = ""; \ if (decomp.empty()) { \ print(lhs); \ res = true; \ } else \ ss << decomp; \ ss << " " #op " "; \ print(rhs); \ return Expression<const R&>(rhs, res && (lhs op rhs), ss.str()); \ } #define ZEROERR_EXPRESSION_ANDOR(op, op_name) \ ExprResult operator op(ExprResult rhs) { \ std::stringstream ss; \ ss << decomp << " " #op " " << rhs.decomp; \ return ExprResult(res op rhs.res, ss.str()); \ } #define ZEROERR_FORBIT_EXPRESSION(rt, op) \ template <typename R> \ rt& operator op(const R&) { \ static_assert(deferred_false<R>::value, \ "Please Rewrite Expression As Binary Comparison!"); \ return *this; \ } struct ExprResult { bool res; std::string decomp; ExprResult(bool res, std::string decomposition = "") : res(res), decomp(decomposition) {} ZEROERR_EXPRESSION_ANDOR(&&, and) ZEROERR_EXPRESSION_ANDOR(||, or) ZEROERR_FORBIT_EXPRESSION(ExprResult, &) ZEROERR_FORBIT_EXPRESSION(ExprResult, ^) ZEROERR_FORBIT_EXPRESSION(ExprResult, |) ZEROERR_FORBIT_EXPRESSION(ExprResult, ==) ZEROERR_FORBIT_EXPRESSION(ExprResult, !=) ZEROERR_FORBIT_EXPRESSION(ExprResult, <) ZEROERR_FORBIT_EXPRESSION(ExprResult, >) ZEROERR_FORBIT_EXPRESSION(ExprResult, <=) ZEROERR_FORBIT_EXPRESSION(ExprResult, >=) ZEROERR_FORBIT_EXPRESSION(ExprResult, +=) ZEROERR_FORBIT_EXPRESSION(ExprResult, -=) ZEROERR_FORBIT_EXPRESSION(ExprResult, *=) ZEROERR_FORBIT_EXPRESSION(ExprResult, /=) ZEROERR_FORBIT_EXPRESSION(ExprResult, %=) ZEROERR_FORBIT_EXPRESSION(ExprResult, <<=) ZEROERR_FORBIT_EXPRESSION(ExprResult, >>=) ZEROERR_FORBIT_EXPRESSION(ExprResult, &=) ZEROERR_FORBIT_EXPRESSION(ExprResult, ^=) ZEROERR_FORBIT_EXPRESSION(ExprResult, |=) }; namespace details { template <typename T> typename std::enable_if<std::is_convertible<T, bool>::value, bool>::type getBool(T&& lhs) { return static_cast<bool>(lhs); } template <typename T> typename std::enable_if<!std::is_convertible<T, bool>::value, bool>::type getBool(T&&) { return true; } } // namespace details template <typename L> struct Expression { L lhs; bool res = true; std::string decomp; explicit Expression(L&& in) : lhs(static_cast<L&&>(in)) { res = details::getBool(lhs); } explicit Expression(L&& in, bool res, std::string&& decomp) : lhs(static_cast<L&&>(in)), res(res), decomp(static_cast<std::string&&>(decomp)) {} operator ExprResult() { if (decomp.empty()) { Printer print; print.isCompact = true; print.line_break = ""; decomp = print(lhs).str(); } return ExprResult(res, decomp); } operator L() const { return lhs; } ZEROERR_EXPRESSION_COMPARISON(==, eq) ZEROERR_EXPRESSION_COMPARISON(!=, ne) ZEROERR_EXPRESSION_COMPARISON(>, gt) ZEROERR_EXPRESSION_COMPARISON(<, lt) ZEROERR_EXPRESSION_COMPARISON(>=, ge) ZEROERR_EXPRESSION_COMPARISON(<=, le) ZEROERR_EXPRESSION_ANDOR(&&, and) ZEROERR_EXPRESSION_ANDOR(||, or) ZEROERR_FORBIT_EXPRESSION(Expression, &) ZEROERR_FORBIT_EXPRESSION(Expression, ^) ZEROERR_FORBIT_EXPRESSION(Expression, |) ZEROERR_FORBIT_EXPRESSION(Expression, =) ZEROERR_FORBIT_EXPRESSION(Expression, +=) ZEROERR_FORBIT_EXPRESSION(Expression, -=) ZEROERR_FORBIT_EXPRESSION(Expression, *=) ZEROERR_FORBIT_EXPRESSION(Expression, /=) ZEROERR_FORBIT_EXPRESSION(Expression, %=) ZEROERR_FORBIT_EXPRESSION(Expression, <<=) ZEROERR_FORBIT_EXPRESSION(Expression, >>=) ZEROERR_FORBIT_EXPRESSION(Expression, &=) ZEROERR_FORBIT_EXPRESSION(Expression, ^=) ZEROERR_FORBIT_EXPRESSION(Expression, |=) ZEROERR_FORBIT_EXPRESSION(Expression, <<) ZEROERR_FORBIT_EXPRESSION(Expression, >>) }; #undef ZEROERR_EXPRESSION_COMPARISON #undef ZEROERR_EXPRESSION_ANDOR #undef ZEROERR_FORBIT_EXPRESSION struct ExpressionDecomposer { // The right operator for capturing expressions is "<=" instead of "<<" (based on the // operator precedence table) but then there will be warnings from GCC about "-Wparentheses" // and since "_Pragma()" is problematic this will stay for now... // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 // For temporary objects, we need to use rvalue reference to avoid copy template <typename L> Expression<L> operator<<(L&& operand) { return Expression<L>(static_cast<L&&>(operand)); } // For other objects, we will store the reference template <typename L, typename std::enable_if<!std::is_rvalue_reference<L>::value, void>::type* = nullptr> Expression<const L&> operator<<(const L& operand) { return Expression<const L&>(operand); } }; template <typename T> class IMatcher { public: virtual ~IMatcher() = default; virtual bool match(const T&) const = 0; }; template <typename T> class IMatcherRef { public: IMatcherRef(const IMatcher<T>* ptr) : p(ptr) {} IMatcherRef(const IMatcherRef&) = delete; IMatcherRef(IMatcherRef&& other) { p = std::move(other.p); other.p = nullptr; } void operator=(IMatcherRef&& other) { p = std::move(other.p); other.p = nullptr; } IMatcherRef& operator=(const IMatcherRef&) = delete; ~IMatcherRef() { if (p) delete p; } IMatcherRef operator&&(IMatcherRef&& other); IMatcherRef operator||(IMatcherRef&& other); IMatcherRef operator!(); const IMatcher<T>* operator->() const { return p; } protected: const IMatcher<T>* p = nullptr; }; template <typename T> class CombinedMatcher : public IMatcher<T> { public: CombinedMatcher(IMatcherRef<T>&& lhs, IMatcherRef<T>&& rhs, bool is_and) : lhs(std::move(lhs)), rhs(std::move(rhs)), is_and(is_and) {} IMatcherRef<T> lhs; IMatcherRef<T> rhs; bool is_and; virtual bool match(const T& t) const override { if (is_and) { return lhs->match(t) && rhs->match(t); } else { return lhs->match(t) || rhs->match(t); } } }; template <typename T> class NotMatcher : public IMatcher<T> { public: NotMatcher(IMatcherRef<T>&& matcher) : matcher(std::move(matcher)) {} IMatcherRef<T> matcher; virtual bool match(const T& t) const override { return !matcher->match(t); } }; template <typename T> inline IMatcherRef<T> IMatcherRef<T>::operator&&(IMatcherRef<T>&& other) { return new CombinedMatcher<T>(std::move(*this), std::move(other), true); } template <typename T> inline IMatcherRef<T> IMatcherRef<T>::operator||(IMatcherRef<T>&& other) { return new CombinedMatcher<T>(std::move(*this), std::move(other), false); } template <typename T> inline IMatcherRef<T> IMatcherRef<T>::operator!() { return new NotMatcher<T>(std::move(*this)); } template <typename T> struct StartWithMatcher : public IMatcher<T> { StartWithMatcher(const T& s) : start(s) {} T start; virtual bool match(const T& t) const override { bool result = true; for (auto i = start.begin(), j = t.begin(); i != start.end(); ++i, ++j) { if (j == t.end() || *i != *j) { result = false; break; } } return result; } }; template <typename T> typename std::enable_if<std::is_constructible<std::string, T>::value, IMatcherRef<std::string>>::type start_with(T&& s) { return new StartWithMatcher<std::string>(std::string(s)); } } // namespace zeroerr ZEROERR_SUPPRESS_COMPARE_POP ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <cstdint> #include <cstring> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * An extremely fast random generator. Currently, this implements *RomuDuoJr*, developed by Mark * Overton. Source: http://www.romu-random.org/ * * RomuDuoJr is extremely fast and provides reasonable good randomness. Not enough for large * jobs, but definitely good enough for a benchmarking framework. * * * Estimated capacity: @f$ 2^{51} @f$ bytes * * Register pressure: 4 * * State size: 128 bits * * This random generator is a drop-in replacement for the generators supplied by ``<random>``. * It is not cryptographically secure. It's intended purpose is to be very fast so that * benchmarks that make use of randomness are not distorted too much by the random generator. * * Rng also provides a few non-standard helpers, optimized for speed. */ class Rng final { public: /** * @brief This RNG provides 64bit randomness. */ using result_type = uint64_t; static uint64_t min(); static uint64_t max(); /** * As a safety precausion, we don't allow copying. Copying a PRNG would mean you would have * two random generators that produce the same sequence, which is generally not what one * wants. Instead create a new rng with the default constructor Rng(), which is * automatically seeded from `std::random_device`. If you really need a copy, use copy(). */ Rng(Rng const&) = delete; /** * Same as Rng(Rng const&), we don't allow assignment. If you need a new Rng create one with * the default constructor Rng(). */ Rng& operator=(Rng const&) = delete; // moving is ok Rng(Rng&&) noexcept = default; Rng& operator=(Rng&&) noexcept = default; ~Rng() noexcept = default; /** * @brief Creates a new Random generator with random seed. * * Instead of a default seed (as the random generators from the STD), this properly seeds * the random generator from `std::random_device`. It guarantees correct seeding. Note that * seeding can be relatively slow, depending on the source of randomness used. So it is best * to create a Rng once and use it for all your randomness purposes. */ Rng(); /*! Creates a new Rng that is seeded with a specific seed. Each Rng created from the same seed will produce the same randomness sequence. This can be useful for deterministic behavior. @verbatim embed:rst .. note:: The random algorithm might change between nanobench releases. Whenever a faster and/or better random generator becomes available, I will switch the implementation. @endverbatim As per the Romu paper, this seeds the Rng with splitMix64 algorithm and performs 10 initial rounds for further mixing up of the internal state. @param seed The 64bit seed. All values are allowed, even 0. */ explicit Rng(uint64_t seed) noexcept; Rng(uint64_t x, uint64_t y) noexcept; Rng(std::vector<uint64_t> const& data); /** * Creates a copy of the Rng, thus the copy provides exactly the same random sequence as the * original. */ Rng copy() const noexcept; /** * @brief Produces a 64bit random value. This should be very fast, thus it is marked as * inline. In my benchmark, this is ~46 times faster than `std::default_random_engine` for * producing 64bit random values. It seems that the fastest std contender is * `std::mt19937_64`. Still, this RNG is 2-3 times as fast. * * @return uint64_t The next 64 bit random value. */ inline uint64_t operator()() noexcept { auto x = mX; mX = UINT64_C(15241094284759029579) * mY; mY = rotl(mY - x, 27); return x; } // This is slightly biased. See /** * Generates a random number between 0 and range (excluding range). * * The algorithm only produces 32bit numbers, and is slightly biased. The effect is quite * small unless your range is close to the maximum value of an integer. It is possible to * correct the bias with rejection sampling (see * [here](https://lemire.me/blog/2016/06/30/fast-random-shuffling/), but this is most likely * irrelevant in practices for the purposes of this Rng. * * See Daniel Lemire's blog post [A fast alternative to the modulo * reduction](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/) * * @param range Upper exclusive range. E.g a value of 3 will generate random numbers 0, * 1, 2. * @return uint32_t Generated random values in range [0, range). */ inline uint32_t bounded(uint32_t range) noexcept { uint64_t r32 = static_cast<uint32_t>(operator()()); auto multiresult = r32 * range; return static_cast<uint32_t>(multiresult >> 32U); } // random double in range [0, 1] // see http://prng.di.unimi.it/ /** * Provides a random uniform double value between 0 and 1. This uses the method described in * [Generating uniform doubles in the unit interval](http://prng.di.unimi.it/), and is * extremely fast. * * @return double Uniformly distributed double value in range [0,1], excluding 1. */ inline double uniform01() noexcept { auto i = (UINT64_C(0x3ff) << 52U) | (operator()() >> 12U); // can't use union in c++ here for type puning, it's undefined behavior. // std::memcpy is optimized anyways. double d; std::memcpy(&d, &i, sizeof(double)); return d - 1.0; } /** * Shuffles all entries in the given container. Although this has a slight bias due to the * implementation of bounded(), this is preferable to `std::shuffle` because it is over 5 * times faster. See Daniel Lemire's blog post [Fast random * shuffling](https://lemire.me/blog/2016/06/30/fast-random-shuffling/). * * @param container The whole container will be shuffled. */ template <typename Container> void shuffle(Container& container) noexcept { auto size = static_cast<uint32_t>(container.size()); for (auto i = size; i > 1U; --i) { using std::swap; auto p = bounded(i); // number in [0, i) swap(container[i - 1], container[p]); } } /** * Extracts the full state of the generator, e.g. for serialization. For this RNG this is * just 2 values, but to stay API compatible with future implementations that potentially * use more state, we use a vector. * * @return Vector containing the full state: */ std::vector<uint64_t> state() const; private: static uint64_t rotl(uint64_t x, unsigned k) noexcept; uint64_t mX; uint64_t mY; }; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief Domain class for generating random values of a specific type. * @tparam ValueType The type of the value to generate. * @tparam CorpusType The type of the corpus stored in the domain. * Here is an example. If you want to generate an list of intergers, * but will store the list in a vector, then ValueType will be * std::list<int> and CorpusType will be std::vector<int>. */ template <typename ValueType, typename CorpusType = ValueType> class Domain { public: virtual ~Domain() = default; virtual CorpusType GetRandomCorpus(Rng& rng) const = 0; virtual ValueType GetRandomValue(Rng& rng) const { return GetValue(GetRandomCorpus(rng)); }; virtual CorpusType FromValue(const ValueType& v) const = 0; virtual ValueType GetValue(const CorpusType& v) const = 0; virtual CorpusType ParseCorpus(IRObject v) const { return IRObject::ToCorpus<CorpusType>(v); } virtual IRObject SerializeCorpus(const CorpusType& v) const { return IRObject::FromCorpus(v); } virtual void Mutate(Rng& rng, CorpusType& v, bool only_shrink = false) const = 0; // virtual void MutateSelectedField(Rng& rng, CorpusType& v, unsigned field, // bool only_shrink = false) const {} // virtual unsigned CountNumberOfFields(CorpusType v) const { return 0; } }; /** * @brief DomainConvertable is a base class for domains that can be converted to and from a ValueType * * This class provides default implementations for the GetValue and FromValue methods. * It is used to convert between the corpus types and the value types. */ template <typename ValueType, typename CorpusType = ValueType> class DomainConvertable : public Domain<ValueType, CorpusType> { public: virtual ValueType GetValue(const CorpusType& v) const { return v; } virtual CorpusType FromValue(const ValueType& v) const { return v; } }; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief InRange is a domain that generates random values within a specified range * * @tparam T The numeric type to generate values for (e.g. int, float) * * This domain generates random values between a minimum and maximum value (inclusive). * It supports any numeric type that can be used with arithmetic operations. * * Example: * ```cpp * // Generate integers between 1 and 100 * auto domain = InRange(1, 100); * * // Generate floating point numbers between 0.0 and 1.0 * auto domain = InRange(0.0, 1.0); * ``` */ template <typename T> class InRange : public DomainConvertable<T> { public: using ValueType = T; using CorpusType = T; ValueType min, max; InRange(T min, T max) : min(min), max(max) {} CorpusType GetRandomCorpus(Rng& rng) const override { ValueType offsize = max - min + 1; ValueType v = rng.bounded(offsize); v = min + v; return v; } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { CorpusType offsize = max - min + 1; v = rng.bounded(offsize); v = min + v; } }; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief ElementOf is a domain that generates random values from a fixed set of elements * * @tparam T The type of elements to generate * * This domain allows generating random values by selecting from a predefined set of elements. * The elements are provided as a vector during construction. * * Example: * ```cpp * // Generate random values from a set of strings * auto domain = ElementOf<std::string>({"red", "green", "blue"}); * * // Generate random values from a set of integers * auto domain = ElementOf<int>({1, 2, 3, 4, 5}); * ``` */ template <typename T> class ElementOf : public Domain<T, uint64_t> { public: using ValueType = T; using CorpusType = uint64_t; std::vector<T> elements; ElementOf(std::vector<T> elements) : elements(elements) {} CorpusType GetRandomCorpus(Rng& rng) override { return rng.bounded(elements.size()); } ValueType GetValue(const CorpusType& v) const override { return elements[v]; } CorpusType FromValue(const ValueType& v) const override { for (size_t i = 0; i < elements.size(); i++) { if (elements[i] == v) return i; } return 0; } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { if (elements.size() <= 1) return; if (only_shrink) { v = rng.bounded(v); } else { v = rng.bounded(elements.size()); } } }; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief ContainerOf is a domain that generates random containers filled with elements from an inner domain * * @tparam T The container type to generate (e.g. vector, list, set) * @tparam InnerDomain The domain type used to generate the container elements * * This domain allows generating containers where each element is generated by an inner domain. * It supports configuring the size constraints of the generated containers. * * Example: * ```cpp * // Generate vectors of ints between 0-100 * auto domain = ContainerOf<std::vector<int>>(InRange(0, 100)); * * // Generate sets of strings * auto domain = ContainerOf<std::set<std::string>>(Arbitrary<std::string>()); * * // Configure size constraints * domain.WithMinSize(5); // At least 5 elements * domain.WithMaxSize(10); // At most 10 elements * domain.WithSize(7); // Exactly 7 elements * ``` */ struct ContainerOfBase { int min_size = 0, max_size = 100, size = -1; void WithMaxSize(unsigned _max_size) { this->max_size = _max_size; } void WithMinSize(unsigned _min_size) { this->min_size = _min_size; } void WithSize(unsigned _size) { this->size = _size; } }; template <typename T, typename InnerDomain> class AssociativeContainerOf : public Domain<T, std::vector<typename InnerDomain::CorpusType>>, public ContainerOfBase { InnerDomain inner_domain; public: using ValueType = T; using CorpusType = std::vector<typename InnerDomain::CorpusType>; AssociativeContainerOf(InnerDomain&& inner_domain) : inner_domain(std::move(inner_domain)) {} virtual ValueType GetValue(const CorpusType& v) const override { ValueType result; for (const auto& elem : v) { result.insert(inner_domain.GetValue(elem)); } return result; } virtual CorpusType FromValue(const ValueType& v) const override { CorpusType result; for (const auto& elem : v) { result.push_back(inner_domain.FromValue(elem)); } return result; } CorpusType GetRandomCorpus(Rng& rng) const override { unsigned E = rng.bounded(max_size - min_size + 1) + min_size; CorpusType result; for (unsigned i = 0; i < E; i++) { result.push_back(inner_domain.GetRandomCorpus(rng)); } return result; } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { int action = rng.bounded(5); if (size == -1 && action == 0) { if (static_cast<int>(v.size()) > min_size) v.erase(v.begin() + rng.bounded(static_cast<uint32_t>(v.size()))); } else if (size == -1 && action == 1) { if (static_cast<int>(v.size()) < max_size) v.push_back(inner_domain.GetRandomCorpus(rng)); } else { inner_domain.Mutate(rng, v[rng.bounded(static_cast<uint32_t>(v.size()))], only_shrink); } } }; template <typename T, typename InnerDomain> class SequenceContainerOf : public Domain<T, std::vector<typename InnerDomain::CorpusType>>, public ContainerOfBase { InnerDomain inner_domain; public: using ValueType = T; using CorpusType = std::vector<typename InnerDomain::CorpusType>; SequenceContainerOf(InnerDomain&& inner_domain) : inner_domain(std::move(inner_domain)) {} virtual ValueType GetValue(const CorpusType& v) const override { return ValueType(v.begin(), v.end()); } virtual CorpusType FromValue(const ValueType& v) const override { return CorpusType(v.begin(), v.end()); } CorpusType GetRandomCorpus(Rng& rng) const override { unsigned E = rng.bounded(max_size - min_size + 1) + min_size; CorpusType result; for (unsigned i = 0; i < E; i++) { result.push_back(inner_domain.GetRandomCorpus(rng)); } return result; } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { int action = rng.bounded(5); if (size == -1 && action == 0) { if (static_cast<int>(v.size()) > min_size) v.erase(v.begin() + rng.bounded(static_cast<uint32_t>(v.size()))); } else if (size == -1 && action == 1) { if (static_cast<int>(v.size()) < max_size) v.push_back(inner_domain.GetRandomCorpus(rng)); } else { inner_domain.Mutate(rng, v[rng.bounded(static_cast<uint32_t>(v.size()))], only_shrink); } } }; // ContainerOf<T>(inner) combinator creates a domain for a container T (eg, a // std::vector, std::set, etc) where elements are created from `inner`. // // Example usage: // // ContainerOf<std::vector<int>>(InRange(1, 2021)) // // The domain also supports customizing the minimum and maximum size via the // `WithSize`, `WithMinSize` and `WithMaxSize` functions. Eg: // // ContainerOf<std::vector<int>>(Arbitrary<int>()).WithMaxSize(5) // template <typename T, typename Inner> typename std::enable_if<detail::is_associative_container<T>::value, AssociativeContainerOf<T, Inner>>::type ContainerOf(Inner&& inner) { return AssociativeContainerOf<T, Inner>(std::move(inner)); } template <typename T, typename Inner> typename std::enable_if<!detail::is_associative_container<T>::value && detail::is_container<T>::value, SequenceContainerOf<T, Inner>>::type ContainerOf(Inner&& inner) { return SequenceContainerOf<T, Inner>(std::move(inner)); } // We can also support with a template template parameter, so that we can use // the name of the container instead of the complete type. In such case, // the ValueType of the inner domain should be passed into the container // // ContainerOf<std::vector>(Positive<int>()).WithSize(3); // template <template <typename, typename...> class T, typename... Inner, typename C = T<typename Inner::ValueType...>> auto ContainerOf(Inner... inner) -> decltype(ContainerOf<C>(std::move(inner)...)) { return ContainerOf<C>(std::move(inner)...); } } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <tuple> #if defined(ZEROERR_ENABLE_PFR) && (ZEROERR_CXX_STANDARD >= 14) #include "pfr.hpp" #endif ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief AggregateOf is a domain that combines multiple inner domains into a tuple or aggregate type * * @tparam T The aggregate type to generate (e.g. struct or tuple) * @tparam Inner The inner domain types that will generate each field * * This domain allows generating structured data by composing multiple inner domains. * Each inner domain generates one field of the aggregate type. * * Example: * ```cpp * struct Point { * int x; * int y; * }; * * auto domain = AggregateOf<Point>( * InRange(0, 100), // Domain for x * InRange(0, 100) // Domain for y * ); * ``` */ template <typename T, typename... Inner> class AggregateOf : public Domain<T, std::tuple<typename Inner::CorpusType...>> { public: using ValueType = T; using CorpusType = std::tuple<typename Inner::CorpusType...>; private: template <unsigned... I> inline CorpusType get_random(Rng& rng, detail::seq<I...>) const { return CorpusType{std::get<I>(inner_domains).GetRandomCorpus(rng)...}; } template <unsigned... I> inline ValueType get_tuple(const CorpusType& v, detail::seq<I...>) const { return ValueType{std::get<I>(inner_domains).GetValue(std::get<I>(v))...}; } template <unsigned... I> inline CorpusType from_tuple(const ValueType& v, detail::seq<I...>) const { return CorpusType{std::get<I>(inner_domains).FromValue(std::get<I>(v))...}; } std::tuple<Inner...> inner_domains; public: AggregateOf(Inner&&... inner) : inner_domains(std::make_tuple(std::move(inner)...)) {} CorpusType GetRandomCorpus(Rng& rng) const override { return get_random(rng, detail::gen_seq<sizeof...(Inner)>{}); } ValueType GetValue(const CorpusType& v) const override { return get_tuple(v, detail::gen_seq<sizeof...(Inner)>{}); } CorpusType FromValue(const ValueType& v) const override { return from_tuple(v, detail::gen_seq<sizeof...(Inner)>{}); } struct GetTupleDomainMapValue { Rng& rng; bool only_shrink; template <typename D, typename H> void operator()(const D& domain, H& value) { domain.Mutate(rng, value, only_shrink); } }; void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { unsigned index = rng.bounded(sizeof...(Inner)); GetTupleDomainMapValue visitor{rng, only_shrink}; detail::visit2_at(inner_domains, v, index, visitor); } }; #ifdef ZEROERR_ENABLE_PFR template <typename T, typename... Inner> AggregateOfImpl<T, Inner...> StructOf(Inner&&... inner) { return AggregateOfImpl<T, Inner...>(std::move(inner)...); } #endif template <typename... Inner> AggregateOf<std::tuple<typename Inner::ValueType...>, Inner...> TupleOf(Inner&&... inner) { return AggregateOf<std::tuple<typename Inner::ValueType...>, Inner...>(std::move(inner)...); } template <typename K, typename V> AggregateOf<std::pair<typename K::ValueType, typename V::ValueType>, K, V> PairOf(K&& k, V&& v) { return AggregateOf<std::pair<typename K::ValueType, typename V::ValueType>, K, V>(std::move(k), std::move(v)); } } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <limits> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH namespace zeroerr { /** * @brief Arbitrary is a domain that generates random values of a given type * * @tparam T The type to generate values for * @tparam N Template parameter for SFINAE-based specialization selection * * This domain provides default random value generation for common types. * It uses template specialization to handle different types appropriately. * * The base template is empty and specializations are provided for: * - bool * - unsigned integers * - signed integers * - floating point numbers * - strings * - containers * * Example: * ```cpp * auto domain = Arbitrary<int>(); // Generates random integers * auto domain = Arbitrary<std::string>(); // Generates random strings * ``` */ template <typename T, unsigned N = 2, typename = void> class Arbitrary : public Arbitrary<T, N-1> {}; template <typename T> struct Arbitrary <T, 0> { static_assert(detail::always_false<T>::value, "No Arbitrary specialization for this type"); }; template <> class Arbitrary<bool> : public DomainConvertable<bool> { public: using ValueType = bool; using CorpusType = bool; CorpusType GetRandomCorpus(Rng& rng) const override { return rng.bounded(2); } void Mutate(Rng&, CorpusType& v, bool) const override { v = !v; } }; template <typename T> using is_unsigned_int = typename std::enable_if<std::is_integral<T>::value && !std::numeric_limits<T>::is_signed, void>::type; template <typename T> class Arbitrary<T, 2, is_unsigned_int<T>> : public DomainConvertable<T> { public: using ValueType = T; using CorpusType = T; CorpusType GetRandomCorpus(Rng& rng) const override { return static_cast<T>(rng.bounded(100)); } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { v = static_cast<T>(rng.bounded(100)); } }; template <typename T> using is_signed_int = typename std::enable_if<std::is_integral<T>::value && std::numeric_limits<T>::is_signed, void>::type; template <typename T> class Arbitrary<T, 2, is_signed_int<T>> : public DomainConvertable<T> { public: using ValueType = T; using CorpusType = T; CorpusType GetRandomCorpus(Rng& rng) const override { return static_cast<T>(rng.bounded(100)); } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { v = static_cast<T>(rng.bounded(100)); v -= 50; } }; template <typename T> using is_float_point = typename std::enable_if<std::is_floating_point<T>::value, void>::type; template <typename T> class Arbitrary<T, 2, is_float_point<T>> : public DomainConvertable<T> { public: using ValueType = T; using CorpusType = T; CorpusType GetRandomCorpus(Rng& rng) const override { return static_cast<T>(rng.bounded(1000)); } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { v = static_cast<T>(rng.bounded(1000)); } }; template <typename T> using is_string = typename std::enable_if<detail::is_specialization<T, std::basic_string>::value>::type; template <typename T> class Arbitrary<T, 2, is_string<T>> : public Domain<T, std::vector<typename T::value_type>> { Arbitrary<std::vector<typename T::value_type>> impl; public: using ValueType = T; using CorpusType = std::vector<typename T::value_type>; ValueType GetValue(const CorpusType& v) const override { return ValueType(v.begin(), v.end()); } CorpusType FromValue(const ValueType& v) const override { return CorpusType(v.begin(), v.end()); } CorpusType GetRandomCorpus(Rng& rng) const override { return impl.GetRandomCorpus(rng); } void Mutate(Rng& rng, CorpusType& v, bool only_shrink) const override { impl.Mutate(rng, v, only_shrink); } }; template <typename T> using is_modifiable = typename std::enable_if<detail::is_modifiable<T>::value>::type; template <typename T> class Arbitrary<T, 1, is_modifiable<T>> : public SequenceContainerOf<T, Arbitrary<typename T::value_type>> { public: Arbitrary() : SequenceContainerOf<T, Arbitrary<typename T::value_type>>( Arbitrary<typename T::value_type>{}) {} }; template <typename T, typename U> class Arbitrary<std::pair<T, U>, 1> : public AggregateOf< std::pair<typename std::remove_const<T>::type, typename std::remove_const<U>::type>> {}; template <typename... T> class Arbitrary<std::tuple<T...>, 1> : public AggregateOf<std::tuple<typename std::remove_const<T>::type...>> {}; template <typename T> class Arbitrary<const T, 2> : public Arbitrary<T> {}; } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP /* * This benchmark component is modified from nanobench by Martin Ankerl * https://github.com/martinus/nanobench */ #pragma once #include <chrono> #include <cstdint> #include <string> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH #define ZEROERR_CREATE_BENCHMARK_FUNC(function, name, ...) \ static void function(zeroerr::TestContext*); \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ {name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::bench); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) #define BENCHMARK(name, ...) \ ZEROERR_CREATE_BENCHMARK_FUNC(ZEROERR_NAMEGEN(_zeroerr_benchmark), name, __VA_ARGS__) namespace zeroerr { /** * @brief PerfCountSet is a set of performance counters. */ template <typename T> struct PerfCountSet { T iterations{}; T data[7]{}; T& timeElapsed() { return data[0]; } T& pageFaults() { return data[1]; } T& cpuCycles() { return data[2]; } T& contextSwitches() { return data[3]; } T& instructions() { return data[4]; } T& branchInstructions() { return data[5]; } T& branchMisses() { return data[6]; } }; using Clock = std::conditional<std::chrono::high_resolution_clock::is_steady, std::chrono::high_resolution_clock, std::chrono::steady_clock>::type; namespace detail { struct LinuxPerformanceCounter; struct WindowsPerformanceCounter; } // namespace detail /** * @brief PerformanceCounter is a class to measure the performance of a function. */ struct PerformanceCounter { PerformanceCounter(); ~PerformanceCounter(); void beginMeasure(); void endMeasure(); void updateResults(uint64_t numIters); const PerfCountSet<uint64_t>& val() const noexcept { return _val; } PerfCountSet<bool> has() const noexcept { return _has; } static PerformanceCounter& inst(); Clock::duration elapsed; protected: Clock::time_point _start; PerfCountSet<uint64_t> _val; PerfCountSet<bool> _has; detail::LinuxPerformanceCounter* _perf = nullptr; detail::WindowsPerformanceCounter* win_perf = nullptr; }; /** * @brief BenchResult is a result of running the benchmark. */ struct BenchResult { enum Measure { time_elapsed = 1 << 0, iterations = 1 << 1, page_faults = 1 << 2, cpu_cycles = 1 << 3, context_switches = 1 << 4, instructions = 1 << 5, branch_instructions = 1 << 6, branch_misses = 1 << 7, all = (1 << 8) - 1, }; std::string name; std::vector<PerfCountSet<double>> epoch_details; PerfCountSet<bool> has; PerfCountSet<double> average() const; PerfCountSet<double> min() const; PerfCountSet<double> max() const; PerfCountSet<double> mean() const; }; struct Benchmark; struct BenchState; BenchState* createBenchState(Benchmark& benchmark); void destroyBenchState(BenchState* state); size_t getNumIter(BenchState* state); void runIteration(BenchState* state); void moveResult(BenchState* state, std::string name); /** * @brief Benchmark create a core object for configuration of a benchmark. * This class is a driver to run multiple times of a benchmark. Each time of a run will generate a * row of data. Report will print the data in console. */ struct Benchmark { std::string title = "benchmark"; const char* op_unit = "op"; const char* time_unit = "ns"; uint64_t epochs = 10; uint64_t warmup = 0; uint64_t iter_per_epoch = 0; using ns = std::chrono::nanoseconds; using ms = std::chrono::milliseconds; using time = ns; time mMaxEpochTime = ms(100); time mMinEpochTime = ms(1); uint64_t minimalResolutionMutipler = 1000; Benchmark(std::string title) { this->title = title; } template <typename Op> Benchmark& run(std::string name, Op&& op) { auto* s = createBenchState(*this); auto& pc = PerformanceCounter::inst(); while (auto n = getNumIter(s)) { pc.beginMeasure(); while (n-- > 0) op(); pc.endMeasure(); runIteration(s); } moveResult(s, name); return *this; } template <typename Op> Benchmark& run(Op&& op) { return run("", std::forward<Op>(op)); } std::vector<BenchResult> result; void report(); }; namespace detail { #if defined(_MSC_VER) void doNotOptimizeAwaySink(const void*); template <typename T> void doNotOptimizeAway(const T& val) { doNotOptimizeAwaySink(&val); } #else // These assembly magic is directly from what Google Benchmark is doing. I have previously used // what facebook's folly was doing, but this seemed to have compilation problems in some cases. // Google Benchmark seemed to be the most well tested anyways. see // https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307 template <typename T> void doNotOptimizeAway(const T& val) { // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : : "r,m"(val) : "memory"); } template <typename T> void doNotOptimizeAway(T& val) { #if defined(__clang__) // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : "+r,m"(val) : : "memory"); #else // NOLINTNEXTLINE(hicpp-no-assembler) asm volatile("" : "+m,r"(val) : : "memory"); #endif } #endif } // namespace detail /** * @brief Makes sure none of the given arguments are optimized away by the compiler. * * @tparam Arg Type of the argument that shouldn't be optimized away. * @param arg The input that we mark as being used, even though we don't do anything with it. */ template <typename Arg> void doNotOptimizeAway(Arg&& arg) { detail::doNotOptimizeAway(std::forward<Arg>(arg)); } } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <cstdint> #include <exception> #include <iostream> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH // This macro will be redefined in the log.h header, so the global context variable could be // envolved only when we use log.h at the same time. If you didn't use log.h, this is still a // header-only library. #ifndef ZEROERR_G_CONTEXT_SCOPE #define ZEROERR_G_CONTEXT_SCOPE(x) #endif /** * @brief Default printer for assertion messages * * This macro defines the default printer for assertion messages. * It prints the assertion message in different colors based on the assertion level. * * The macro can be overridden by defining ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER before * including this header. (Or undefine it and implement your own printer.) */ #ifndef ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER #define ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, ...) \ do { \ if (cond) { \ switch (zeroerr::assert_level::ZEROERR_CAT(level, _l)) { \ case zeroerr::assert_level::ZEROERR_WARN_l: \ std::cerr << zeroerr::FgYellow << "WARN" << zeroerr::Reset; \ break; \ case zeroerr::assert_level::ZEROERR_ERROR_l: \ std::cerr << zeroerr::FgRed << "ERROR" << zeroerr::Reset; \ break; \ case zeroerr::assert_level::ZEROERR_FATAL_l: \ std::cerr << zeroerr::FgMagenta << "FATAL" << zeroerr::Reset; \ break; \ } \ std::cerr << zeroerr::format(__VA_ARGS__) << std::endl; \ } \ } while (0) #endif #ifdef ZEROERR_OS_WINDOWS #define ZEROERR_PRINT_ASSERT(cond, level, pattern, ...) \ ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, " Assertion Failed:\n{msg}" pattern, \ assertion_data.log(), __VA_ARGS__) #else ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments") #define ZEROERR_PRINT_ASSERT(cond, level, pattern, ...) \ ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, " Assertion Failed:\n{msg}" pattern, \ assertion_data.log(), ##__VA_ARGS__) ZEROERR_CLANG_SUPPRESS_WARNING_POP #endif #define ZEROERR_ASSERT_EXP(cond, level, expect_throw, is_false, ...) \ ZEROERR_FUNC_SCOPE_BEGIN { \ zeroerr::assert_info info{zeroerr::assert_level::ZEROERR_CAT(level, _l), \ zeroerr::assert_throw::expect_throw, is_false}; \ \ zeroerr::AssertionData assertion_data(__FILE__, __LINE__, #cond, info); \ try { \ assertion_data.setResult(zeroerr::ExpressionDecomposer() << cond); \ } catch (const std::exception& e) { \ assertion_data.setException(e); \ } \ zeroerr::detail::context_helper< \ decltype(_ZEROERR_TEST_CONTEXT), \ std::is_same<decltype(_ZEROERR_TEST_CONTEXT), \ const bool>::value>::setContext(assertion_data, _ZEROERR_TEST_CONTEXT); \ ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, __VA_ARGS__); \ if (false) debug_break(); \ assertion_data(); \ ZEROERR_FUNC_SCOPE_RET(assertion_data.passed); \ } \ ZEROERR_FUNC_SCOPE_END #define ZEROERR_ASSERT_CMP(lhs, op, rhs, level, expect_throw, is_false, ...) \ ZEROERR_FUNC_SCOPE_BEGIN { \ zeroerr::assert_info info{zeroerr::assert_level::ZEROERR_CAT(level, _l), \ zeroerr::assert_throw::expect_throw, is_false}; \ \ zeroerr::Printer print; \ print.isQuoted = false; \ zeroerr::AssertionData assertion_data(__FILE__, __LINE__, #lhs " " #op " " #rhs, info); \ try { \ assertion_data.setResult({(lhs)op(rhs), print(lhs, #op, rhs)}); \ } catch (const std::exception& e) { \ assertion_data.setException(e); \ } \ zeroerr::detail::context_helper< \ decltype(_ZEROERR_TEST_CONTEXT), \ std::is_same<decltype(_ZEROERR_TEST_CONTEXT), \ const bool>::value>::setContext(assertion_data, _ZEROERR_TEST_CONTEXT); \ ZEROERR_PRINT_ASSERT(assertion_data.passed == false, level, __VA_ARGS__); \ if (false) debug_break(); \ assertion_data(); \ ZEROERR_FUNC_SCOPE_RET(assertion_data.passed); \ } \ ZEROERR_FUNC_SCOPE_END #ifdef ZEROERR_NO_ASSERT #define CHECK(...) #define CHECK_NOT(...) #define CHECK_THROWS(...) #define REQUIRE(...) #define REQUIRE_NOT(...) #define REQUIRE_THROWS(...) #define ASSERT(...) #define ASSERT_NOT(...) #define ASSERT_THROWS(...) #define CHECK_EQ(...) #define CHECK_NE(...) #define CHECK_LT(...) #define CHECK_LE(...) #define CHECK_GT(...) #define CHECK_GE(...) #define REQUIRE_EQ(...) #define REQUIRE_NE(...) #define REQUIRE_LT(...) #define REQUIRE_LE(...) #define REQUIRE_GT(...) #define REQUIRE_GE(...) #define ASSERT_EQ(...) #define ASSERT_NE(...) #define ASSERT_LT(...) #define ASSERT_LE(...) #define ASSERT_GT(...) #define ASSERT_GE(...) #else // clang-format off ZEROERR_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wgnu-zero-variadic-macro-arguments") #define ZEROERR_CHECK(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_WARN, no_throw, false, __VA_ARGS__)) #define ZEROERR_CHECK_NOT(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_WARN, no_throw, true, __VA_ARGS__)) #define ZEROERR_CHECK_THROWS(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_WARN, throws, false, __VA_ARGS__)) #define ZEROERR_REQUIRE(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_ERROR, no_throw, false, __VA_ARGS__)) #define ZEROERR_REQUIRE_NOT(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_ERROR, no_throw, true, __VA_ARGS__)) #define ZEROERR_REQUIRE_THROWS(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_ERROR, throws, false, __VA_ARGS__)) #define ZEROERR_ASSERT(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_FATAL, no_throw, false, __VA_ARGS__)) #define ZEROERR_ASSERT_NOT(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_FATAL, no_throw, true, __VA_ARGS__)) #define ZEROERR_ASSERT_THROWS(cond, ...) ZEROERR_EXPAND(ZEROERR_ASSERT_EXP(cond, ZEROERR_FATAL, throws, false, __VA_ARGS__)) #define CHECK(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_NOT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_NOT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_THROWS(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_THROWS(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_NOT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_NOT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_THROWS(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_THROWS(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_NOT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_NOT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_THROWS(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_THROWS(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ZEROERR_CHECK_EQ(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, ==, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_CHECK_NE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, !=, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_CHECK_LT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_CHECK_LE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <=, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_CHECK_GT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_CHECK_GE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >=, rhs, ZEROERR_WARN, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_EQ(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, ==, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_NE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, !=, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_LT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_LE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <=, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_GT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_REQUIRE_GE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >=, rhs, ZEROERR_ERROR, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_EQ(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, ==, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_NE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, !=, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_LT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_LE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, <=, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_GT(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define ZEROERR_ASSERT_GE(lhs, rhs, ...) ZEROERR_ASSERT_CMP(lhs, >=, rhs, ZEROERR_FATAL, no_throw, false, __VA_ARGS__) #define CHECK_EQ(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_EQ(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_NE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_NE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_LT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_LT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_LE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_LE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_GT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_GT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define CHECK_GE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_CHECK_GE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_EQ(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_EQ(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_NE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_NE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_LT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_LT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_LE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_LE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_GT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_GT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define REQUIRE_GE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_REQUIRE_GE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_EQ(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_EQ(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_NE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_NE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_LT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_LT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_LE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_LE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_GT(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_GT(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ASSERT_GE(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_ASSERT_GE(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP ZEROERR_CLANG_SUPPRESS_WARNING_POP // clang-format on #endif // This symbol must be in the global namespace or anonymous namespace // used for checking the assert is inside testing or not namespace { constexpr bool _ZEROERR_TEST_CONTEXT = false; } // namespace namespace zeroerr { enum class assert_level : uint8_t { ZEROERR_WARN_l, ZEROERR_ERROR_l, ZEROERR_FATAL_l }; enum class assert_throw : uint8_t { no_throw, throws, throws_as }; enum class assert_cmp : uint8_t { eq, ne, gt, ge, lt, le }; /** * @brief This is a one-byte assert info struct, which is used to collect the meta info of an * assertion */ struct assert_info { assert_level level : 2; assert_throw throw_type : 2; bool is_false : 1; }; /** * @brief AssertionData is a struct that contains all the information of an assertion. * It will be thrown as an exception when the assertion failed. */ struct AssertionData : std::exception { const char* file; // file name unsigned line; // line number assert_info info; // assert info bool passed; // if the assertion passed std::string message; // the message of the assertion std::string cond; // the condition of the assertion AssertionData(const char* file, unsigned line, const char* cond, assert_info info) : file(file), line(line), info(info) { static std::string pattern = "zeroerr::ExpressionDecomposer() << "; std::string cond_str(cond); size_t pos = cond_str.find(pattern); if (pos != std::string::npos) cond_str.replace(pos, pos + pattern.size(), ""); this->cond = cond_str; } void setResult(ExprResult&& result) { ExprResult r(std::move(result)); if (info.is_false) passed = !r.res; else passed = r.res; message = r.decomp; } void setException(const std::exception& e) { passed = info.throw_type == assert_throw::throws; message = e.what(); } std::string log() { std::stringstream ss; ss << " " << cond << " expands to " << message << std::endl; ss << Dim << "(" << file << ":" << line << ")" << Reset << std::endl; return ss.str(); } // throw the exception void operator()() { if (passed) return; if (shouldThrow()) throw *this; } bool shouldThrow() { return info.level != assert_level::ZEROERR_WARN_l; } }; namespace detail { // This struct is used for handle constexpr if in C++11 // https://stackoverflow.com/questions/43587405/constexpr-if-alternative template <typename T, bool> struct context_helper; template <typename T> struct context_helper<T, true> { static void setContext(AssertionData& data, T) { if (data.passed) return; } }; template <typename T> struct context_helper<T, false> { static void setContext(AssertionData& data, T ctx) { if (data.passed) { ctx->passed_as++; return; } switch (data.info.level) { case assert_level::ZEROERR_FATAL_l: case assert_level::ZEROERR_ERROR_l: ctx->failed_as++; break; case assert_level::ZEROERR_WARN_l: ctx->warning_as++; break; } } }; } // namespace detail } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <iostream> #include <tuple> // for std::get and std::tie ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH #ifndef ZEROERR_DISABLE_DBG_MARCO #define dbg(...) zeroerr::DebugExpr(__FILE__, __LINE__, __func__, #__VA_ARGS__, __VA_ARGS__) #else #define dbg(...) (__VA_ARGS__) #endif namespace zeroerr { template <class T1, class... T> struct last { using type = typename last<T...>::type; }; template <class T1> struct last<T1> { using type = T1; }; /** * @brief get_last is a function to get the last argument of a variadic template. * It is used by DebugExpr. */ template <typename... Args> auto get_last(Args&&... args) -> typename last<Args...>::type { return std::get<sizeof...(Args) - 1>(std::tie(args...)); } /** * @brief DebugExpr is a function to print any type of variable with its type name. * It is used by dbg macro. */ template <typename... T> auto DebugExpr(const char* file, unsigned line, const char* func, const char* exprs, T... t) -> typename last<T...>::type { std::string fileName(file); auto p = fileName.find_last_of('/'); if (p != std::string::npos) fileName = fileName.substr(p + 1); std::cerr << Dim << "[" << fileName << ":" << line << " " << func << "] " << Reset; std::cerr << FgCyan << exprs << Reset << " = "; Printer print(std::cerr); print.line_break = ""; print(t...); std::cerr << " (" << FgGreen; std::string typenames[] = {print.type(t)...}; for (unsigned i = 0; i < sizeof...(T); ++i) { if (i != 0) std::cerr << ", "; std::cerr << typenames[i]; } std::cerr << Reset << ")" << std::endl; return get_last(t...); } } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <sstream> #include <string> namespace zeroerr { /** * @brief Format a string with arguments * @param fmt The format string * @param args The arguments * @return std::string The formatted string * * This function is used to format a string with arguments. The format string * is a string with placeholders in the form of `{}`. You can pass any type of * arguments to this function and it will format the string accordingly. * * Example: * format("Hello, {name}!", "John") -> "Hello, John!" * */ template <typename... T> std::string format(const char* fmt, T... args) { std::stringstream ss; bool parse_name = false; Printer print; print.isQuoted = false; print.isCompact = true; print.line_break = ""; std::string str_args[] = {print(args)...}; int j = 0; for (const char* i = fmt; *i != '\0'; i++) { switch (*i) { case '{': parse_name = true; break; case '}': parse_name = false; ss << str_args[j++]; break; default: if (!parse_name) ss << *i; break; } } return ss.str(); } } // namespace zeroerr #pragma once #include <chrono> #include <iosfwd> #include <map> #include <string> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH extern const char* ZEROERR_LOG_CATEGORY; namespace zeroerr { // clang-format off #define ZEROERR_INFO(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_INFO_(__VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ZEROERR_LOG(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_LOG_(LOG_l, __VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ZEROERR_WARN(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_LOG_(WARN_l, __VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ZEROERR_ERROR(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_LOG_(ERROR_l, __VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP #define ZEROERR_FATAL(...) ZEROERR_SUPPRESS_VARIADIC_MACRO ZEROERR_EXPAND(ZEROERR_LOG_(FATAL_l, __VA_ARGS__)) ZEROERR_SUPPRESS_VARIADIC_MACRO_POP // clang-format on #ifdef ZEROERR_USE_SHORT_LOG_MACRO #ifdef INFO #undef INFO #endif #ifdef LOG #undef LOG #endif #ifdef WARN #undef WARN #endif #ifdef ERR #undef ERR #endif #ifdef FATAL #undef FATAL #endif #ifdef VERBOSE #undef VERBOSE #endif #define INFO(...) ZEROERR_INFO(__VA_ARGS__) #define LOG(...) ZEROERR_LOG(__VA_ARGS__) #define WARN(...) ZEROERR_WARN(__VA_ARGS__) #define ERR(...) ZEROERR_ERROR(__VA_ARGS__) #define FATAL(...) ZEROERR_FATAL(__VA_ARGS__) #define VERBOSE(v) ZEROERR_VERBOSE(v) #define LOG_GET(func, id, name, type) ZEROERR_LOG_GET(func, id, name, type) #endif // ZEROERR_USE_SHORT_LOG_MACRO #define ZEROERR_LOG_IF(condition, ACTION, ...) \ do { \ if (condition) ACTION(__VA_ARGS__); \ } while (0) #define INFO_IF(cond, ...) ZEROERR_LOG_IF(cond, ZEROERR_INFO, __VA_ARGS__) #define LOG_IF(cond, ...) ZEROERR_LOG_IF(cond, ZEROERR_LOG, __VA_ARGS__) #define WARN_IF(cond, ...) ZEROERR_LOG_IF(cond, ZEROERR_WARN, __VA_ARGS__) #define ERR_IF(cond, ...) ZEROERR_LOG_IF(cond, ZEROERR_ERROR, __VA_ARGS__) #define FATAL_IF(cond, ...) ZEROERR_LOG_IF(cond, ZEROERR_FATAL, __VA_ARGS__) #define ZEROERR_LOG_EVERY_(n, ACTION, ...) \ do { \ static unsigned counter = 0; \ if (counter == 0) { \ counter = n; \ ACTION(__VA_ARGS__); \ } \ --counter; \ } while (0) #define INFO_EVERY_(n, ...) ZEROERR_LOG_EVERY_(n, ZEROERR_INFO, __VA_ARGS__) #define LOG_EVERY_(n, ...) ZEROERR_LOG_EVERY_(n, ZEROERR_LOG, __VA_ARGS__) #define WARN_EVERY_(n, ...) ZEROERR_LOG_EVERY_(n, ZEROERR_WARN, __VA_ARGS__) #define ERR_EVERY_(n, ...) ZEROERR_LOG_EVERY_(n, ZEROERR_ERROR, __VA_ARGS__) #define FATAL_EVERY_(n, ...) ZEROERR_LOG_EVERY_(n, ZEROERR_FATAL, __VA_ARGS__) #define ZEROERR_LOG_IF_EVERY_(n, cond, ACTION, ...) \ do { \ if (cond) { \ static unsigned counter = 0; \ if (counter == 0) { \ counter = n; \ ACTION(__VA_ARGS__); \ } \ --counter; \ } \ } while (0) #define INFO_IF_EVERY_(n, cond, ...) ZEROERR_LOG_IF_EVERY_(n, cond, ZEROERR_INFO, __VA_ARGS__) #define LOG_IF_EVERY_(n, cond, ...) ZEROERR_LOG_IF_EVERY_(n, cond, ZEROERR_LOG, __VA_ARGS__) #define WARN_IF_EVERY_(n, cond, ...) ZEROERR_LOG_IF_EVERY_(n, cond, ZEROERR_WARN, __VA_ARGS__) #define ERR_IF_EVERY_(n, cond, ...) ZEROERR_LOG_IF_EVERY_(n, cond, ZEROERR_ERROR, __VA_ARGS__) #define FATAL_IF_EVERY_(n, cond, ...) ZEROERR_LOG_IF_EVERY_(n, cond, ZEROERR_FATAL, __VA_ARGS__) #define ZEROERR_LOG_FIRST(cond, ACTION, ...) \ do { \ static bool first = true; \ if (first && (cond)) { \ first = false; \ ACTION(__VA_ARGS__); \ } \ } while (0) #define INFO_FIRST(cond, ...) ZEROERR_LOG_FIRST(cond, ZEROERR_INFO, __VA_ARGS__) #define LOG_FIRST(cond, ...) ZEROERR_LOG_FIRST(cond, ZEROERR_LOG, __VA_ARGS__) #define WARN_FIRST(cond, ...) ZEROERR_LOG_FIRST(cond, ZEROERR_WARN, __VA_ARGS__) #define ERR_FIRST(cond, ...) ZEROERR_LOG_FIRST(cond, ZEROERR_ERROR, __VA_ARGS__) #define FATAL_FIRST(cond, ...) ZEROERR_LOG_FIRST(cond, ZEROERR_FATAL, __VA_ARGS__) #define ZEROERR_LOG_FIRST_(n, cond, ACTION, ...) \ do { \ static unsigned counter = n; \ if (counter && (cond)) { \ counter--; \ ACTION(__VA_ARGS__); \ } \ } while (0) #define INFO_FIRST_(n, cond, ...) ZEROERR_LOG_FIRST_(n, cond, ZEROERR_INFO, __VA_ARGS__) #define LOG_FIRST_(n, cond, ...) ZEROERR_LOG_FIRST_(n, cond, ZEROERR_LOG, __VA_ARGS__) #define WARN_FIRST_(n, cond, ...) ZEROERR_LOG_FIRST_(n, cond, ZEROERR_WARN, __VA_ARGS__) #define ERR_FIRST_(n, cond, ...) ZEROERR_LOG_FIRST_(n, cond, ZEROERR_ERROR, __VA_ARGS__) #define FATAL_FIRST_(n, cond, ...) ZEROERR_LOG_FIRST_(n, cond, ZEROERR_FATAL, __VA_ARGS__) #ifdef _DEBUG #define DLOG(ACTION, ...) ZEROERR_EXPAND(ACTION(__VA_ARGS__)) #else #define DLOG(ACTION, ...) #endif extern int _ZEROERR_G_VERBOSE; #define ZEROERR_VERBOSE(v) if (zeroerr::_ZEROERR_G_VERBOSE >= (v)) #define ZEROERR_LOG_(severity, message, ...) \ do { \ ZEROERR_G_CONTEXT_SCOPE(true); \ auto msg = zeroerr::log(__VA_ARGS__); \ \ static zeroerr::LogInfo log_info{__FILE__, \ __func__, \ message, \ ZEROERR_LOG_CATEGORY, \ __LINE__, \ msg.size, \ zeroerr::LogSeverity::severity}; \ msg.log->info = &log_info; \ if (msg.stream.getFlushMode() == zeroerr::LogStream::FlushMode::FLUSH_AT_ONCE) \ msg.stream.flush(); \ } while (0) #define ZEROERR_INFO_(...) \ ZEROERR_INFO_IMPL(ZEROERR_NAMEGEN(_capture_), ZEROERR_NAMEGEN(_capture_), __VA_ARGS__) #define ZEROERR_INFO_IMPL(mb_name, v_name, ...) \ auto v_name = zeroerr::MakeContextScope([&](std::ostream& _capture_name) { \ Printer print(_capture_name); \ print.isQuoted = false; \ print(__VA_ARGS__); \ }) #ifdef ZEROERR_G_CONTEXT_SCOPE #undef ZEROERR_G_CONTEXT_SCOPE #endif #define ZEROERR_G_CONTEXT_SCOPE(x) \ if (x) { \ for (auto* i : zeroerr::_ZEROERR_G_CONTEXT_SCOPE_VECTOR) { \ i->str(std::cerr); \ } \ } #ifdef ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER #undef ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER #endif #define ZEROERR_PRINT_ASSERT_DEFAULT_PRINTER(cond, level, ...) \ ZEROERR_LOG_IF(cond, level, __VA_ARGS__) // This macro can access the log in memory #define ZEROERR_LOG_GET(func, id, name, type) \ zeroerr::LogStream::getDefault().getLog<type>(#func, id, #name) namespace detail { template <typename T, unsigned... I> std::string gen_str(const char* msg, const T& args, seq<I...>) { return format(msg, std::get<I>(args)...); } template <typename T> std::string gen_str(const char* msg, const T&, seq<>) { return msg; } } // namespace detail enum LogSeverity { INFO_l, // it will not write to file if no other log related LOG_l, WARN_l, ERROR_l, FATAL_l, // it will contain a stack trace }; /** * @brief LogInfo is a struct to store the meta data of the log message. * @details LogInfo is a struct to store the meta data of the log message. * It contains filename, function, message, category, line number, size, and severity. * Those data is initialized when the first log message is created using a static * local variable in the function where the log message is put. * * For example: * void foo() { * log("Hello, {name}!", "John"); * } * * The inner implementation could be considered as (not exactly * since message is allocated from a pool): * void foo() { * static LogInfo log_info{ * __FILE__, __func__, "Hello, {name}!", * ZEROERR_LOG_CATEGORY, * __LINE__, * sizeof("Hello, world!"), * LogSeverity::INFO_l); * LogMessage* logdata = new LogMessageImpl<std::string>("John"); * logdata->info = &log_info; * } */ struct LogInfo { const char* filename; const char* function; const char* message; const char* category; unsigned line; unsigned size; LogSeverity severity; std::map<std::string, int> names; LogInfo(const char* filename, const char* function, const char* message, const char* category, unsigned line, unsigned size, LogSeverity severity); }; struct LogMessage; typedef std::string (*LogCustomCallback)(const LogMessage&, bool colorful); /** * @brief set the log level */ extern void setLogLevel(LogSeverity level); /** * @brief set the log category */ extern void setLogCategory(const char* categories); /** * @brief set the log custom callback, this can support custom format of the log message */ extern void setLogCustomCallback(LogCustomCallback callback); /** * @brief suspend the log to flush to the file */ extern void suspendLog(); /** * @brief resume the log to flush to the file */ extern void resumeLog(); /** * @brief LogMessage is a class to store the log message. * @details LogMessage is a class to store the log message and a base class * for all the messages implementation. You can create a log message with any * type of arguments and it will store the arguments in a tuple. * The log message can be converted to a string with the str() function. * You can also get the raw pointer of the arguments with the getRawLog() function. */ struct LogMessage { // time is assigned when the log message is created LogMessage() { time = std::chrono::system_clock::now(); } // convert the log message to a string virtual std::string str() const = 0; // get the raw data pointer of the field with the name virtual void* getRawLog(std::string name) const = 0; // a map of the data indexing by the field name // for example: log("print {i}", 1); // a map of {"i": "1"} will be returned virtual std::map<std::string, std::string> getData() const = 0; // meta data of this log message const LogInfo* info; // recorded wall time std::chrono::system_clock::time_point time; }; /** * @brief LogMessageImpl is the implementation of the LogMessage. * @details LogMessageImpl is the implementation of the LogMessage. It stores * the arguments in a tuple and provides the str() function to convert the log * message to a string. All fields could be accessed by getRawLog() or getData(). */ template <typename... T> struct LogMessageImpl final : LogMessage { std::tuple<T...> args; LogMessageImpl(T... args) : LogMessage(), args(args...) {} std::string str() const override { return gen_str(info->message, args, detail::gen_seq<sizeof...(T)>{}); } // This is a helper class to get the raw pointer of the tuple struct GetTuplePtr { void* ptr = nullptr; template <typename H> void operator()(H& v) { ptr = (void*)&v; } }; void* getRawLog(std::string name) const override { GetTuplePtr f; detail::visit_at(args, info->names.at(name), f); return f.ptr; } struct PrintTupleData { std::map<std::string, std::string> data; Printer print; std::string name; PrintTupleData() : print() { print.isCompact = true; print.line_break = ""; } template <typename H> void operator()(H& v) { data[name] = print(v); } }; std::map<std::string, std::string> getData() const override { PrintTupleData printer; for (auto it = info->names.begin(); it != info->names.end(); ++it) { printer.name = it->first; detail::visit_at(args, it->second, printer); } return printer.data; } }; struct DataBlock; class LogStream; class Logger { public: virtual ~Logger() = default; virtual void flush(DataBlock*) = 0; }; struct PushResult { LogMessage* log; unsigned size; LogStream& stream; }; /** * @brief LogIterator is a class to iterate the log messages. * @details LogIterator is a class to iterate the log messages. You can also filter * the log messages by message, function name, and line number. * * An example of using LogIterator: * for (int i = 0; i < 10; ++i) * log("i = {i}", i); * * LogIterator it = LogStream::getDefault().begin("Hello, world!"); * LogIterator end = LogStream::getDefault().end(); * * for (; it != end; ++it) { * LogMessage& msg = *it; * std::cout << msg.str() << std::endl; // "i = {i}" * std::cout << it.get<int>("i") << std::endl; // "0", "1", "2", ... * } */ class LogIterator { public: LogIterator() : p(nullptr), q(nullptr) {} LogIterator(LogStream& stream, std::string message = "", std::string function_name = "", int line = -1); LogIterator(const LogIterator& rhs) : p(rhs.p), q(rhs.q) {} LogIterator& operator=(const LogIterator& rhs) { p = rhs.p; q = rhs.q; return *this; } LogIterator& operator++(); LogIterator operator++(int) { LogIterator tmp = *this; ++*this; return tmp; } template <typename T> T get(std::string name) { void* data = q->getRawLog(name); if (data) return *(T*)(data); return T{}; } bool operator==(const LogIterator& rhs) const { return p == rhs.p && q == rhs.q; } bool operator!=(const LogIterator& rhs) const { return !(*this == rhs); } LogMessage& get() const { return *q; } LogMessage& operator*() const { return *q; } LogMessage* operator->() const { return q; } void check_at_safe_pos(); friend class LogStream; protected: bool check_filter(); void next(); DataBlock* p; LogMessage* q; std::string function_name_filter; std::string message_filter; int line_filter = -1; }; /** * @brief LogStream is a class to manage the log messages. * @details LogStream is a class to manage the log messages. It can be used to * create log messages and push them to the logger. A default LogStream is * created when the first time you call getDefault() function (or first log happens). * You can also adjust the way to flush the messages and how the log messages are * written to the log file. */ class LogStream { public: LogStream(); virtual ~LogStream(); enum FlushMode { FLUSH_AT_ONCE, FLUSH_WHEN_FULL, FLUSH_MANUALLY }; enum LogMode { ASYNC, SYNC }; enum DirMode { SINGLE_FILE = 0, DAILY_FILE = 1, SPLIT_BY_SEVERITY = 1 << 1, SPLIT_BY_CATEGORY = 1 << 2 }; /** * @brief push a log message to the stream * @tparam T The types of the arguments * @param args The arguments * @return PushResult The result of the push * * This function is used to push a log message to the stream. You can pass * any type of arguments to this function and it will create a log message * with the arguments. The log message is not written to the log file until * the stream is flushed. * * The log message is structured as a tuple of the arguments in the inner * implementation class LogMessageImpl. After the log message is created, it * used type erasure to return a LogMessage pointer to the caller. * * The stored data type is determined by the to_store_type_t<T> template. * For all the string type in raw pointer like const char* or char[], * it will be converted to std::string. * All reference type (including right value reference) will be converted * to the original type. */ template <typename... T> PushResult push(T&&... args) { // unsigned size = sizeof(LogMessageImpl<T...>); unsigned size = sizeof(LogMessageImpl<detail::to_store_type_t<T>...>); void* p; if (use_lock_free) p = alloc_block_lockfree(size); else p = alloc_block(size); // LogMessage* msg = new (p) LogMessageImpl<T...>(std::forward<T>(args)...); LogMessage* msg = new (p) LogMessageImpl<detail::to_store_type_t<T>...>(args...); return {msg, size, *this}; } /** * @brief get a log message from the stream * @tparam T The type of the log message * @param func The function name of the log message * @param line The line number of the log message * @param name The name of field you want to get * * This function is used to get a log message from the stream and extract * the field with the name. The function will return the field with the type * T. However, this type must be specified by the caller. */ template <typename T> T getLog(std::string func, unsigned line, std::string name) { void* data = getRawLog(func, line, name); if (data) return *(T*)(data); return T{}; } /** * @brief get a log message from the stream * @tparam T The type of the log message * @param func The function name of the log message * @param msg The message of the log message * @param name The name of field you want to get * * This function is used to get a log message from the stream and extract * the field with the name. The function will return the field with the type * T. However, this type must be specified by the caller. */ template <typename T> T getLog(std::string func, std::string msg, std::string name) { void* data = getRawLog(func, msg, name); if (data) return *(T*)(data); return T{}; } LogIterator begin(std::string message = "", std::string function_name = "", int line = -1) { return LogIterator(*this, message, function_name, line); } LogIterator end() { return LogIterator(); } LogIterator current(std::string message = "", std::string function_name = "", int line = -1); void flush(); void setFileLogger(std::string name, DirMode mode1 = SINGLE_FILE, DirMode mode2 = SINGLE_FILE, DirMode mode3 = SINGLE_FILE); void setStdoutLogger(); void setStderrLogger(); static LogStream& getDefault(); void setFlushAtOnce() { flush_mode = FLUSH_AT_ONCE; } void setFlushWhenFull() { flush_mode = FLUSH_WHEN_FULL; } void setFlushManually() { flush_mode = FLUSH_MANUALLY; } void setAsyncLog() { log_mode = ASYNC; } void setSyncLog() { log_mode = SYNC; } FlushMode getFlushMode() const { return flush_mode; } void setFlushMode(FlushMode mode) { flush_mode = mode; } LogMode getLogMode() const { return log_mode; } void setLogMode(LogMode mode) { log_mode = mode; } bool use_lock_free = true; friend class LogIterator; private: DataBlock *first, *prepare; ZEROERR_ATOMIC(DataBlock*) m_last; Logger* logger = nullptr; FlushMode flush_mode = FLUSH_AT_ONCE; LogMode log_mode = SYNC; #ifndef ZEROERR_NO_THREAD_SAFE std::mutex* mutex; #endif // The implementation of alloc objects by giving a size void* alloc_block(unsigned size); void* alloc_block_lockfree(unsigned size); // The implementation of getLog which returns a raw pointer // This way can reduce the overhead of code generation by template void* getRawLog(std::string func, unsigned line, std::string name); void* getRawLog(std::string func, std::string msg, std::string name); }; template <typename... T> PushResult log(T&&... args) { return LogStream::getDefault().push(std::forward<T>(args)...); } template <typename... T> PushResult log(LogStream& stream, T&&... args) { return stream.push(std::forward<T>(args)...); } /** * @brief ContextScope is a helper class created in each basic block where you use INFO(). * The context scope can has lazy evaluated function F(std::ostream&) that is called when the * assertation is failed */ class IContextScope { public: virtual void str(std::ostream& os) const = 0; }; extern thread_local std::vector<IContextScope*> _ZEROERR_G_CONTEXT_SCOPE_VECTOR; template <typename F> class ContextScope : public IContextScope { public: ContextScope(F f) : f_(f) { _ZEROERR_G_CONTEXT_SCOPE_VECTOR.push_back(this); } ~ContextScope() { _ZEROERR_G_CONTEXT_SCOPE_VECTOR.pop_back(); } virtual void str(std::ostream& os) const override { return f_(os); } protected: F f_; }; template <typename F> ContextScope<F> MakeContextScope(const F& f) { return ContextScope<F>(f); } } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <ostream> #include <string> #include <vector> namespace zeroerr { /** * @brief Card defines a display range in the console. */ struct Card { Card() : title(), width(0), height(0) {} Card(std::string title) : title(title), width(0), height(0) {} Card(unsigned width, unsigned height) : title(), width(width), height(height) {} Card(std::string title, unsigned width, unsigned height) : title(title), width(width), height(height) {} std::string title; unsigned width, height; void show(std::ostream& os, std::string str); }; /** * @brief Table is used to generate a table with configurable style. */ class Table : public Card { public: Table() : Card() {} Table(std::string title) : Card(title) {} Table(unsigned width, unsigned height) : Card(width, height) {} Table(std::string title, unsigned width, unsigned height) : Card(title, width, height) {} ~Table() {} /** * @brief Style is used to define the border style of the table. */ struct Style { Style() {} Style(std::initializer_list<std::string> args) : m_args(args) {} std::vector<std::string> m_args; operator bool() const { return !m_args.empty(); } }; static void registerStyle(std::string name, Style style); /** * @brief getStyle can load predefined style from the StyleManager. * @param name is the name of the style. * Available styles: * "ascii" * "ascii2" * "ascii_double_head" * "square" * "square_double_head" * "simple" * "simple_head" * "simple_heavy" * "horizontal" * "rounded" * "heavy" * "heavy_edge" * "heavy_head" * "double" * "double_edge" * "minimal" * "minimal_heavy_head" * "minimal_double_hea */ static Style getStyle(std::string name); /** * @brief Config is used to configure the table style how it is displayed. */ struct Config { bool show_tb_border; // show top and bottom border bool show_lr_border; // show left and right border bool show_header_split; // show header split bool show_col_split; // show column split bool show_row_split; // show row split bool show_footer_split; // show footer split Config() : show_tb_border(true), show_lr_border(true), show_header_split(true), show_col_split(true), show_row_split(true), show_footer_split(true) {} }; /** * @brief str is used to generate the table string. * @param config decides how the table is displayed. * @param style decides the border style of the table. */ std::string str(Config config = Config(), Style style = Table::getStyle("square_double_head")); void set_header(std::vector<std::string> _header) { header = _header; } void add_row(std::initializer_list<std::string> _row) { cells.push_back(_row); } template <typename T, typename... Args> void push_back(std::vector<std::string>& row, T&& t, Args&&... args) { _push_back(rank<2>{}, row, std::forward<T>(t)); push_back(row, std::forward<Args>(args)...); } template <typename T> void push_back(std::vector<std::string>& row, T&& t) { _push_back(rank<2>{}, row, std::forward<T>(t)); } template <typename... Args> void add_row(Args&&... args) { std::vector<std::string> row; push_back(row, std::forward<Args>(args)...); cells.push_back(row); } template <typename T, typename... Args> void add_rows(T&& t, Args&&... args) { add_row(std::forward<T>(t)); add_rows(std::forward<Args>(args)...); } template <typename T> void add_rows(T&& t) { add_row(std::forward<T>(t)); } protected: ZEROERR_ENABLE_IF(!ZEROERR_IS_CONTAINER) _push_back(rank<0>, std::vector<std::string>& row, T&& t) { Printer print; print.isCompact = true; print.isQuoted = false; print.line_break = ""; print(std::forward<T>(t)); row.push_back(print.str()); } ZEROERR_ENABLE_IF(ZEROERR_IS_STRING) _push_back(rank<2>, std::vector<std::string>& row, T t) { row.push_back(std::forward<std::string>(t)); } ZEROERR_ENABLE_IF(ZEROERR_IS_CONTAINER) _push_back(rank<1>, std::vector<std::string>& row, const T& t) { for (auto& ele : t) { Printer print; print.isCompact = true; print.isQuoted = false; print.line_break = ""; print(ele); row.push_back(print.str()); } } std::vector<unsigned> col_width; std::vector<std::string> header, footer; std::vector<std::vector<std::string>> cells; }; } // namespace zeroerr #pragma once #include <chrono> #include <functional> #include <string> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH #define ZEROERR_CREATE_TEST_FUNC(function, name, ...) \ static void function(zeroerr::TestContext*); \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ {name, __FILE__, __LINE__, function, {__VA_ARGS__}}); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) #define TEST_CASE(name, ...) \ ZEROERR_CREATE_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name, __VA_ARGS__) #define SUB_CASE(name, ...) \ zeroerr::SubCase(name, __FILE__, __LINE__, _ZEROERR_TEST_CONTEXT, {__VA_ARGS__}) \ << [=](ZEROERR_UNUSED(zeroerr::TestContext * _ZEROERR_TEST_CONTEXT)) mutable #define ZEROERR_CREATE_TEST_CLASS(fixture, classname, funcname, name, ...) \ class classname : public fixture { \ public: \ void funcname(zeroerr::TestContext*); \ }; \ static void ZEROERR_CAT(call_, funcname)(zeroerr::TestContext * _ZEROERR_TEST_CONTEXT) { \ classname instance; \ instance.funcname(_ZEROERR_TEST_CONTEXT); \ } \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ {name, __FILE__, __LINE__, ZEROERR_CAT(call_, funcname), {__VA_ARGS__}}); \ inline void classname::funcname(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) #define TEST_CASE_FIXTURE(fixture, name, ...) \ ZEROERR_CREATE_TEST_CLASS(fixture, ZEROERR_NAMEGEN(_zeroerr_class), \ ZEROERR_NAMEGEN(_zeroerr_test_method), name, __VA_ARGS__) #define ZEROERR_HAVE_SAME_OUTPUT _ZEROERR_TEST_CONTEXT->save_output(); #ifndef ZEROERR_DISABLE_BDD #define SCENARIO(...) TEST_CASE("Scenario: " __VA_ARGS__) #define GIVEN(...) SUB_CASE("given: " __VA_ARGS__) #define WHEN(...) SUB_CASE("when: " __VA_ARGS__) #define THEN(...) SUB_CASE("then: " __VA_ARGS__) #endif namespace zeroerr { class IReporter; struct TestCase; class Decorator; /** * @brief TestContext is a class that holds the test results and reporter context. * There are 8 different matrices that are used to store the test results. * * passed : Number of passed tests * * warning : Number of tests that passed with warning * * failed : Number of failed tests * * skipped : Number of skipped tests * * passed_as : Number of passed tests in assertion * * warning_as: Number of tests that passed with warning in assertion * * failed_as : Number of failed tests in assertion * * skipped_as: Number of skipped tests in assertion */ class TestContext { public: unsigned passed = 0; unsigned warning = 0; unsigned failed = 0; unsigned skipped = 0; unsigned passed_as = 0; unsigned warning_as = 0; unsigned failed_as = 0; unsigned skipped_as = 0; std::chrono::duration<double> duration = std::chrono::duration<double>::zero(); IReporter& reporter; /** * @brief Add the subtest results to the matrices. * @param local The local test context that will be added to the global context. * @return int 0 if the test passed, 1 if the test passed with warning, 2 if the test failed. */ int add(TestContext& local); /** * @brief Reset the matrices to 0. */ void reset(); /** * @brief Save the output of the test to the correct_output_path as a golden file. */ void save_output(); /** * @brief Construct a new Test Context object * @param reporter The reporter object that will be used to report the test results. */ TestContext(IReporter& reporter) : reporter(reporter) {} ~TestContext() = default; }; /** * @brief UnitTest is a class that holds the test configuration. * There are several options that can be set to configure the test. * * silent : If true, the test will not print the test results. * * run_bench : If true, the test will run the benchmark tests. * * run_fuzz : If true, the test will run the fuzz tests. * * list_test_cases : If true, the test will list the test cases. * * no_color : If true, the test will not print the test results with color. * * log_to_report : If true, the test will log the test results to the report. * * correct_output_path : The path that the golden files will be saved. * * reporter_name : The name of the reporter that will be used to report the test results. * * binary : The binary name that will be used to run the test. * * filters : The filters that will be used to filter the test cases. */ struct UnitTest { /** * @brief Parse the arguments to configure the test. * @param argc The number of arguments. * @param argv The arguments. * @return UnitTest& The test configuration. */ UnitTest& parseArgs(int argc, const char** argv); /** * @brief Run the test. * @return int 0 if the test passed. */ int run(); /** * @brief Run the test with the given filter. * @param tc The test case that will be run. * @return true If the test passed. * @return false If the test failed. */ bool run_filter(const TestCase& tc); bool silent = false; bool run_bench = false; bool run_fuzz = false; bool list_test_cases = false; bool no_color = false; bool log_to_report = false; std::string correct_output_path; std::string reporter_name = "console"; std::string binary; struct Filters* filters; }; /** * @brief TestCase is a class that holds the test case information. * There are several fields that are used to store the test case information. * * name : The name of the test case. * * file : The file that the test case is defined. * * line : The line that the test case is defined. * * func : The function that will be run to test the test case. * * subcases : The subcases that are defined in the test case. */ struct TestCase { std::string name; std::string file; unsigned line; std::function<void(TestContext*)> func; std::vector<TestCase*> subcases; std::vector<Decorator*> decorators; /** * @brief Compare the test cases. * @param rhs The test case that will be compared. * @return true If the test case is less than the rhs, otherwise false. */ bool operator<(const TestCase& rhs) const; /** * @brief Construct a new Test Case object * @param name The name of the test case. * @param file The file that the test case is defined. * @param line The line that the test case is defined. */ TestCase(std::string name, std::string file, unsigned line, std::vector<Decorator*> decorators) : name(name), file(file), line(line), decorators(decorators) {} /** * @brief Construct a new Test Case object * @param name The name of the test case. * @param file The file that the test case is defined. * @param line The line that the test case is defined. * @param func The function that will be run to test the test case. * @param decorators The decorators that will be used to decorate the test case. */ TestCase(std::string name, std::string file, unsigned line, std::function<void(TestContext*)> func, std::vector<Decorator*> decorators) : name(name), file(file), line(line), func(func), decorators(decorators) {} }; /** * @brief SubCase is a class that holds the subcase information. */ struct SubCase : TestCase { SubCase(std::string name, std::string file, unsigned line, TestContext* context, std::vector<Decorator*> decorators); ~SubCase() = default; TestContext* context; void operator<<(std::function<void(TestContext*)> op); }; template <typename T> struct TestedObjects { void add(T&& obj) { objects.push_back(std::forward<T>(obj)); } std::vector<T> objects; }; /** * @brief IReporter is an interface that is used to report the test results. * You can create a new reporter by inheriting this class and implementing the virtual functions. * The following events will be called once it happens during testing. * * testStart : called when the test starts. * * testCaseStart : called when the test case starts. * * testCaseEnd : called when the test case ends. * * subCaseStart : called when the subcase starts. * * subCaseEnd : called when the subcase ends. * * testEnd : called when the test ends. */ class IReporter { public: virtual ~IReporter() = default; virtual std::string getName() const = 0; // There are a list of events virtual void testStart() = 0; virtual void testCaseStart(const TestCase& tc, std::stringbuf& sb) = 0; virtual void testCaseEnd(const TestCase& tc, std::stringbuf& sb, const TestContext& ctx, int type) = 0; virtual void subCaseStart(const TestCase& tc, std::stringbuf& sb) = 0; virtual void subCaseEnd(const TestCase& tc, std::stringbuf& sb, const TestContext& ctx, int type) = 0; virtual void testEnd(const TestContext& tc) = 0; /** * @brief Create the reporter object with the given name. * @param name The name of the reporter. Available reporters are: console, xml. * @param ut The unit test object that will be used to configure the test. */ static IReporter* create(const std::string& name, UnitTest& ut); IReporter(UnitTest& ut) : ut(ut) {} protected: UnitTest& ut; }; /** * @brief TestType is a enum describe the type of the test case. */ enum TestType { test_case = 1, sub_case = 1 << 1, bench = 1 << 2, fuzz_test = 1 << 3 }; namespace detail { /** * @brief regTest is a class that is used to register the test case. * It will be used as global variables and the constructor will be called to register the test case. */ struct regTest { explicit regTest(const TestCase& tc, TestType type = test_case); }; /** * @brief regReporter is a class that is used to register the reporter. * It will be used as global variables and the constructor will be called to register the reporter. */ struct regReporter { explicit regReporter(IReporter*); }; } // namespace detail /** * @brief CombinationalTest is a class that is used to cross test a few lists of arguments. * One example * ```cpp * TestArgs<int> a{1, 2, 3}; * TestArgs<int> b{4, 5, 6}; * CombinationalTest test([&]{ * CHECK(targetFunc(a, b) == (a+b)); * }); * test(a, b); * ``` * * This will test the targetFunc with all the combinations of a and b, e.g. (1,4), (1,5), (1,6), * (2,4), (2,5) ... etc. */ class CombinationalTest { public: CombinationalTest(std::function<void()> func) : func(func) {} std::function<void()> func; template <typename T> void operator()(T& arg) { arg.reset(); for (size_t i = 0; i < arg.size(); ++i, ++arg) { func(); } } template <typename T, typename... Args> void operator()(T& arg, Args&... args) { arg.reset(); for (size_t i = 0; i < arg.size(); ++i, ++arg) { operator()(args...); } } }; /** * @brief TestArgs is a class that is used to store the test arguments. */ template <typename T> class TestArgs { public: TestArgs(std::initializer_list<T> args) : args(args) {} std::vector<T> args; operator T() const { return args[index]; } TestArgs& operator++() { index++; return *this; } size_t size() const { return args.size(); } void reset() { index = 0; } private: int index = 0; }; class Decorator { public: // Called when the test registered, return true can block the test registering virtual bool onStartup(const TestCase&) { return false; } // Called when the test executing, return true can block the test execution virtual bool onExecution(const TestCase&) { return false; } // Called on each assertion, return true can skip the assertion virtual bool onAssertion() { return false; } // Called when the test finished, return true means the test containing errors virtual bool onFinish(const TestCase&, const TestContext&) { return false; } }; Decorator* skip(bool isSkip = true); Decorator* timeout(float timeout = 0.1f); // in seconds Decorator* may_fail(bool isMayFail = true); Decorator* should_fail(bool isShouldFail = true); } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #pragma once #include <exception> #include <functional> #include <string> #include <tuple> #include <type_traits> #include <vector> ZEROERR_SUPPRESS_COMMON_WARNINGS_PUSH #define ZEROERR_CREATE_FUZZ_TEST_FUNC(function, name, ...) \ static void function(zeroerr::TestContext*); \ static zeroerr::detail::regTest ZEROERR_NAMEGEN(_zeroerr_reg)( \ {name, __FILE__, __LINE__, function, {__VA_ARGS__}}, zeroerr::TestType::fuzz_test); \ static void function(ZEROERR_UNUSED(zeroerr::TestContext* _ZEROERR_TEST_CONTEXT)) #define FUZZ_TEST_CASE(name, ...) \ ZEROERR_CREATE_FUZZ_TEST_FUNC(ZEROERR_NAMEGEN(_zeroerr_testcase), name, __VA_ARGS__) #define FUZZ_FUNC(func) zeroerr::FuzzFunction(func, _ZEROERR_TEST_CONTEXT) namespace zeroerr { namespace detail { /** * @brief FunctionDecomposition is a meta function to decompose the function type. * The ValueType and numOfArgs will be the result of the decomposition. */ template <typename... Args> struct FunctionDecomposition { static constexpr size_t numOfArgs = sizeof...(Args); using ValueType = std::tuple<Args...>; }; template <typename... Args> FunctionDecomposition<typename std::decay<Args>::type...> FunctionDecompositionImpl( void (*)(Args...)); template <typename... Args> FunctionDecomposition<typename std::decay<Args>::type...> FunctionDecompositionImpl( std::function<void(Args...)>); template <typename T> struct memfun_type { using type = void; }; template <typename Ret, typename Class, typename... Args> struct memfun_type<Ret (Class::*)(Args...) const> { using ret_type = FunctionDecomposition<typename std::decay<Args>::type...>; }; template <typename F> typename memfun_type<decltype(&F::operator())>::ret_type FunctionDecompositionImpl(F); template <typename T> using FunctionDecompositionT = decltype(FunctionDecompositionImpl(std::declval<T>())); } // namespace detail /** * @brief FuzzTest is a template class to create a fuzz test for the target function. * @tparam TargetFunction The target function to fuzz. * @tparam FuncType is the decomposition result of the target function. */ template <typename TargetFunction, typename FuncType = detail::FunctionDecompositionT<TargetFunction>> struct FuzzTest; template <typename TargetFunction, typename FuncType, typename Domain, typename Base = FuzzTest<TargetFunction, FuncType>> struct FuzzTestWithDomain; struct IFuzzTest { virtual void Run(int count = 1000, int seed = 0) = 0; virtual void RunOneTime(const uint8_t* data, size_t size) = 0; virtual std::string MutateData(const uint8_t* data, size_t size, size_t max_size, unsigned int seed) = 0; int count = 0, max_count = 0; bool should_stop() { return count == max_count; } }; class FuzzFinishedException : public std::exception { public: virtual const char* what() const throw() { return "Fuzzing finished"; } }; // The details are described in the declaration of the class. template <typename TargetFunction, typename FuncType> struct FuzzTest : IFuzzTest { using ValueType = typename FuncType::ValueType; FuzzTest(TargetFunction func, TestContext* context) : func(func), context(context) {} FuzzTest(const FuzzTest& other) : func(other.func), context(other.context), seeds(other.seeds) {} template <typename... T> using FuzzTestWithDomainType = FuzzTestWithDomain<TargetFunction, FuncType, AggregateOf<std::tuple<typename T::ValueType...>, T...>>; /** * @tparam T The domain type to use. */ template <typename... T> FuzzTestWithDomainType<T...> WithDomains( AggregateOf<std::tuple<typename T::ValueType...>, T...> domain) { return FuzzTestWithDomainType<T...>(*this, domain); } /** * @tparam T The domain type to use. * This function will decompose the domain type and use tuple to represent the list of domains. * The previous function is matched in this call. */ template <typename... T> FuzzTestWithDomainType<T...> WithDomains(T&&... domains) { return WithDomains(TupleOf(std::forward<T>(domains)...)); } FuzzTest& WithSeeds(std::vector<ValueType> _seeds) { this->seeds.insert(this->seeds.end(), _seeds.begin(), _seeds.end()); return *this; } // This should create default domains virtual void Run(int = 1000, int = 0) {} virtual void RunOneTime(const uint8_t*, size_t) {} virtual std::string MutateData(const uint8_t*, size_t, size_t, unsigned int) { return ""; } TargetFunction func; TestContext* context; std::vector<ValueType> seeds; }; extern void RunFuzzTest(IFuzzTest& fuzz_test, int seed = 0, int runs = 1000, int max_len = 0, int timeout = 1200, int len_control = 100); /** * @brief FuzzTestWithDomain implements the Base class with the domain passed into. * @tparam TargetFunction The target function to fuzz. * @tparam FuncType is the decomposition result of the target function. * @tparam Domain The domain to use. * @tparam Base The base class to inherit from. */ template <typename TargetFunction, typename FuncType, typename Domain, typename Base> struct FuzzTestWithDomain : public Base { FuzzTestWithDomain(const Base& ft, const Domain& domain) : Base(ft), m_domain(domain) {} virtual void Run(int _count = 1000, int _seed = 0) override { Base::count = 1; Base::max_count = _count; m_rng = new Rng(_seed); RunFuzzTest(*this, _seed, _count, 500, 1200, 1); delete m_rng; m_rng = nullptr; } typename Domain::CorpusType GetRandomCorpus() { Rng& rng = *this->m_rng; if (Base::seeds.size() > 0 && rng.bounded(2) == 0) { return m_domain.FromValue(Base::seeds[rng() % Base::seeds.size()]); } return m_domain.GetRandomCorpus(rng); } typename Domain::CorpusType TryParse(const std::string& input) { try { IRObject obj = IRObject::FromString(input); if (obj.type == IRObject::Type::Undefined) return GetRandomCorpus(); return m_domain.ParseCorpus(obj); } catch (...) { return GetRandomCorpus(); } } template <typename T, unsigned... I> void apply(T args, detail::seq<I...>) { this->func(std::get<I>(args)...); } virtual void RunOneTime(const uint8_t* data, size_t size) override { Base::count++; std::string input = std::string((const char*)data, size); typename Domain::CorpusType corpus = TryParse(input); typename Domain::ValueType value = m_domain.GetValue(corpus); apply(value, detail::gen_seq<std::tuple_size<typename Domain::ValueType>::value>{}); } virtual std::string MutateData(const uint8_t* data, size_t size, size_t max_size, unsigned int seed) override { Rng rng(seed); std::string input = std::string((const char*)data, size); typename Domain::CorpusType corpus = TryParse(input); m_domain.Mutate(rng, corpus, false); IRObject mutated_obj = m_domain.SerializeCorpus(corpus); return IRObject::ToString(mutated_obj); } Domain m_domain; Rng* m_rng; }; template <typename T> FuzzTest<T> FuzzFunction(T func, TestContext* context) { return FuzzTest<T>(func, context); } template <typename T> std::vector<T> ReadCorpusFromDir(std::string dir); } // namespace zeroerr ZEROERR_SUPPRESS_COMMON_WARNINGS_POP #ifdef ZEROERR_IMPLEMENTATION #include <random> #include <stdexcept> #include <string> namespace zeroerr { Rng::Rng() : mX(0), mY(0) { std::random_device rd; std::uniform_int_distribution<uint64_t> dist; do { mX = dist(rd); mY = dist(rd); } while (mX == 0 && mY == 0); } static uint64_t splitMix64(uint64_t& state) noexcept { uint64_t z = (state += UINT64_C(0x9e3779b97f4a7c15)); z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9); z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb); return z ^ (z >> 31U); } // Seeded as described in romu paper (update april 2020) Rng::Rng(uint64_t seed) noexcept : mX(splitMix64(seed)), mY(splitMix64(seed)) { for (size_t i = 0; i < 10; ++i) { operator()(); } } // only internally used to copy the RNG. Rng::Rng(uint64_t x, uint64_t y) noexcept : mX(x), mY(y) {} Rng Rng::copy() const noexcept { return Rng{mX, mY}; } Rng::Rng(std::vector<uint64_t> const& data) : mX(0), mY(0) { if (data.size() != 2) { throw std::runtime_error("Rng::Rng: needed exactly 2 entries in data, but got " + std::to_string(data.size())); } mX = data[0]; mY = data[1]; } std::vector<uint64_t> Rng::state() const { std::vector<uint64_t> data(2); data[0] = mX; data[1] = mY; return data; } uint64_t Rng::min() { return 0; } uint64_t Rng::max() { return (std::numeric_limits<uint64_t>::max)(); } uint64_t Rng::rotl(uint64_t x, unsigned k) noexcept { return (x << k) | (x >> (64U - k)); } } // namespace zeroerr #if !defined(ZEROERR_ALWAYS_COLORFUL) && !defined(ZEROERR_DISABLE_COLORFUL) namespace zeroerr { static const char* _Reset = "\x1b[0m"; static const char* _Bright = "\x1b[1m"; static const char* _Dim = "\x1b[2m"; static const char* _Underscore = "\x1b[4m"; static const char* _Blink = "\x1b[5m"; static const char* _Reverse = "\x1b[7m"; static const char* _Hidden = "\x1b[8m"; static const char* _FgBlack = "\x1b[30m"; static const char* _FgRed = "\x1b[31m"; static const char* _FgGreen = "\x1b[32m"; static const char* _FgYellow = "\x1b[33m"; static const char* _FgBlue = "\x1b[34m"; static const char* _FgMagenta = "\x1b[35m"; static const char* _FgCyan = "\x1b[36m"; static const char* _FgWhite = "\x1b[37m"; static const char* _BgBlack = "\x1b[40m"; static const char* _BgRed = "\x1b[41m"; static const char* _BgGreen = "\x1b[42m"; static const char* _BgYellow = "\x1b[43m"; static const char* _BgBlue = "\x1b[44m"; static const char* _BgMagenta = "\x1b[45m"; static const char* _BgCyan = "\x1b[46m"; static const char* _BgWhite = "\x1b[47m"; const char* Reset = _Reset; const char* Bright = _Bright; const char* Dim = _Dim; const char* Underscore = _Underscore; const char* Blink = _Blink; const char* Reverse = _Reverse; const char* Hidden = _Hidden; const char* FgBlack = _FgBlack; const char* FgRed = _FgRed; const char* FgGreen = _FgGreen; const char* FgYellow = _FgYellow; const char* FgBlue = _FgBlue; const char* FgMagenta = _FgMagenta; const char* FgCyan = _FgCyan; const char* FgWhite = _FgWhite; const char* BgBlack = _BgBlack; const char* BgRed = _BgRed; const char* BgGreen = _BgGreen; const char* BgYellow = _BgYellow; const char* BgBlue = _BgBlue; const char* BgMagenta = _BgMagenta; const char* BgCyan = _BgCyan; const char* BgWhite = _BgWhite; ZEROERR_MUTEX(m); void disableColorOutput() { ZEROERR_LOCK(m); Reset = ""; Bright = ""; Dim = ""; Underscore = ""; Blink = ""; Reverse = ""; Hidden = ""; FgBlack = ""; FgRed = ""; FgGreen = ""; FgYellow = ""; FgBlue = ""; FgMagenta = ""; FgCyan = ""; FgWhite = ""; BgBlack = ""; BgRed = ""; BgGreen = ""; BgYellow = ""; BgBlue = ""; BgMagenta = ""; BgCyan = ""; BgWhite = ""; } void enableColorOutput() { ZEROERR_LOCK(m); Reset = _Reset; Bright = _Bright; Dim = _Dim; Underscore = _Underscore; Blink = _Blink; Reverse = _Reverse; Hidden = _Hidden; FgBlack = _FgBlack; FgRed = _FgRed; FgGreen = _FgGreen; FgYellow = _FgYellow; FgBlue = _FgBlue; FgMagenta = _FgMagenta; FgCyan = _FgCyan; FgWhite = _FgWhite; BgBlack = _BgBlack; BgRed = _BgRed; BgGreen = _BgGreen; BgYellow = _BgYellow; BgBlue = _BgBlue; BgMagenta = _BgMagenta; BgCyan = _BgCyan; BgWhite = _BgWhite; } #ifndef ZEROERR_DISABLE_AUTO_INIT static struct ColorInit { ColorInit() { if (isTerminalOutput(STDERR)) { enableColorOutput(); } else { disableColorOutput(); } } } colorInit; #endif } // namespace zeroerr #endif #include <iostream> namespace zeroerr { Printer& getStdoutPrinter() { static Printer printer(std::cout); return printer; } Printer& getStderrPrinter() { static Printer printer(std::cerr); return printer; } } // namespace zeroerr #include <cstdio> #ifdef ZEROERR_OS_UNIX #include <sys/ioctl.h> #include <unistd.h> #endif #ifdef ZEROERR_OS_WINDOWS #define NOMINMAX #include <Windows.h> #endif namespace zeroerr { #ifdef ZEROERR_OS_UNIX bool isTerminalOutput(OutputStream stream) { switch (stream) { case STDOUT: return isatty(fileno(stdout)) != 0; case STDERR: return isatty(fileno(stderr)) != 0; default: return false; } } TerminalSize getTerminalWidth() { struct winsize ws; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { return {80, 24}; } return {ws.ws_col, ws.ws_row}; } #endif #ifdef ZEROERR_OS_WINDOWS bool isTerminalOutput(OutputStream stream) { switch (stream) { case STDOUT: return GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_CHAR; case STDERR: return GetFileType(GetStdHandle(STD_ERROR_HANDLE)) == FILE_TYPE_CHAR; default: return false; } } TerminalSize getTerminalWidth() { CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); return {csbi.dwSize.X, csbi.dwSize.Y}; } #endif } // namespace zeroerr #include <iomanip> #include <unordered_set> #ifdef _WIN32 #include <windows.h> #else #include <sys/stat.h> #endif const char* ZEROERR_LOG_CATEGORY = "default"; namespace zeroerr { int _ZEROERR_G_VERBOSE = 0; thread_local std::vector<IContextScope*> _ZEROERR_G_CONTEXT_SCOPE_VECTOR; static std::string DefaultLogCallback(const LogMessage& msg, bool colorful); static LogCustomCallback log_custom_callback = DefaultLogCallback; void setLogCustomCallback(LogCustomCallback callback) { log_custom_callback = callback; } LogInfo::LogInfo(const char* filename, const char* function, const char* message, const char* category, unsigned line, unsigned size, LogSeverity severity) : filename(filename), function(function), message(message), category(category), line(line), size(size), severity(severity), names() { for (const char* p = message; *p; p++) if (*p == '{') { const char* q = p + 1; while (*q && *q != '}') q++; if (*q == '}') { std::string N(p + 1, (size_t)(q - p - 1)); names[N] = static_cast<int>(names.size()); p = q; } } } constexpr size_t LogStreamMaxSize = 1 * 1024 - 16; struct DataBlock { ZEROERR_ATOMIC(size_t) size; DataBlock* next = nullptr; char data[LogStreamMaxSize]; DataBlock() : size(0) {} LogMessage* begin() { return (LogMessage*)data; } LogMessage* end() { return (LogMessage*)&data[size]; } }; LogStream::LogStream() { first = m_last = new DataBlock(); prepare = new DataBlock(); #ifndef ZEROERR_NO_THREAD_SAFE mutex = new std::mutex(); #endif setStderrLogger(); } LogStream::~LogStream() { while (first) { DataBlock* next = first->next; logger->flush(first); delete first; first = next; } if (logger) delete logger; #ifndef ZEROERR_NO_THREAD_SAFE delete mutex; #endif } void* LogStream::alloc_block(unsigned size) { if (size > LogStreamMaxSize) { throw std::runtime_error("LogStream::push: size > LogStreamMaxSize"); } ZEROERR_LOCK(*mutex); auto* last = ZEROERR_LOAD(m_last); if (last->size + size > LogStreamMaxSize) { if (flush_mode == FLUSH_WHEN_FULL) { logger->flush(last); last->size = 0; } else { last->next = new DataBlock(); last = last->next; } } void* p = last->data + last->size; last->size += size; return p; } void* LogStream::alloc_block_lockfree(unsigned size) { #ifndef ZEROERR_NO_THREAD_SAFE if (size > LogStreamMaxSize) { throw std::runtime_error("LogStream::push: size > LogStreamMaxSize"); } DataBlock* last = ZEROERR_LOAD(m_last); while (true) { size_t p = last->size.load(); if (p <= (LogStreamMaxSize - size)) { if (last->size.compare_exchange_strong(p, p + size)) return last->data + p; } else { if (m_last.compare_exchange_strong(last, prepare)) { if (flush_mode == FLUSH_WHEN_FULL) { logger->flush(last); last->size = 0; prepare = last; } else { prepare->next = last; prepare = new DataBlock(); } } } } #else return alloc_block(size); #endif } LogIterator LogStream::current(std::string message, std::string function_name, int line) { LogIterator iter(*this, message, function_name, line); DataBlock* last = ZEROERR_LOAD(m_last); iter.p = last; iter.q = reinterpret_cast<LogMessage*>(&(last->data[ZEROERR_LOAD(last->size)])); return iter; } void LogStream::flush() { ZEROERR_LOCK(*mutex); DataBlock* last = ZEROERR_LOAD(m_last); for (DataBlock* p = first; p != last; p = p->next) { logger->flush(p); delete p; } logger->flush(last); last->size = 0; first = last; } static LogMessage* moveBytes(LogMessage* p, unsigned size) { char* src = (char*)p; char* dst = src + size; return (LogMessage*)dst; } void* LogStream::getRawLog(std::string func, unsigned line, std::string name) { for (DataBlock* p = first; p; p = p->next) for (auto q = p->begin(); q < p->end(); q = moveBytes(q, q->info->size)) if (line == q->info->line && func == q->info->function) return q->getRawLog(name); return nullptr; } static bool startWith(const std::string& str, const std::string& prefix) { return str.rfind(prefix, 0) == 0; } void* LogStream::getRawLog(std::string func, std::string msg, std::string name) { for (DataBlock* p = first; p; p = p->next) for (auto q = p->begin(); q < p->end(); q = moveBytes(q, q->info->size)) if (startWith(q->info->message, msg) && func == q->info->function) return q->getRawLog(name); return nullptr; } LogIterator::LogIterator(LogStream& stream, std::string message, std::string function_name, int line) : p(stream.first), q(stream.first->begin()), message_filter(message), function_name_filter(function_name), line_filter(line) { while (!check_filter() && p) next(); } void LogIterator::check_at_safe_pos() { if (static_cast<size_t>((char*)q - p->data) >= ZEROERR_LOAD(p->size)) { p = p->next; q = reinterpret_cast<LogMessage*>(p->data); } } void LogIterator::next() { if (q < p->end()) { q = moveBytes(q, q->info->size); if (q >= p->end()) next(); } else { p = p->next; if (p) q = p->begin(); else q = nullptr; } } LogIterator& LogIterator::operator++() { do { next(); } while (p && !check_filter()); return *this; } bool LogIterator::check_filter() { if (!message_filter.empty() && startWith(q->info->message, message_filter)) return false; if (!function_name_filter.empty() && q->info->function != function_name_filter) return false; if (line_filter != -1 && static_cast<int>(q->info->line) != line_filter) return false; return true; } class FileLogger : public Logger { public: FileLogger(std::string name) { file = fopen(name.c_str(), "w"); } ~FileLogger() { if (file) fclose(file); } void flush(DataBlock* msg) override { if (file) { for (auto p = msg->begin(); p < msg->end(); p = moveBytes(p, p->info->size)) { auto ss = log_custom_callback(*p, false); fwrite(ss.c_str(), ss.size(), 1, file); } fflush(file); } } protected: FILE* file; }; #ifdef _WIN32 static char split = '\\'; #else static char split = '/'; #endif static void make_dir(std::string path) { size_t pos = 0; do { pos = path.find_first_of(split, pos + 1); std::string sub = path.substr(0, pos); #ifdef _WIN32 CreateDirectory(sub.c_str(), NULL); #else struct stat st; if (stat(sub.c_str(), &st) == -1) { mkdir(sub.c_str(), 0755); } #endif } while (pos != std::string::npos); } struct FileCache { std::map<std::string, FILE*> files; ~FileCache() { for (auto& p : files) { fclose(p.second); } } FILE* get(const std::string& name) { auto it = files.find(name); if (it != files.end()) return it->second; auto p = name.find_last_of(split); std::string path = name.substr(0, p); make_dir(path.c_str()); FILE* file = fopen(name.c_str(), "w"); if (file) { files[name] = file; return file; } return nullptr; } }; class DirectoryLogger : public Logger { public: DirectoryLogger(std::string path, LogStream::DirMode dir_mode[3]) : dirpath(path) { make_dir(path.c_str()); for (int i = 0; i < 3; i++) { this->dir_mode[i] = dir_mode[i]; } } ~DirectoryLogger() {} void flush(DataBlock* msg) override { FileCache cache; for (auto p = msg->begin(); p < msg->end(); p = moveBytes(p, p->info->size)) { auto ss = log_custom_callback(*p, false); std::stringstream path; path << dirpath; if (dirpath.back() != split) path << split; int last = 0; for (int i = 0; i < 3; i++) { if (last != 0 && dir_mode[i] != 0) path << split; switch (dir_mode[i]) { case LogStream::DAILY_FILE: { std::time_t t = std::chrono::system_clock::to_time_t(p->time); std::tm tm = *std::localtime(&t); path << std::put_time(&tm, "%Y-%m-%d"); break; } case LogStream::SPLIT_BY_SEVERITY: { path << to_string(p->info->severity); break; } case LogStream::SPLIT_BY_CATEGORY: { path << to_category(p->info->category); break; } default: continue; } last = 1; } std::cerr << path.str() << std::endl; FILE* file = cache.get(path.str()); fwrite(ss.c_str(), ss.size(), 1, file); } } protected: std::string to_string(LogSeverity severity) { switch (severity) { case INFO_l: return "INFO"; case LOG_l: return "LOG"; case WARN_l: return "WARN"; case ERROR_l: return "ERROR"; case FATAL_l: return "FATAL"; } return ""; } std::string to_category(const char* category) { std::string cat = category; for (auto& c : cat) { if (c == '/') c = split; } return cat; } LogStream::DirMode dir_mode[3]; std::string dirpath; }; class OStreamLogger : public Logger { public: OStreamLogger(std::ostream& os) : os(os) {} void flush(DataBlock* msg) override { for (auto p = msg->begin(); p < msg->end(); p = moveBytes(p, p->info->size)) { os << log_custom_callback(*p, true); } os.flush(); } protected: std::ostream& os; }; LogStream& LogStream::getDefault() { static LogStream stream; return stream; } void LogStream::setFileLogger(std::string name, DirMode mode1, DirMode mode2, DirMode mode3) { if (logger) delete logger; if (mode1 == 0 || mode2 == 0 || mode3 == 0) logger = new FileLogger(name); else { LogStream::DirMode dir_mode_group[3] = {mode1, mode2, mode3}; logger = new DirectoryLogger(name, dir_mode_group); } } void LogStream::setStdoutLogger() { if (logger) delete logger; logger = new OStreamLogger(std::cout); } void LogStream::setStderrLogger() { if (logger) delete logger; logger = new OStreamLogger(std::cerr); } static LogSeverity LogLevel; static std::unordered_set<std::string> LogCategory; static std::vector<std::string> AllLogCategory; void setLogLevel(LogSeverity level) { LogLevel = level; } void setLogCategory(const char* categories) { LogCategory.clear(); std::string str = categories; std::string cat; for (auto c : str) { if (c == ',') { LogCategory.insert(cat); cat.clear(); } else { cat.push_back(c); } } if (!cat.empty()) { LogCategory.insert(cat); } } static LogStream::FlushMode saved_flush_mode = LogStream::FlushMode::FLUSH_AT_ONCE; void suspendLog() { saved_flush_mode = LogStream::getDefault().getFlushMode(); LogStream::getDefault().setFlushMode(LogStream::FLUSH_MANUALLY); } void resumeLog() { LogStream::getDefault().setFlushMode(saved_flush_mode); LogStream::getDefault().flush(); } #define zeroerr_color(x) (colorful ? x : "") static std::string DefaultLogCallback(const LogMessage& msg, bool colorful) { std::stringstream ss; std::time_t t = std::chrono::system_clock::to_time_t(msg.time); std::tm tm = *std::localtime(&t); ss << zeroerr_color(Dim) << '[' << zeroerr_color(Reset); switch (msg.info->severity) { case INFO_l: ss << "INFO "; break; case LOG_l: ss << zeroerr_color(FgGreen) << "LOG " << zeroerr_color(Reset); break; case WARN_l: ss << zeroerr_color(FgYellow) << "WARN " << zeroerr_color(Reset); break; case ERROR_l: ss << zeroerr_color(FgRed) << "ERROR" << zeroerr_color(Reset); break; case FATAL_l: ss << zeroerr_color(FgMagenta) << "FATAL" << zeroerr_color(Reset); break; } ss << " " << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); std::string fileName(msg.info->filename); auto p = fileName.find_last_of('/'); if (p != std::string::npos) fileName = fileName.substr(p + 1); auto q = fileName.find_last_of('\\'); if (q != std::string::npos) fileName = fileName.substr(q + 1); ss << " " << fileName << ":" << msg.info->line; ss << zeroerr_color(Dim) << ']' << zeroerr_color(Reset) << " " << msg.str(); ss << std::endl; return ss.str(); } #undef zeroerr_color } // namespace zeroerr #include <algorithm> #include <map> #include <sstream> namespace zeroerr { // clang-format off static Table::Style ASCII{ "+","-","-","+", "|"," ","|","|", "|","-","+","|", "|"," ","|","|", "|","-","+","|", "|","-","+","|", "|"," ","|","|", "+","-","-","+", }; static Table::Style ASCII2{ "+","-","+","+", "|"," ","|","|", "+","-","+","+", "|"," ","|","|", "+","-","+","+", "+","-","+","+", "|"," ","|","|", "+","-","+","+", }; static Table::Style ASCII_DOUBLE_HEAD{ "+","-","+","+", "|"," ","|","|", "+","=","+","+", "|"," ","|","|", "+","-","+","+", "+","-","+","+", "|"," ","|","|", "+","-","+","+", }; static Table::Style SQUARE{ "┌","─","┬","┐", "│"," ","│","│", "├","─","┼","┤", "│"," ","│","│", "├","─","┼","┤", "├","─","┼","┤", "│"," ","│","│", "└","─","┴","┘", }; static Table::Style SQUARE_DOUBLE_HEAD{ "┌","─","┬","┐", "│"," ","│","│", "╞","═","╪","╡", "│"," ","│","│", "├","─","┼","┤", "├","─","┼","┤", "│"," ","│","│", "└","─","┴","┘", }; static Table::Style MINIMAL{ " "," ","╷"," ", " "," ","│"," ", "╶","─","┼","╴", " "," ","│"," ", "╶","─","┼","╴", "╶","─","┼","╴", " "," ","│"," ", " "," ","╵"," ", }; static Table::Style MINIMAL_HEAVY_HEAD{ " "," ","╷"," ", " "," ","│"," ", "╺","━","┿","╸", " "," ","│"," ", "╶","─","┼","╴", "╶","─","┼","╴", " "," ","│"," ", " "," ","╵"," ", }; static Table::Style MINIMAL_DOUBLE_HEAD{ " "," ","╷"," ", " "," ","│"," ", " ","═","╪"," ", " "," ","│"," ", " ","─","┼"," ", " ","─","┼"," ", " "," ","│"," ", " "," ","╵"," ", }; static Table::Style SIMPLE{ " "," "," "," ", " "," "," "," ", " ","─","─"," ", " "," "," "," ", " "," "," "," ", " ","─","─"," ", " "," "," "," ", " "," "," "," ", }; static Table::Style SIMPLE_HEAD{ " "," "," "," ", " "," "," "," ", " ","─","─"," ", " "," "," "," ", " "," "," "," ", " "," "," "," ", " "," "," "," ", " "," "," "," ", }; static Table::Style SIMPLE_HEAVY{ " "," "," "," ", " "," "," "," ", " ","━","━"," ", " "," "," "," ", " "," "," "," ", " ","━","━"," ", " "," "," "," ", " "," "," "," ", }; static Table::Style HORIZONTALS{ " ","─","─"," ", " "," "," "," ", " ","─","─"," ", " "," "," "," ", " ","─","─"," ", " ","─","─"," ", " "," "," "," ", " ","─","─"," ", }; static Table::Style ROUNDED{ "╭","─","┬","╮", "│"," ","│","│", "├","─","┼","┤", "│"," ","│","│", "├","─","┼","┤", "├","─","┼","┤", "│"," ","│","│", "╰","─","┴","╯", }; static Table::Style HEAVY{ "┏","━","┳","┓", "┃"," ","┃","┃", "┣","━","╋","┫", "┃"," ","┃","┃", "┣","━","╋","┫", "┣","━","╋","┫", "┃"," ","┃","┃", "┗","━","┻","┛", }; static Table::Style HEAVY_EDGE{ "┏","━","┯","┓", "┃"," ","│","┃", "┠","─","┼","┨", "┃"," ","│","┃", "┠","─","┼","┨", "┠","─","┼","┨", "┃"," ","│","┃", "┗","━","┷","┛", }; static Table::Style HEAVY_HEAD{ "┏","━","┳","┓", "┃"," ","┃","┃", "┡","━","╇","┩", "│"," ","│","│", "├","─","┼","┤", "├","─","┼","┤", "│"," ","│","│", "└","─","┴","┘", }; static Table::Style DOUBLE{ "╔","═","╦","╗", "║"," ","║","║", "╠","═","╬","╣", "║"," ","║","║", "╠","═","╬","╣", "╠","═","╬","╣", "║"," ","║","║", "╚","═","╩","╝", }; static Table::Style DOUBLE_EDGE{ "╔","═","╤","╗", "║"," ","│","║", "╟","─","┼","╢", "║"," ","│","║", "╟","─","┼","╢", "╟","─","┼","╢", "║"," ","│","║", "╚","═","╧","╝", }; // clang-format on struct StyleManager { std::map<std::string, Table::Style> styles; static StyleManager& instance() { static StyleManager instance; instance.styles["ascii"] = ASCII; instance.styles["ascii2"] = ASCII2; instance.styles["ascii_double_head"] = ASCII_DOUBLE_HEAD; instance.styles["square"] = SQUARE; instance.styles["square_double_head"] = SQUARE_DOUBLE_HEAD; instance.styles["simple"] = SIMPLE; instance.styles["simple_head"] = SIMPLE_HEAD; instance.styles["simple_heavy"] = SIMPLE_HEAVY; instance.styles["horizontal"] = HORIZONTALS; instance.styles["rounded"] = ROUNDED; instance.styles["heavy"] = HEAVY; instance.styles["heavy_edge"] = HEAVY_EDGE; instance.styles["heavy_head"] = HEAVY_HEAD; instance.styles["double"] = DOUBLE; instance.styles["double_edge"] = DOUBLE_EDGE; instance.styles["minimal"] = MINIMAL; instance.styles["minimal_heavy_head"] = MINIMAL_HEAVY_HEAD; instance.styles["minimal_double_head"] = MINIMAL_DOUBLE_HEAD; return instance; } }; void Table::registerStyle(std::string name, Table::Style style) { StyleManager::instance().styles[name] = style; } Table::Style Table::getStyle(std::string name) { return StyleManager::instance().styles[name]; } // TODO: Refactor those macros into functions #define left (c.show_lr_border ? s.m_args[p] : "") #define space s.m_args[p + 1] #define bar (c.show_col_split ? s.m_args[p + 2] : s.m_args[p + 1]) #define right (c.show_lr_border ? s.m_args[p + 3] : "") #define last (i == header.size() - 1) #define for_row \ if (!skip_lb) \ ss << std::endl; \ else \ skip_lb = false; \ ss << left; \ for (size_t i = 0; i < header.size(); ++i) inline std::string _rept(unsigned k, std::string j, Table::Style&) { std::stringstream ss; for (unsigned i = 0; i < k; i++) { ss << j; } return ss.str(); } #define rep(k, t) _rept(k, t, s) #define remain(k) (col_width[i] - static_cast<unsigned>(k.size())) std::string Table::str(Config c, Table::Style s) { std::stringstream ss; for (auto& row : cells) { REQUIRE(row.size() == header.size()); } if (col_width.size() == 0) { for (size_t i = 0; i < header.size(); ++i) { size_t max_width = 0; for (auto& row : cells) { max_width = std::max<size_t>(row[i].size(), max_width); } max_width = std::max<size_t>(max_width, header[i].size()); col_width.push_back(static_cast<unsigned>(max_width)); } } int p; bool skip_lb = true; // Header if (c.show_tb_border) { p = 0; for_row { ss << rep(col_width[i] + 2, space) << (last ? right : bar); } } p = 4; for_row { REQUIRE(col_width[i] >= header[i].size()); ss << space << rep(remain(header[i]), space) << header[i] << space << (last ? right : bar); } if (c.show_header_split) { p = 8; for_row { ss << rep(col_width[i] + 2, space) << (last ? right : bar); } } p = 12; // Body bool first = true; for (auto& row : cells) { if (!first && c.show_row_split) { p += 4; for_row { ss << rep(col_width[i] + 2, space) << (last ? right : bar); } p -= 4; } else first = false; for_row { REQUIRE(col_width[i] >= row[i].size()); ss << space << rep(remain(row[i]), space) << row[i] << space << (last ? right : bar); } } if (footer.size() != 0) { } // Bottom if (c.show_tb_border) { p = 28; for_row { ss << rep(col_width[i] + 2, space) << (last ? right : bar); } } return ss.str(); } #undef left #undef space #undef bar #undef right #undef last #undef for_row #undef rep #undef remain } // namespace zeroerr #include <iomanip> #include <iostream> #include <ostream> #include <regex> #include <set> #include <string> #include <vector> namespace zeroerr { namespace detail { static std::set<TestCase> getRegisteredTests(unsigned type); } // namespace detail // This function update both sum and local. // Local need to be updated since the reporter needs to know the result of the subcase. int TestContext::add(TestContext& local) { int type = 0; if (local.failed_as == 0 && local.warning_as == 0) { passed += 1; local.passed += 1; } else if (local.failed_as == 0) { warning += 1; local.warning += 1; type = 1; } else { failed += 1; local.failed += 1; type = 2; } passed_as += local.passed_as; warning_as += local.warning_as; failed_as += local.failed_as; return type; } void TestContext::save_output() { std::fstream file; file.open("output.txt", std::ios::in); std::stringbuf* outbuf = static_cast<std::stringbuf*>(std::cerr.rdbuf()); if (file.is_open()) { std::stringstream buffer; buffer << file.rdbuf(); if (buffer.str() != outbuf->str()) { std::cerr << "Output mismatch" << std::endl; throw std::runtime_error("Output mismatch"); } else { std::cerr << "Output match" << std::endl; } } else { file.open("output.txt", std::ios::out); file << outbuf->str(); } file.close(); } void TestContext::reset() { passed = warning = failed = skipped = 0; passed_as = warning_as = failed_as = skipped_as = 0; } static inline std::string getFileName(std::string file) { std::string fileName(file); auto p = fileName.find_last_of('/'); if (p == std::string::npos) p = fileName.find_last_of('\\'); if (p != std::string::npos) fileName = fileName.substr(p + 1); return fileName; } SubCase::SubCase(std::string name, std::string file, unsigned line, TestContext* context, std::vector<Decorator*> decorators) : TestCase(name, file, line, decorators), context(context) {} void SubCase::operator<<(std::function<void(TestContext*)> op) { func = op; std::stringbuf new_buf; context->reporter.subCaseStart(*this, new_buf); TestContext local(context->reporter); std::streambuf* orig_buf = std::cerr.rdbuf(); std::cerr.rdbuf(&new_buf); try { op(&local); } catch (const AssertionData&) { } catch (const FuzzFinishedException&) { } catch (const std::exception& e) { std::cerr << e.what() << std::endl; if (local.failed_as == 0) { local.failed_as = 1; } } std::cerr.rdbuf(orig_buf); int type = context->add(local); context->reporter.subCaseEnd(*this, new_buf, local, type); } struct Filters { std::vector<std::regex> name, name_exclude; std::vector<std::regex> file, file_exclude; }; UnitTest& UnitTest::parseArgs(int argc, const char** argv) { filters = new Filters(); auto convert_to_vec = [=]() { std::vector<std::string> result; for (int i = 1; i < argc; i++) { result.emplace_back(argv[i]); } return result; }; auto parse_char = [&](char arg) { if (arg == 'v') { this->silent = false; return true; } if (arg == 'q') { this->silent = true; return true; } if (arg == 'b') { this->run_bench = true; return true; } if (arg == 'f') { this->run_fuzz = true; return true; } if (arg == 'l') { this->list_test_cases = true; return true; } if (arg == 'x') { this->reporter_name = "xml"; return true; } return false; }; auto parse_token = [&](std::string arg) { if (arg == "verbose") { this->silent = false; return true; } if (arg == "quiet") { this->silent = true; return true; } if (arg == "bench") { this->run_bench = true; } if (arg == "fuzz") { this->run_fuzz = true; } if (arg == "list-test-cases") { this->list_test_cases = true; } if (arg == "no-color") { this->no_color = true; disableColorOutput(); } if (arg == "log-to-report") { this->log_to_report = true; } if (arg.substr(0, 9) == "reporters") { this->reporter_name = arg.substr(10); return true; } if (arg.substr(0, 8) == "testcase") { filters->name.push_back(std::regex(arg.substr(9))); return true; } if (arg.substr(0, 14) == "testcase-exclude") { filters->name_exclude.push_back(std::regex(arg.substr(15))); return true; } if (arg.substr(0, 5) == "file") { filters->file.push_back(std::regex(arg.substr(6))); return true; } if (arg.substr(0, 11) == "file-exclude") { filters->file_exclude.push_back(std::regex(arg.substr(12))); return true; } return false; }; auto parse_pos = [&](const std::vector<std::string>& args, size_t pos) { if (args[pos].size() == 2 && args[pos][0] == '-') { return parse_char(args[pos][1]); } if (args[pos].size() > 2 && args[pos][0] == '-' && args[pos][1] == '-') { return parse_token(args[pos].substr(2)); } return false; }; auto args = convert_to_vec(); for (size_t i = 0; i < args.size(); ++i) parse_pos(args, i); binary = argv[0]; return *this; } static std::string insertIndentation(std::string str) { std::stringstream result; std::stringstream ss(str); std::string line; while (std::getline(ss, line)) { result << line << std::endl << " "; } return result.str(); } bool UnitTest::run_filter(const TestCase& tc) { if (filters == nullptr) return true; for (auto& r : filters->name) if (!std::regex_match(tc.name, r)) return false; for (auto& r : filters->name_exclude) if (std::regex_match(tc.name, r)) return false; for (auto& r : filters->file) if (!std::regex_match(tc.file, r)) return false; for (auto& r : filters->file_exclude) if (std::regex_match(tc.file, r)) return false; return true; } static bool runOnExecution(const TestCase& tc) { for (auto& decorator : tc.decorators) { if (decorator->onExecution(tc)) return true; } return false; } static bool runOnFinish(const TestCase& tc, const TestContext& ctx) { for (auto& decorator : tc.decorators) { if (decorator->onFinish(tc, ctx)) return true; } return false; } int UnitTest::run() { IReporter* reporter = IReporter::create(reporter_name, *this); if (!reporter) reporter = IReporter::create("console", *this); TestContext context(*reporter), sum(*reporter); reporter->testStart(); std::stringbuf new_buf; unsigned types = TestType::test_case; if (run_bench) types |= TestType::bench; if (run_fuzz) types |= TestType::fuzz_test; std::set<TestCase> test_cases = detail::getRegisteredTests(types); for (auto& tc : test_cases) { if (!run_filter(tc)) continue; if (runOnExecution(tc)) { sum.skipped += 1; continue; } reporter->testCaseStart(tc, new_buf); if (!list_test_cases) { std::streambuf* orig_buf = std::cerr.rdbuf(); std::cerr.rdbuf(&new_buf); std::cerr << std::endl; auto start = std::chrono::high_resolution_clock::now(); try { tc.func(&context); // run the test case } catch (const AssertionData&) { } catch (const FuzzFinishedException&) { } catch (const std::exception& e) { std::cerr << e.what() << std::endl; if (context.failed_as == 0) { context.failed_as = 1; } } auto end = std::chrono::high_resolution_clock::now(); context.duration = end - start; std::cerr.rdbuf(orig_buf); } int type = sum.add(context); if (runOnFinish(tc, context)) { if (type != 2) { sum.failed += 1; type = 2; } } reporter->testCaseEnd(tc, new_buf, context, type); context.reset(); new_buf.str(""); } reporter->testEnd(sum); delete reporter; return 0; } // sorted by file names and line numbers bool TestCase::operator<(const TestCase& rhs) const { return (file < rhs.file) || (file == rhs.file && line < rhs.line); } namespace detail { std::set<TestCase>& getTestSet(TestType type) { static std::set<TestCase> test_set, bench_set, fuzz_set; switch (type) { case TestType::test_case: return test_set; case TestType::bench: return bench_set; case TestType::fuzz_test: return fuzz_set; case TestType::sub_case: return test_set; } throw std::runtime_error("Invalid test type"); } static std::set<TestCase> getRegisteredTests(unsigned type) { std::set<TestCase> result; if (type & TestType::test_case) result.insert(getTestSet(test_case).begin(), getTestSet(test_case).end()); if (type & TestType::bench) result.insert(getTestSet(bench).begin(), getTestSet(bench).end()); if (type & TestType::fuzz_test) result.insert(getTestSet(fuzz_test).begin(), getTestSet(fuzz_test).end()); return result; } regTest::regTest(const TestCase& tc, TestType type) { for (auto& decorator : tc.decorators) { if (decorator->onStartup(tc)) return; } getTestSet(type).insert(tc); } static std::set<IReporter*>& getRegisteredReporters() { static std::set<IReporter*> data; return data; } regReporter::regReporter(IReporter* reporter) { getRegisteredReporters().insert(reporter); } } // namespace detail class ConsoleReporter : public IReporter { public: std::string getName() const override { return "console"; } virtual void testStart() override { setlocale(LC_ALL, "en_US.utf8"); std::cerr << "ZeroErr Unit Test" << std::endl; } virtual void testEnd(const TestContext& sum) override { std::cerr << "----------------------------------------------------------------" << std::endl; std::cerr << " " << FgGreen << "PASSED" << Reset << " | " << FgYellow << "WARNING" << Reset << " | " << FgRed << "FAILED" << Reset << " | " << Dim << "SKIPPED" << Reset << std::endl; std::cerr << "TEST CASE: " << std::setw(6) << sum.passed << " " << std::setw(7) << sum.warning << " " << std::setw(6) << sum.failed << " " << std::setw(7) << sum.skipped << std::endl; std::cerr << "ASSERTION: " << std::setw(6) << sum.passed_as << " " << std::setw(7) << sum.warning_as << " " << std::setw(6) << sum.failed_as << " " << std::setw(7) << sum.skipped_as << std::endl; } virtual void testCaseStart(const TestCase& tc, std::stringbuf&) override { std::cerr << "TEST CASE " << Dim << "[" << getFileName(tc.file) << ":" << tc.line << "] " << Reset << FgCyan << tc.name << Reset << std::endl; } virtual void testCaseEnd(const TestCase&, std::stringbuf& sb, const TestContext&, int type) override { if (!(ut.silent && type == 0)) std::cerr << insertIndentation(sb.str()) << std::endl; } virtual void subCaseStart(const TestCase& tc, std::stringbuf&) override { std::cerr << "SUB CASE " << Dim << "[" << getFileName(tc.file) << ":" << tc.line << "] " << Reset << FgCyan << tc.name << Reset << std::endl; } virtual void subCaseEnd(const TestCase&, std::stringbuf& sb, const TestContext&, int type) override { if (!(ut.silent && type == 0)) std::cerr << insertIndentation(sb.str()) << std::endl; } ConsoleReporter(UnitTest& ut) : IReporter(ut) {} }; namespace detail { // ================================================================================================= // The following code has been taken verbatim from Catch2/include/internal/catch_xmlwriter.h/cpp // ================================================================================================= class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode(const std::string& str, ForWhat forWhat = ForTextNodes); void encodeTo(std::ostream& os) const; friend std::ostream& operator<<(std::ostream& os, const XmlEncode& xmlEncode); private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement(XmlWriter* writer); ScopedElement(ScopedElement&& other) noexcept; ScopedElement& operator=(ScopedElement&& other) noexcept; ~ScopedElement(); ScopedElement& writeText(const std::string& text, bool indent = true, bool new_line = true); template <typename T> ScopedElement& writeAttribute(const std::string& name, const T& attribute) { m_writer->writeAttribute(name, attribute); return *this; } private: mutable XmlWriter* m_writer = nullptr; }; XmlWriter(std::ostream& os = std::cerr); ~XmlWriter(); XmlWriter(const XmlWriter&) = delete; XmlWriter& operator=(const XmlWriter&) = delete; XmlWriter& startElement(const std::string& name); ScopedElement scopedElement(const std::string& name); XmlWriter& endElement(); XmlWriter& writeAttribute(const std::string& name, const std::string& attribute); XmlWriter& writeAttribute(const std::string& name, const char* attribute); XmlWriter& writeAttribute(const std::string& name, bool attribute); template <typename T> XmlWriter& writeAttribute(const std::string& name, const T& attribute) { std::stringstream rss; rss << attribute; return writeAttribute(name, rss.str()); } XmlWriter& writeText(const std::string& text, bool indent = true, bool new_line = true); void ensureTagClosed(bool new_line = true); void writeDeclaration(); private: void newlineIfNecessary(); bool m_tagIsOpen = false; bool m_needsNewline = false; bool m_needsIndent = false; std::vector<std::string> m_tags; std::string m_indent; std::ostream& m_os; }; using uchar = unsigned char; static size_t trailingBytes(unsigned char c) { if ((c & 0xE0) == 0xC0) { return 2; } if ((c & 0xF0) == 0xE0) { return 3; } if ((c & 0xF8) == 0xF0) { return 4; } throw std::runtime_error("Invalid multibyte utf-8 start byte encountered"); } static uint32_t headerValue(unsigned char c) { if ((c & 0xE0) == 0xC0) { return c & 0x1F; } if ((c & 0xF0) == 0xE0) { return c & 0x0F; } if ((c & 0xF8) == 0xF0) { return c & 0x07; } throw std::runtime_error("Invalid multibyte utf-8 start byte encountered"); } static void hexEscapeChar(std::ostream& os, unsigned char c) { std::ios_base::fmtflags f(os.flags()); os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(c); os.flags(f); } XmlEncode::XmlEncode(const std::string& str, ForWhat forWhat) : m_str(str), m_forWhat(forWhat) {} void XmlEncode::encodeTo(std::ostream& os) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: https://www.w3.org/TR/xml/#syntax) for (std::size_t idx = 0; idx < m_str.size(); ++idx) { uchar c = m_str[idx]; switch (c) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: https://www.w3.org/TR/xml/#syntax if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') os << ">"; else os << c; break; case '\"': if (m_forWhat == ForAttributes) os << """; else os << c; break; default: // Check for control characters and invalid utf-8 // Escape control characters in standard ascii, see: // https://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { hexEscapeChar(os, c); break; } // Plain ASCII: Write it to stream if (c < 0x7F) { os << c; break; } // UTF-8 territory // Check if the encoding is valid and if it is not, hex escape bytes. // Important: We do not check the exact decoded values for validity, only the // encoding format First check that this bytes is a valid lead byte: This means that // it is not encoded as 1111 1XXX Or as 10XX XXXX if (c < 0xC0 || c >= 0xF8) { hexEscapeChar(os, c); break; } auto encBytes = trailingBytes(c); // Are there enough bytes left to avoid accessing out-of-bounds memory? if (idx + encBytes - 1 >= m_str.size()) { hexEscapeChar(os, c); break; } // The header is valid, check data // The next encBytes bytes must together be a valid utf-8 // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) bool valid = true; uint32_t value = headerValue(c); for (std::size_t n = 1; n < encBytes; ++n) { uchar nc = m_str[idx + n]; valid &= ((nc & 0xC0) == 0x80); value = (value << 6) | (nc & 0x3F); } if ( // Wrong bit pattern of following bytes (!valid) || // Overlong encodings (value < 0x80) || (value < 0x800 && encBytes > 2) || // removed "0x80 <= value &&" because redundant (0x800 < value && value < 0x10000 && encBytes > 3) || // Encoded value out of range (value >= 0x110000)) { hexEscapeChar(os, c); break; } // If we got here, this is in fact a valid(ish) utf-8 sequence for (std::size_t n = 0; n < encBytes; ++n) { os << m_str[idx + n]; } idx += encBytes - 1; break; } } } std::ostream& operator<<(std::ostream& os, const XmlEncode& xmlEncode) { xmlEncode.encodeTo(os); return os; } XmlWriter::ScopedElement::ScopedElement(XmlWriter* writer) : m_writer(writer) {} XmlWriter::ScopedElement::ScopedElement(ScopedElement&& other) noexcept : m_writer(other.m_writer) { other.m_writer = nullptr; } XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=(ScopedElement&& other) noexcept { if (m_writer) { m_writer->endElement(); } m_writer = other.m_writer; other.m_writer = nullptr; return *this; } XmlWriter::ScopedElement::~ScopedElement() { if (m_writer) m_writer->endElement(); } XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText(const std::string& text, bool indent, bool new_line) { m_writer->writeText(text, indent, new_line); return *this; } XmlWriter::XmlWriter(std::ostream& os) : m_os(os) {} XmlWriter::~XmlWriter() { while (!m_tags.empty()) endElement(); } XmlWriter& XmlWriter::startElement(const std::string& name) { ensureTagClosed(); newlineIfNecessary(); m_os << m_indent << '<' << name; m_tags.push_back(name); m_indent += " "; m_tagIsOpen = true; return *this; } XmlWriter::ScopedElement XmlWriter::scopedElement(const std::string& name) { ScopedElement scoped(this); startElement(name); return scoped; } XmlWriter& XmlWriter::endElement() { newlineIfNecessary(); m_indent = m_indent.substr(0, m_indent.size() - 2); if (m_tagIsOpen) { m_os << "/>"; m_tagIsOpen = false; } else { if (m_needsIndent) m_os << m_indent; else m_needsIndent = true; m_os << "</" << m_tags.back() << ">"; } m_os << std::endl; m_tags.pop_back(); return *this; } XmlWriter& XmlWriter::writeAttribute(const std::string& name, const std::string& attribute) { if (!name.empty() && !attribute.empty()) m_os << ' ' << name << "=\"" << XmlEncode(attribute, XmlEncode::ForAttributes) << '"'; return *this; } XmlWriter& XmlWriter::writeAttribute(const std::string& name, const char* attribute) { if (!name.empty() && attribute && attribute[0] != '\0') m_os << ' ' << name << "=\"" << XmlEncode(attribute, XmlEncode::ForAttributes) << '"'; return *this; } XmlWriter& XmlWriter::writeAttribute(const std::string& name, bool attribute) { m_os << ' ' << name << "=\"" << (attribute ? "true" : "false") << '"'; return *this; } XmlWriter& XmlWriter::writeText(const std::string& text, bool indent, bool new_line) { if (!text.empty()) { bool tagWasOpen = m_tagIsOpen; ensureTagClosed(new_line); if (tagWasOpen && indent) m_os << m_indent; m_os << XmlEncode(text); m_needsNewline = new_line; m_needsIndent = new_line; } return *this; } void XmlWriter::ensureTagClosed(bool new_line) { if (m_tagIsOpen) { m_os << ">"; if (new_line) m_os << std::endl; m_tagIsOpen = false; } } void XmlWriter::writeDeclaration() { m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; } void XmlWriter::newlineIfNecessary() { if (m_needsNewline) { m_os << std::endl; m_needsNewline = false; } } // ================================================================================================= // End of copy-pasted code from Catch // ================================================================================================= } // namespace detail class XmlReporter : public IReporter { public: detail::XmlWriter xml; struct TestCaseTemp { const TestCase* tc; }; std::vector<TestCaseTemp> current; virtual std::string getName() const override { return "xml"; } // There are a list of events virtual void testStart() override { xml.writeDeclaration(); xml.startElement("ZeroErr") .writeAttribute("binary", ut.binary) .writeAttribute("version", ZEROERR_VERSION_STR); xml.startElement("TestSuite"); } virtual void testCaseStart(const TestCase& tc, std::stringbuf&) override { current.push_back({&tc}); xml.startElement("TestCase") .writeAttribute("name", tc.name) .writeAttribute("filename", tc.file) .writeAttribute("line", tc.line) .writeAttribute("skipped", "false"); if (ut.log_to_report) suspendLog(); } virtual void testCaseEnd(ZEROERR_UNUSED(const TestCase&), std::stringbuf& sb, const TestContext& ctx, int) override { current.pop_back(); xml.scopedElement("Result") .writeAttribute("time", 0) .writeAttribute("passed", ctx.passed) .writeAttribute("warnings", ctx.warning) .writeAttribute("failed", ctx.failed) .writeAttribute("skipped", ctx.skipped); xml.scopedElement("ResultAsserts") .writeAttribute("passed", ctx.passed_as) .writeAttribute("warnings", ctx.warning_as) .writeAttribute("failed", ctx.failed_as) .writeAttribute("skipped", ctx.skipped_as); xml.scopedElement("Output").writeText(sb.str()); if (ut.log_to_report) { xml.startElement("Log"); LogIterator begin = LogStream::getDefault().begin(); LogIterator end = LogStream::getDefault().end(); for (auto p = begin; p != end; ++p) { xml.startElement("LogEntry") .writeAttribute("function", p->info->function) .writeAttribute("line", p->info->line) .writeAttribute("message", p->info->message) .writeAttribute("category", p->info->category) .writeAttribute("severity", p->info->severity); for (auto pair : p->getData()) { xml.scopedElement(pair.first).writeText(pair.second, false, false); } xml.endElement(); } xml.endElement(); resumeLog(); } xml.endElement(); } virtual void subCaseStart(const TestCase& tc, std::stringbuf& sb) override { testCaseStart(tc, sb); } virtual void subCaseEnd(const TestCase& tc, std::stringbuf& sb, const TestContext& ctx, int) override { testCaseEnd(tc, sb, ctx, 0); } virtual void testEnd(const TestContext& tc) override { xml.endElement(); xml.startElement("OverallResults") .writeAttribute("errors", tc.failed_as) .writeAttribute("failures", tc.failed) .writeAttribute("tests", tc.passed + tc.failed + tc.warning); xml.endElement(); xml.endElement(); } XmlReporter(UnitTest& ut) : IReporter(ut), xml(std::cout) {} }; IReporter* IReporter::create(const std::string& name, UnitTest& ut) { if (name == "console") return new ConsoleReporter(ut); if (name == "xml") return new XmlReporter(ut); return nullptr; } class SkipDecorator : public Decorator { bool onExecution(const TestCase&) override { return true; } }; Decorator* skip(bool isSkip) { static SkipDecorator skip_dec; if (isSkip) return &skip_dec; return nullptr; } class TimeoutDecorator : public Decorator { float timeout; public: TimeoutDecorator() : timeout(0) {} TimeoutDecorator(float timeout) : timeout(timeout) {} bool onFinish(const TestCase& tc, const TestContext& ctx) override { if (ctx.duration > std::chrono::duration<double>(timeout)) { std::cerr << FgRed << "Timeout: " << Reset << ctx.duration.count() << "s > " << timeout << "s" << std::endl; return true; } return false; } }; Decorator* timeout(float timeout) { static std::map<float, TimeoutDecorator> timeout_dec; if (timeout_dec.find(timeout) == timeout_dec.end()) { timeout_dec[timeout] = TimeoutDecorator(timeout); } return &timeout_dec[timeout]; } class FailureDecorator : public Decorator { public: enum FailureType { may_fail, should_fail }; FailureDecorator(FailureType type) : type(type) {} private: FailureType type; }; Decorator* may_fail(bool isMayFail) { static FailureDecorator may_fail_dec(FailureDecorator::may_fail); if (isMayFail) return &may_fail_dec; return nullptr; } Decorator* should_fail(bool isShouldFail) { static FailureDecorator should_fail_dec(FailureDecorator::should_fail); if (isShouldFail) return &should_fail_dec; return nullptr; } } // namespace zeroerr int main(int argc, const char** argv) { zeroerr::UnitTest().parseArgs(argc, argv).run(); std::_Exit(0); } #include <cstring> #ifdef ZEROERR_ENABLE_FUZZING extern "C" int LLVMFuzzerRunDriver(int* argc, char*** argv, int (*user_callback)(const uint8_t* data, size_t size)); extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed); #endif namespace zeroerr { static IFuzzTest* current_fuzz_test = nullptr; } // namespace zeroerr size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed) { const std::string mutated_data = zeroerr::current_fuzz_test->MutateData(data, size, max_size, seed); if (mutated_data.size() > max_size) { WARN("Mutated data is larger than the limit({limit}). Returning the original data ({ori})", max_size, size); return size; } memcpy(data, mutated_data.data(), mutated_data.size()); return mutated_data.size(); } namespace zeroerr { void RunFuzzTest(IFuzzTest& fuzz_test, int seed, int runs, int max_len, int timeout, int len_control) { #ifdef ZEROERR_ENABLE_FUZZING current_fuzz_test = &fuzz_test; int argc = 6; std::string argv[] = { "fuzztest", "-max_len=" + std::to_string(max_len), "-timeout=" + std::to_string(timeout), "-runs=" + std::to_string(runs), "-seed=" + std::to_string(seed), "-len_control=" + std::to_string(len_control), }; char** argv_c = new char*[argc]; for (int i = 0; i < argc; i++) { argv_c[i] = (char*)argv[i].c_str(); } LOG("Running fuzz test"); LLVMFuzzerRunDriver(&argc, &argv_c, [](const uint8_t* data, size_t size) -> int { LOG("Running RunOneTime"); if (current_fuzz_test->should_stop()) { throw FuzzFinishedException(); } current_fuzz_test->RunOneTime(data, size); return 0; }); current_fuzz_test = nullptr; #else (void) fuzz_test; (void) seed; (void) runs; (void) max_len; (void) timeout; (void) len_control; #endif } } // namespace zeroerr #include <sstream> namespace zeroerr { IRObject* IRObject::alloc(size_t size) { IRObject* list = new IRObject[size + 1]; list[0].i = size; return list + 1; } char* IRObject::alloc_str(size_t size) { char* s = new char[size + 1]; s[size] = 0; return s; } static std::string escape(std::string str) { std::string result; for (char c : str) { switch (c) { case ' ': result += "\\s"; break; case '\n': result += "\\n"; break; case '\t': result += "\\t"; break; case '\r': result += "\\r"; break; case '\f': result += "\\f"; break; case '\v': result += "\\v"; break; case '\\': result += "\\\\"; break; case '"': result += "\\\""; break; default: { if (c < 32 || c > 126) { result += "\\x"; result += "0123456789abcdef"[c >> 4]; result += "0123456789abcdef"[c & 15]; continue; } result += c; } } } return result; } static std::string unescape(std::string str) { std::string result; for (size_t i = 0; i < str.size(); ++i) { if (str[i] == '\\') { switch (str[++i]) { case 's': result += ' '; break; case 'n': result += '\n'; break; case 't': result += '\t'; break; case 'r': result += '\r'; break; case 'f': result += '\f'; break; case 'v': result += '\v'; break; case '\\': result += '\\'; break; case '"': result += '"'; break; case 'x': { char c = 0; for (int j = 0; j < 2; ++j) { c *= 16; if (str[i + 1] >= '0' && str[i + 1] <= '9') { c += str[i + 1] - '0'; } else if (str[i + 1] >= 'a' && str[i + 1] <= 'f') { c += str[i + 1] - 'a' + 10; } else if (str[i + 1] >= 'A' && str[i + 1] <= 'F') { c += str[i + 1] - 'A' + 10; } } result += c; i += 2; } } } else { result += str[i]; } } return result; } static void to_string(IRObject obj, std::stringstream& ss) { switch (obj.type) { case IRObject::Type::Int: ss << obj.i; break; case IRObject::Type::Float: ss << obj.f << 'f'; break; case IRObject::Type::String: ss << '"' << escape(obj.s) << '"'; break; case IRObject::Type::ShortString: ss << '"' << escape(obj.ss) << '"'; break; case IRObject::Type::Object: ss << "{ "; auto c = obj.GetChildren(); for (unsigned i = 0; i < c.size; ++i) { to_string(*(c.children + i), ss); ss << " "; } ss << "}"; break; } } static IRObject from_string(std::stringstream& ss, std::string& token) { IRObject obj; if (token == "{") { std::vector<IRObject> children; while (ss >> token) { if (token == "}") break; IRObject child = from_string(ss, token); if (child.type == IRObject::Type::Undefined) return obj; children.push_back(child); } IRObject* child = IRObject::alloc(children.size()); for (unsigned i = 0; i < children.size(); ++i) { child[i] = children[i]; } obj.SetChildren(child); return obj; } if (token[0] == '"') { CHECK(token.size() > 1 AND token.back() == '"'); obj.SetScalar(unescape(token.substr(1, token.size() - 2))); return obj; } if (token.back() == 'f') { obj.SetScalar(std::stod(token.substr(0, token.size() - 1))); return obj; } if (token[0] == '-' || (token[0] >= '0' && token[0] <= '9')) { obj.SetScalar(std::stoll(token)); return obj; } return obj; } std::string IRObject::ToString(IRObject obj) { std::stringstream ss; to_string(obj, ss); return ss.str(); } IRObject IRObject::FromString(std::string str) { std::stringstream ss(str); std::string token; ss >> token; return from_string(ss, token); } std::vector<uint8_t> IRObject::ToBinary(IRObject obj) { std::vector<uint8_t> bin; return bin; } IRObject IRObject::FromBinary(std::vector<uint8_t> bin) { IRObject obj; return obj; } } // namespace zeroerr #include <cstring> #include <iostream> #include <map> #include <random> #include <stdexcept> // #ifdef _WIN32 // #define ZEROERR_ETW 1 // #endif namespace zeroerr { // determines resolution of the given clock. This is done by measuring multiple times and returning // the minimum time difference. Clock::duration calcClockResolution(size_t numEvaluations) noexcept { auto bestDuration = Clock::duration::max(); Clock::time_point tBegin; Clock::time_point tEnd; for (size_t i = 0; i < numEvaluations; ++i) { tBegin = Clock::now(); do { tEnd = Clock::now(); } while (tBegin == tEnd); bestDuration = (std::min)(bestDuration, tEnd - tBegin); } return bestDuration; } // Calculates clock resolution once, and remembers the result Clock::duration clockResolution() noexcept { static Clock::duration sResolution = calcClockResolution(20); return sResolution; } // helpers to get double values template <typename T> static inline double d(T t) noexcept { return static_cast<double>(t); } static inline double d(Clock::duration duration) noexcept { return std::chrono::duration_cast<std::chrono::duration<double, std::nano>>(duration).count(); } struct BenchState { BenchState(Benchmark& bench) : bench(bench), stage(UnInit) { targetEpochTime = clockResolution() * bench.minimalResolutionMutipler; targetEpochTime = std::max(targetEpochTime, bench.mMinEpochTime); targetEpochTime = std::min(targetEpochTime, bench.mMaxEpochTime); numEpoch = bench.epochs; numIteration = 0; elapsed = Clock::duration(0); } Benchmark& bench; enum { UnInit, WarmUp, UpScaling, Measurement } stage; Clock::duration elapsed; Clock::duration targetEpochTime; uint64_t numIteration, numEpoch; Rng mRng{1024}; bool isCloseEnoughForMeasurements() const noexcept { return elapsed * 3 >= targetEpochTime * 2; } uint64_t calcBestNumIters() noexcept { double Elapsed = d(elapsed); double TargetRuntimePerEpoch = d(targetEpochTime); double NewIters = TargetRuntimePerEpoch * d(numIteration) / Elapsed; NewIters *= (1.0 + 0.2 * mRng.uniform01()); // +1 for correct rounding when casting and make sure there are at least 1 iteration return static_cast<uint64_t>(NewIters + 1); } void upscale() { if (elapsed * 10 < targetEpochTime) { // we are far below the target runtime. Multiply iterations by 10 (with overflow check) if (numIteration * 10 < numIteration) { // overflow :-( printf("iterations overflow. Maybe your code got optimized away?\n"); numIteration = 0; return; } if (elapsed * 100 < targetEpochTime) numIteration *= 100; else numIteration *= 10; } else { numIteration = calcBestNumIters(); } } void nextStage() noexcept { switch (stage) { case UnInit: if (bench.warmup != 0) { stage = WarmUp; numIteration = bench.warmup; } else if (bench.iter_per_epoch != 0) { stage = Measurement; numIteration = bench.iter_per_epoch; } else { stage = UpScaling; numIteration = 1; } break; case WarmUp: if (bench.iter_per_epoch != 0) { stage = Measurement; numIteration = bench.iter_per_epoch; } else if (isCloseEnoughForMeasurements()) { stage = Measurement; numIteration = calcBestNumIters(); } else { stage = UpScaling; nextStage(); } break; case UpScaling: if (isCloseEnoughForMeasurements()) { stage = Measurement; numIteration = calcBestNumIters(); } else { stage = UpScaling; upscale(); } break; case Measurement: if (numEpoch) { numEpoch--; } else { numIteration = 0; } break; } } BenchResult result; }; BenchState* createBenchState(Benchmark& benchmark) { return new BenchState(benchmark); } void destroyBenchState(BenchState* state) { delete state; } size_t getNumIter(BenchState* state) { state->nextStage(); return state->numIteration; } void runIteration(BenchState* state) { auto& pc = PerformanceCounter::inst(); state->elapsed = pc.elapsed; pc.updateResults(state->numIteration); if (state->stage == BenchState::Measurement) { PerfCountSet<double> pcset; pcset.iterations = d(state->numIteration); pcset.timeElapsed() = d(state->elapsed) / pcset.iterations; for (int i = 1; i < 7; ++i) { if (pc.has().data[i]) { pcset.data[i] = d(pc.val().data[i]) / pcset.iterations; } } state->result.epoch_details.push_back(pcset); } } void moveResult(BenchState* state, std::string name) { auto& pc = PerformanceCounter::inst(); state->result.name = name; state->result.has = pc.has(); state->bench.result.push_back(state->result); destroyBenchState(state); } PerfCountSet<double> BenchResult::average() const { PerfCountSet<double> avg; for (int i = 0; i < 7; ++i) { if (has.data[i]) { double sum = 0; for (auto& pcset : epoch_details) { sum += pcset.data[i]; } sum /= epoch_details.size(); avg.data[i] = sum; } } return avg; } PerfCountSet<double> BenchResult::min() const { PerfCountSet<double> min; for (int i = 0; i < 7; ++i) { if (has.data[i]) { double min_val = std::numeric_limits<double>::max(); for (auto& pcset : epoch_details) { min_val = std::min(min_val, pcset.data[i]); } min.data[i] = min_val; } } return min; } PerfCountSet<double> BenchResult::max() const { PerfCountSet<double> max; for (int i = 0; i < 7; ++i) { if (has.data[i]) { double max_val = std::numeric_limits<double>::min(); for (auto& pcset : epoch_details) { max_val = std::max(max_val, pcset.data[i]); } max.data[i] = max_val; } } return max; } PerfCountSet<double> BenchResult::mean() const { PerfCountSet<double> mean; return mean; } void Benchmark::report() { static const char* names[] = { "elapsed(ns)", "page faults", "cpu_cycles", "ctx switch", "inst", "branch", "branch misses", }; std::cerr << "" << title << ":" << std::endl; std::vector<std::string> headers{""}; for (unsigned i = 0; i < sizeof(names) / sizeof(names[0]); i++) { if (result[0].has.data[i]) headers.push_back(names[i]); } Table output; output.set_header(headers); for (auto& row : result) { auto row_avg = row.average(); std::vector<double> values; for (int j = 0; j < 7; ++j) if (row.has.data[j]) values.push_back(row_avg.data[j]); output.add_row(row.name, values); } std::cerr << output.str() << std::endl; } namespace detail { // Windows version of doNotOptimizeAway // see https://github.com/google/benchmark/blob/master/include/benchmark/benchmark.h#L307 // see https://github.com/facebook/folly/blob/master/folly/Benchmark.h#L280 // see https://docs.microsoft.com/en-us/cpp/preprocessor/optimize #if defined(_MSC_VER) #pragma optimize("", off) void doNotOptimizeAwaySink(void const*) {} #pragma optimize("", on) #endif } // namespace detail #ifdef ZEROERR_PERF #include <linux/perf_event.h> #include <sys/ioctl.h> #include <sys/syscall.h> #include <unistd.h> namespace detail { struct LinuxPerformanceCounter { inline void beginMeasure() { if (mHasError) return; mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP); if (mHasError) return; mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP); } inline void endMeasure() { if (mHasError) return; mHasError = (-1 == ioctl(mFd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP)); if (mHasError) return; auto const numBytes = sizeof(uint64_t) * mCounters.size(); auto ret = read(mFd, mCounters.data(), numBytes); mHasError = ret != static_cast<ssize_t>(numBytes); } // rounded integer division template <typename T> static inline T divRounded(T a, T divisor) { return (a + divisor / 2) / divisor; } static inline uint32_t mix(uint32_t x) noexcept { x ^= x << 13; x ^= x >> 17; x ^= x << 5; return x; } template <typename Op> void calibrate(Op&& op) { // clear current calibration data, for (auto& v : mCalibratedOverhead) { v = UINT64_C(0); } // create new calibration data auto newCalibration = mCalibratedOverhead; for (auto& v : newCalibration) { v = std::numeric_limits<uint64_t>::max(); } for (size_t iter = 0; iter < 100; ++iter) { beginMeasure(); op(); endMeasure(); if (mHasError) return; for (size_t i = 0; i < newCalibration.size(); ++i) { auto diff = mCounters[i]; if (newCalibration[i] > diff) { newCalibration[i] = diff; } } } mCalibratedOverhead = std::move(newCalibration); { // calibrate loop overhead. For branches & instructions this makes sense, not so much // for everything else like cycles. marsaglia's xorshift: mov, sal/shr, xor. Times 3. // This has the nice property that the compiler doesn't seem to be able to optimize // multiple calls any further. see https://godbolt.org/z/49RVQ5 uint64_t const numIters = 100000U + (std::random_device{}() & 3); uint64_t n = numIters; uint32_t x = 1234567; beginMeasure(); while (n-- > 0) { x = mix(x); } endMeasure(); detail::doNotOptimizeAway(x); auto measure1 = mCounters; n = numIters; beginMeasure(); while (n-- > 0) { // we now run *twice* so we can easily calculate the overhead x = mix(x); x = mix(x); } endMeasure(); detail::doNotOptimizeAway(x); auto measure2 = mCounters; for (size_t i = 0; i < mCounters.size(); ++i) { // factor 2 because we have two instructions per loop auto m1 = measure1[i] > mCalibratedOverhead[i] ? measure1[i] - mCalibratedOverhead[i] : 0; auto m2 = measure2[i] > mCalibratedOverhead[i] ? measure2[i] - mCalibratedOverhead[i] : 0; auto overhead = m1 * 2 > m2 ? m1 * 2 - m2 : 0; mLoopOverhead[i] = divRounded(overhead, numIters); } } } struct Target { uint64_t* targetValue; bool correctMeasuringOverhead; bool correctLoopOverhead; }; std::map<uint64_t, Target> mIdToTarget{}; // start with minimum size of 3 for read_format std::vector<uint64_t> mCounters{3}; std::vector<uint64_t> mCalibratedOverhead{3}; std::vector<uint64_t> mLoopOverhead{3}; uint64_t mTimeEnabledNanos = 0; uint64_t mTimeRunningNanos = 0; int mFd = -1; bool mHasError = false; ~LinuxPerformanceCounter() { if (mFd != -1) close(mFd); } bool monitor(perf_sw_ids swId, Target target) { return monitor(PERF_TYPE_SOFTWARE, swId, target); } bool monitor(perf_hw_id hwId, Target target) { return monitor(PERF_TYPE_HARDWARE, hwId, target); } bool monitor(uint32_t type, uint64_t eventid, Target target) { *target.targetValue = (std::numeric_limits<uint64_t>::max)(); if (mHasError) return false; auto pea = perf_event_attr(); std::memset(&pea, 0, sizeof(perf_event_attr)); pea.type = type; pea.size = sizeof(perf_event_attr); pea.config = eventid; pea.disabled = 1; // start counter as disabled pea.exclude_kernel = 1; pea.exclude_hv = 1; pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; const int pid = 0; // the current process const int cpu = -1; // all CPUs #if defined(PERF_FLAG_FD_CLOEXEC) // since Linux 3.14 const unsigned long flags = PERF_FLAG_FD_CLOEXEC; #else const unsigned long flags = 0; #endif auto fd = static_cast<int>(syscall(__NR_perf_event_open, &pea, pid, cpu, mFd, flags)); if (-1 == fd) return false; // first call: set to fd, and use this from now on if (-1 == mFd) mFd = fd; uint64_t id = 0; if (-1 == ioctl(fd, PERF_EVENT_IOC_ID, &id)) return false; // insert into map, rely on the fact that map's references are constant. mIdToTarget.emplace(id, target); // prepare readformat with the correct size (after the insert) auto size = 3 + 2 * mIdToTarget.size(); mCounters.resize(size); mCalibratedOverhead.resize(size); mLoopOverhead.resize(size); return true; } void updateResults(uint64_t numIters) { // clear old data for (auto& id_value : mIdToTarget) { *id_value.second.targetValue = UINT64_C(0); } if (mHasError) return; mTimeEnabledNanos = mCounters[1] - mCalibratedOverhead[1]; mTimeRunningNanos = mCounters[2] - mCalibratedOverhead[2]; for (uint64_t i = 0; i < mCounters[0]; ++i) { auto idx = static_cast<size_t>(3 + i * 2 + 0); auto id = mCounters[idx + 1U]; auto it = mIdToTarget.find(id); if (it != mIdToTarget.end()) { auto& tgt = it->second; *tgt.targetValue = mCounters[idx]; if (tgt.correctMeasuringOverhead) { if (*tgt.targetValue >= mCalibratedOverhead[idx]) { *tgt.targetValue -= mCalibratedOverhead[idx]; } else { *tgt.targetValue = 0U; } } if (tgt.correctLoopOverhead) { auto correctionVal = mLoopOverhead[idx] * numIters; if (*tgt.targetValue >= correctionVal) { *tgt.targetValue -= correctionVal; } else { *tgt.targetValue = 0U; } } } } } }; } // namespace detail #endif #ifdef ZEROERR_ETW #define INITGUID #define NOMINMAX #include <Windows.h> #include <evntrace.h> #include <wmistr.h> namespace detail { struct WindowsPerformanceCounter { TRACEHANDLE mTraceHandle; std::string name = "ZeroErr ETW"; PEVENT_TRACE_PROPERTIES traceProperties; inline void beginMeasure() { size_t buffersize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME); traceProperties = (PEVENT_TRACE_PROPERTIES)malloc(buffersize); ZeroMemory(traceProperties, buffersize); traceProperties->Wnode.BufferSize = buffersize; traceProperties->Wnode.Flags = WNODE_FLAG_TRACED_GUID; traceProperties->Wnode.Guid = SystemTraceControlGuid; traceProperties->Wnode.ClientContext = 1; // QPC clock resolution traceProperties->BufferSize = 32; // 32 KB traceProperties->MinimumBuffers = 32; // 32 buffers traceProperties->MaximumBuffers = 32; // 32 buffers traceProperties->LogFileMode = EVENT_TRACE_BUFFERING_MODE; traceProperties->EnableFlags = EVENT_TRACE_FLAG_CSWITCH; traceProperties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES); ULONG status = StartTrace(&mTraceHandle, KERNEL_LOGGER_NAME, traceProperties); if (ERROR_SUCCESS != status) { if (ERROR_ALREADY_EXISTS == status) { printf("The NT Kernel Logger session is already in use.\n"); } else { printf("EnableTrace() failed with %lu\n", status); } goto cleanup; } // I got those values from here: // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ke/profobj/kprofile_source.htm // TotalIssues TotalCycles CacheMisses BranchMispredictions unsigned long perf_counter[4] = {0x02, 0x13, 0x0A, 0x0B}; TraceSetInformation(mTraceHandle, TracePmcCounterListInfo, perf_counter, sizeof(perf_counter)); cleanup: if (mTraceHandle) { status = ControlTrace(mTraceHandle, KERNEL_LOGGER_NAME, traceProperties, EVENT_TRACE_CONTROL_STOP); if (ERROR_SUCCESS != status) printf("ControlTrace(stop) failed with %lu\n", status); } if (traceProperties) { free(traceProperties); traceProperties = nullptr; } } inline void endMeasure() { StopTrace(mTraceHandle, KERNEL_LOGGER_NAME, traceProperties); if (traceProperties) { free(traceProperties); traceProperties = nullptr; } } }; } // namespace detail #endif PerformanceCounter::PerformanceCounter() { _has.timeElapsed() = true; // this should be always available #ifdef ZEROERR_PERF _perf = new detail::LinuxPerformanceCounter(); using Target = detail::LinuxPerformanceCounter::Target; // clang-format off _has.pageFaults() = _perf->monitor(PERF_COUNT_SW_PAGE_FAULTS, Target{&_val.pageFaults(), true, false}); _has.cpuCycles() = _perf->monitor(PERF_COUNT_HW_CPU_CYCLES, Target{&_val.cpuCycles(), true, false}); _has.contextSwitches() = _perf->monitor(PERF_COUNT_SW_CONTEXT_SWITCHES, Target{&_val.contextSwitches(), true, false}); _has.instructions() = _perf->monitor(PERF_COUNT_HW_INSTRUCTIONS, Target{&_val.instructions(), true, true }); _has.branchInstructions() = _perf->monitor(PERF_COUNT_HW_BRANCH_INSTRUCTIONS, Target{&_val.branchInstructions(), true, false}); _has.branchMisses() = _perf->monitor(PERF_COUNT_HW_BRANCH_MISSES, Target{&_val.branchMisses(), true, false}); // clang-format on _perf->calibrate([] { auto before = Clock::now(); auto after = Clock::now(); (void)before; (void)after; }); if (_perf->mHasError) { // something failed, don't monitor anything. _has = PerfCountSet<bool>{}; } #endif #ifdef ZEROERR_ETW win_perf = new detail::WindowsPerformanceCounter(); #endif } PerformanceCounter::~PerformanceCounter() { #ifdef ZEROERR_PERF delete _perf; #endif } PerformanceCounter& PerformanceCounter::inst() { static PerformanceCounter counter; return counter; } void PerformanceCounter::beginMeasure() { #ifdef ZEROERR_PERF _perf->beginMeasure(); #endif _start = Clock::now(); } void PerformanceCounter::endMeasure() { elapsed = Clock::now() - _start; #ifdef ZEROERR_PERF _perf->endMeasure(); #endif } void PerformanceCounter::updateResults(uint64_t numIters) { #ifdef ZEROERR_PERF _perf->updateResults(numIters); #else (void)numIters; #endif } } // namespace zeroerr #endif // ZEROERR_IMPLEMENTATION