diff --git a/src/symbols/symbols_with_libbacktrace.cpp b/src/symbols/symbols_with_libbacktrace.cpp index 2d7937b..c719bba 100644 --- a/src/symbols/symbols_with_libbacktrace.cpp +++ b/src/symbols/symbols_with_libbacktrace.cpp @@ -38,7 +38,7 @@ namespace libbacktrace { } void error_callback(void*, const char* msg, int errnum) { - throw std::runtime_error(stringf("Libbacktrace error: %s, code %d\n", msg, errnum)); + throw std::runtime_error(microfmt::format("Libbacktrace error: {}, code {}\n", msg, errnum)); } backtrace_state* get_backtrace_state() { diff --git a/src/utils/common.hpp b/src/utils/common.hpp index d091afa..b6426fe 100644 --- a/src/utils/common.hpp +++ b/src/utils/common.hpp @@ -52,18 +52,6 @@ namespace cpptrace { namespace detail { - // Placed here instead of utils because it's used by error.hpp and utils.hpp - template std::string stringf(T... args) { - int length = std::snprintf(nullptr, 0, args...); - if(length < 0) { - throw std::logic_error("invalid arguments to stringf"); - } - std::string str(length, 0); - // .data is const char* in c++11, but &str[0] should be legal - std::snprintf(&str[0], length + 1, args...); - return str; - } - static const stacktrace_frame null_frame { 0, 0, diff --git a/src/utils/dbghelp_syminit_manager.hpp b/src/utils/dbghelp_syminit_manager.hpp index 6f7ffd7..688b349 100644 --- a/src/utils/dbghelp_syminit_manager.hpp +++ b/src/utils/dbghelp_syminit_manager.hpp @@ -17,7 +17,7 @@ namespace detail { ~dbghelp_syminit_manager() { for(auto handle : set) { if(!SymCleanup(handle)) { - ASSERT(false, stringf("Cpptrace SymCleanup failed with code %llu\n", to_ull(GetLastError()))); + ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError())); } } } @@ -25,7 +25,7 @@ namespace detail { void init(HANDLE proc) { if(set.count(proc) == 0) { if(!SymInitialize(proc, NULL, TRUE)) { - throw std::logic_error(stringf("SymInitialize failed %llu", to_ull(GetLastError()))); + throw std::logic_error(microfmt::format("SymInitialize failed {}", GetLastError())); } set.insert(proc); } diff --git a/src/utils/dwarf.hpp b/src/utils/dwarf.hpp index 468e318..3a282de 100644 --- a/src/utils/dwarf.hpp +++ b/src/utils/dwarf.hpp @@ -28,7 +28,7 @@ namespace libdwarf { char* msg = dwarf_errmsg(error); (void)dbg; // dwarf_dealloc_error(dbg, error); - throw std::runtime_error(stringf("Cpptrace dwarf error %u %s\n", ev, msg)); + throw std::runtime_error(microfmt::format("Cpptrace dwarf error {} {}\n", ev, msg)); } struct die_object { @@ -235,7 +235,7 @@ namespace libdwarf { return die_object(dbg, target); } default: - PANIC(stringf("unknown form for attribute %d %d\n", attr_num, form)); + PANIC(microfmt::format("unknown form for attribute {} {}\n", attr_num, form)); } } diff --git a/src/utils/error.hpp b/src/utils/error.hpp index d0533ab..962844f 100644 --- a/src/utils/error.hpp +++ b/src/utils/error.hpp @@ -7,6 +7,7 @@ #include #include "common.hpp" +#include "microfmt.hpp" #if IS_MSVC #define CPPTRACE_PFUNC __FUNCSIG__ @@ -57,8 +58,8 @@ namespace detail { const char* name = assert_names[static_cast::type>(type)]; if(message == "") { throw std::logic_error( - stringf( - "Cpptrace %s failed at %s:%d: %s\n" + microfmt::format( + "Cpptrace {} failed at {}:{}: {}\n" " %s(%s);\n", action, location.file, location.line, signature, name, expression @@ -66,8 +67,8 @@ namespace detail { ); } else { throw std::logic_error( - stringf( - "Cpptrace %s failed at %s:%d: %s: %s\n" + microfmt::format( + "Cpptrace {} failed at {}:{}: {}: {}\n" " %s(%s);\n", action, location.file, location.line, signature, message.c_str(), name, expression @@ -83,15 +84,15 @@ namespace detail { ) { if(message == "") { throw std::logic_error( - stringf( - "Cpptrace panic %s:%d: %s\n", + microfmt::format( + "Cpptrace panic {}:{}: {}\n", location.file, location.line, signature ) ); } else { throw std::logic_error( - stringf( - "Cpptrace panic %s:%d: %s: %s\n", + microfmt::format( + "Cpptrace panic {}:{}: {}: {}\n", location.file, location.line, signature, message.c_str() ) ); diff --git a/src/utils/microfmt.hpp b/src/utils/microfmt.hpp new file mode 100644 index 0000000..4386b03 --- /dev/null +++ b/src/utils/microfmt.hpp @@ -0,0 +1,316 @@ +#ifndef MICROFMT_HPP +#define MICROFMT_HPP + +// Copyright (c) 2024 Jeremy Rifkin; MIT License + +#include +#include +#include +#include +#include +#include +#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L +#include +#endif +#ifdef _MSC_VER +#include +#endif + +// {[[align][width]:[fill][base]]} +// width: number or {} + +namespace microfmt { + namespace detail { + #define STR2(x) #x + #define STR(x) STR2(x) + #define MICROFMT_ASSERT(expr) if(!(expr)) { \ + throw std::runtime_error("Microfmt check failed" __FILE__ ":" STR(__LINE__) ": " #expr); \ + } + + #ifdef _MSC_VER + inline std::uint64_t clz(std::uint64_t value) { + unsigned long out = 0; + #ifdef _WIN64 + _BitScanForward64(&out, value); + #else + if(_BitScanForward(&out, std::uint32_t(value >> 32))) { + return 63 ^ int(out + 32); + } + _BitScanForward(&out, std::uint32_t(value)); + #endif + return out; + } + #else + inline std::uint64_t clz(std::uint64_t value) { + return __builtin_clzll(value); + } + #endif + + enum class alignment { left, right }; + + struct format_options { + alignment align = alignment::left; + char fill = ' '; + size_t width = 0; + char base = 'd'; + }; + + template void do_write(std::string& out, It begin, It end, const format_options& options) { + auto size = end - begin; + MICROFMT_ASSERT(size >= 0); + if(static_cast(size) >= options.width) { + out.append(begin, end); + } else { + auto out_size = out.size(); + out.resize(out_size + options.width); + if(options.align == alignment::left) { + std::copy(begin, end, out.begin() + out_size); + std::fill(out.begin() + out_size + size, out.end(), options.fill); + } else { + std::fill(out.begin() + out_size, out.begin() + out_size + (options.width - size), options.fill); + std::copy(begin, end, out.begin() + out_size + (options.width - size)); + } + } + } + + template + std::string to_string(std::uint64_t value, const char* digits = "0123456789abcdef") { + if(value == 0) { + return "0"; + } else { + // digits = floor(1 + log_base(x)) + // log_base(x) = log_2(x) / log_2(base) + // log_2(x) == 63 - clz(x) + // 1 + (63 - clz(value)) / (63 - clz(1 << shift)) + // 63 - clz(1 << shift) is the same as shift + auto n_digits = 1 + (63 - clz(value)) / shift; + std::string number; + number.resize(n_digits); + std::size_t i = n_digits - 1; + while(value > 0) { + number[i--] = digits[value & mask]; + value >>= shift; + } + return number; + } + } + + inline std::string to_string(std::uint64_t value, const format_options& options) { + switch(options.base) { + case 'd': return std::to_string(value); + case 'H': return to_string<4, 0xf>(value, "0123456789ABCDEF"); + case 'h': return to_string<4, 0xf>(value); + case 'o': return to_string<3, 0x7>(value); + case 'b': return to_string<1, 0x1>(value); + default: + MICROFMT_ASSERT(false); + } + } + + class format_value { + enum class value_type { + char_value, + int64_value, + uint64_value, + string_value, + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + string_view_value, + #endif + c_string_value, + }; + union { + char char_value; + std::int64_t int64_value; + std::uint64_t uint64_value; + const std::string* string_value; + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + std::string_view string_view_value; + #endif + const char* c_string_value; + }; + value_type value; + + public: + format_value(char c) : char_value(c), value(value_type::char_value) {} + format_value(short int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(int int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(long int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(long long int_val) : int64_value(int_val), value(value_type::int64_value) {} + format_value(unsigned char int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned short int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned int int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned long int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {} + format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {} + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + format_value(std::string_view sv) : string_view_value(sv), value(value_type::string_view_value) {} + #endif + format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {} + + int unwrap_int() const { + switch(value) { + case value_type::int64_value: return static_cast(int64_value); + case value_type::uint64_value: return static_cast(uint64_value); + default: MICROFMT_ASSERT(false); + } + } + + public: + void write(std::string& out, const format_options& options) const { + switch(value) { + case value_type::char_value: + do_write(out, &char_value, &char_value + 1, options); + break; + case value_type::int64_value: + { + std::string str; + std::int64_t val = int64_value; + if(val < 0) { + str += '-'; + val *= -1; + } + str += to_string(static_cast(val), options); + do_write(out, str.begin(), str.end(), options); + } + break; + case value_type::uint64_value: + { + std::string str = to_string(uint64_value, options); + do_write(out, str.begin(), str.end(), options); + } + break; + case value_type::string_value: + do_write(out, string_value->begin(), string_value->end(), options); + break; + #if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L + case value_type::string_view_value: + do_write(out, string_view_value.begin(), string_view_value.end(), options); + break; + #endif + case value_type::c_string_value: + do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options); + break; + default: + MICROFMT_ASSERT(false); + } + } + }; + + inline int parse_int(std::string::const_iterator begin, std::string::const_iterator end) { + int x = 0; + for(auto it = begin; it != end; it++) { + MICROFMT_ASSERT(isdigit(*it)); + x *= 10; + x += *it - '0'; + } + return x; + } + + template + std::string format(const std::string& format_string, std::array args) { + std::string str; + std::size_t arg_i = 0; + auto it = format_string.begin(); + auto peek = [&] (std::size_t dist = 1) -> char { // 0 on failure + if(it != format_string.end()) { + return *(it + dist); + } else { + return 0; + } + }; + auto read_number = [&] () -> int { // -1 on failure + auto scan = it; + while(scan != format_string.end() && isdigit(*scan)) { + scan++; + } + if(scan != it) { + int val = parse_int(it, scan); + it = scan; + return val; + } else { + return -1; + } + }; + while(it != format_string.end()) { + if(*it == '{') { + if(peek() == '{') { + // try to handle escape + str += '{'; + it++; + } else { + // parse format string + it++; + MICROFMT_ASSERT(it != format_string.end()); + format_options options; + // try to parse alignment + if(*it == '<' || *it == '>') { + options.align = *it == '<' ? alignment::left : alignment::right; + it++; + } + // try to parse width + auto width = read_number(); + if(width != -1) { + options.width = width; + } else if(*it == '{') { // try to parse variable width + MICROFMT_ASSERT(peek() == '}'); + it += 2; + MICROFMT_ASSERT(arg_i < N); + options.width = args[arg_i++].unwrap_int(); + } + // try to parse fill/base + if(*it == ':') { + it++; + // try to parse fill + if(*it != '}' && peek(1) != '}') { + // two chars before the }, treat as fill+base + options.fill = *it++; + options.base = *it++; + } else if(*it != '}') { + // one char before the }, treat as base if possible + if(*it == 'd' || *it == 'h' || *it == 'H' || *it == 'o' || *it == 'b') { + options.base = *it++; + } else { + options.fill = *it++; + } + } else { + MICROFMT_ASSERT(false); + } + } + MICROFMT_ASSERT(*it == '}'); + MICROFMT_ASSERT(arg_i < N); + args[arg_i++].write(str, options); + } + } else if(*it == '}') { + // parse }} escape + if(peek() == '}') { + str += '}'; + it++; + } else { + MICROFMT_ASSERT(false); + } + } else { + str += *it; + } + it++; + } + return str; + } + } + + template + std::string format(const std::string& format_string, Args... args) { + return detail::format(format_string, {detail::format_value(args)...}); + } + + template + void print(const std::string& format_string, Args... args) { + std::cout< + void print(std::ostream& ostream, const std::string& format_string, Args... args) { + ostream<