Merge branch 'dev' into jr/more-thorough-error-handling

This commit is contained in:
Jeremy 2024-03-30 13:25:27 -05:00
commit 89a7afaded
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
17 changed files with 435 additions and 64 deletions

View File

@ -34,14 +34,15 @@ jobs:
- uses: actions/checkout@v4
- name: dependencies
run: |
pip3 install colorama
python3 -m venv env
env/bin/pip install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}} --default-config
build-windows:
runs-on: windows-2022
strategy:
@ -95,14 +96,15 @@ jobs:
- uses: actions/checkout@v4
- name: dependencies
run: |
pip3 install colorama
python3 -m venv env
env/bin/pip install colorama
- name: libdwarf
run: |
cd ..
cpptrace/ci/setup-prerequisites.sh
- name: build
run: |
python3 ci/build-in-all-configs.py --${{matrix.compiler}}
env/bin/python ci/build-in-all-configs.py --${{matrix.compiler}}
build-windows-all-configurations:
runs-on: windows-2022
needs: build-windows

View File

@ -42,10 +42,11 @@ jobs:
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
pip3 install colorama
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}} --default-config
env/bin/python ci/test-all-configs.py --${{matrix.compiler}} --default-config
test-windows:
runs-on: windows-2022
strategy:
@ -106,10 +107,11 @@ jobs:
cpptrace/ci/setup-prerequisites.sh
- name: dependencies
run: |
pip3 install colorama
python3 -m venv env
env/bin/pip install colorama
- name: build and test
run: |
python3 ci/test-all-configs.py --${{matrix.compiler}}
env/bin/python ci/test-all-configs.py --${{matrix.compiler}}
test-windows-all-configurations:
runs-on: windows-2022
strategy:

View File

@ -1,6 +1,7 @@
# Changelog
- [Changelog](#changelog)
- [v0.5.0](#v050)
- [v0.4.1](#v041)
- [v0.4.0](#v040)
- [v0.3.1](#v031)
@ -10,6 +11,30 @@
- [v0.1.1](#v011)
- [v0.1](#v01)
# v0.5.1
Fixes:
- Fix MSVC warning treated as error for 32-bit windows
- Fix MSVC issue with min/max macros
- Fix potential null dereference issue identified by eyalgolan1337
# v0.5.0
New:
- Traces with source code snippets with `cpptrace::stacktrace::print_with_snippets`
- Added `cpptrace::get_snippet` utility
- Added `cpptrace::can_signal_safe_unwind` utility
- Added `stacktrace_frame::get_object_info`
Changes:
- The library is now compiled with position-independent code by default
Fixes:
- Fixed issue with `_dl_find_object` implementation
Misc:
- Various refactoring, cleanup, and improvements
# v0.4.1
Changes:

View File

@ -9,7 +9,7 @@ set(package_name "cpptrace")
project(
cpptrace
VERSION 0.4.1
VERSION 0.5.1
DESCRIPTION "Simple, portable, and self-contained stacktrace library for C++11 and newer "
HOMEPAGE_URL "https://github.com/jeremy-rifkin/cpptrace"
LANGUAGES C CXX
@ -288,6 +288,8 @@ target_compile_features(
PRIVATE cxx_std_11
)
target_compile_definitions(${target_name} PRIVATE NOMINMAX)
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
SET(CMAKE_C_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")
SET(CMAKE_CXX_ARCHIVE_FINISH "<CMAKE_RANLIB> -no_warning_for_no_symbols -c <TARGET>")

View File

@ -6,7 +6,7 @@
<br/>
[![Community Discord Link](https://img.shields.io/badge/Chat%20on%20the%20(very%20small)-Community%20Discord-blue?labelColor=2C3239&color=7289DA&style=flat&logo=discord&logoColor=959DA5)](https://discord.gg/frjaAZvqUZ)
<br/>
[![Try on Compiler Explorer](https://img.shields.io/badge/-Compiler%20Explorer-brightgreen?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAACXBIWXMAAACwAAAAsAEUaqtpAAABSElEQVQokYVTsU7DMBB9QMTCEJbOMLB5oF0tRfUPIPIJZctYJkZYu3WMxNL+ARUfQKpImcPgDYnsXWBgYQl61TkYyxI3Wef37j3fnQ/6vkcsikY9AbiWq0mpbevDBmLRqDEAA4CEHMADgFRwrwDmch6X2i73RCFVHvC/WCeCMAFpC2AFoPPu5x4md4rnAN4luS61nYWSgauNU8ydkr0bLTMYAoIYtWqxM4LtEumeERDtfUjlMDrp7L67iddyyJtOvUIu2rquVn4iiVSOKXYhiMSJWLwUJZLuQ2CWmVldV4MT11UmXgB8fr0dX3WP6VHMiVrscim6Da2mJxffzwSU2v6xWzSKmzQ4cUTOaCBTvWgU14xkzjhckKm/q3wnrRAcAhksxMZNAdxEf0fRKI6E8zqT1C0X28ccRpqAUltW5pu4sxv5Mb8B4AciE3bHMxz/+gAAAABJRU5ErkJggg==&labelColor=2C3239&style=flat&label=Try+it+on&color=30C452)](https://godbolt.org/z/5sEszzEPE)
[![Try on Compiler Explorer](https://img.shields.io/badge/-Compiler%20Explorer-brightgreen?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAACXBIWXMAAACwAAAAsAEUaqtpAAABSElEQVQokYVTsU7DMBB9QMTCEJbOMLB5oF0tRfUPIPIJZctYJkZYu3WMxNL+ARUfQKpImcPgDYnsXWBgYQl61TkYyxI3Wef37j3fnQ/6vkcsikY9AbiWq0mpbevDBmLRqDEAA4CEHMADgFRwrwDmch6X2i73RCFVHvC/WCeCMAFpC2AFoPPu5x4md4rnAN4luS61nYWSgauNU8ydkr0bLTMYAoIYtWqxM4LtEumeERDtfUjlMDrp7L67iddyyJtOvUIu2rquVn4iiVSOKXYhiMSJWLwUJZLuQ2CWmVldV4MT11UmXgB8fr0dX3WP6VHMiVrscim6Da2mJxffzwSU2v6xWzSKmzQ4cUTOaCBTvWgU14xkzjhckKm/q3wnrRAcAhksxMZNAdxEf0fRKI6E8zqT1C0X28ccRpqAUltW5pu4sxv5Mb8B4AciE3bHMxz/+gAAAABJRU5ErkJggg==&labelColor=2C3239&style=flat&label=Try+it+on&color=30C452)](https://godbolt.org/z/c6TqTzqcf)
Cpptrace is a simple, portable, and self-contained C++ stacktrace library supporting C++11 and greater on Linux, macOS,
and Windows including MinGW and Cygwin environments. The goal: Make stack traces simple for once.
@ -93,6 +93,9 @@ Additional notable features:
- Utilities for demangling
- Utilities for catching `std::exception`s and wrapping them in traced exceptions
- Signal-safe stack tracing
- Source code snippets in traces
![Snippets](res/snippets.png)
## CMake FetchContent Usage
@ -101,7 +104,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.4.1 # <HASH or TAG>
GIT_TAG v0.5.1 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
@ -139,14 +142,18 @@ thrown, and providing an API for safe tracing from signal handlers.
# In-Depth Documentation
## Prerequisites
> [!IMPORTANT]
> Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`/`-DBUILD_TYPE=Debug`/`-DBUILD_TYPE=RelWithDebInfo`) is required for complete
> trace information.
## `namespace cpptrace`
`cpptrace::generate_trace()` can be used to generate a stacktrace object at the current call site. Resolved frames can
be accessed from this object with `.frames` and also the trace can be printed with `.print()`. Cpptrace also provides a
method to get lightweight raw traces, which are just vectors of program counters, which can be resolved at a later time.
**Note:** Debug info (`-g`/`/Z7`/`/Zi`/`/DEBUG`) is generally required for good trace information.
All functions are thread-safe unless otherwise noted.
### Stack Traces
@ -155,6 +162,9 @@ The core resolved stack trace object. Generate a trace with `cpptrace::generate_
`cpptrace::stacktrace::current()`. On top of a set of helper functions `struct stacktrace` allows
direct access to frames as well as iterators.
`cpptrace::stacktrace::print` can be used to print a stacktrace. `cpptrace::stacktrace::print_with_snippets` can be used
to print a stack trace with source code snippets.
```cpp
namespace cpptrace {
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
@ -184,6 +194,9 @@ namespace cpptrace {
void print() const;
void print(std::ostream& stream) const;
void print(std::ostream& stream, bool color) const;
void print_with_snippets() const;
void print_with_snippets(std::ostream& stream) const;
void print_with_snippets(std::ostream& stream, bool color) const;
std::string to_string(bool color = false) const;
void clear();
bool empty() const noexcept;
@ -554,7 +567,7 @@ include(FetchContent)
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v0.4.1 # <HASH or TAG>
GIT_TAG v0.5.1 # <HASH or TAG>
)
FetchContent_MakeAvailable(cpptrace)
target_link_libraries(your_target cpptrace::cpptrace)
@ -570,7 +583,7 @@ information.
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.4.1
git checkout v0.5.1
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -606,7 +619,7 @@ you when installing new libraries.
```ps1
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.4.1
git checkout v0.5.1
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release
@ -624,7 +637,7 @@ To install just for the local user (or any custom prefix):
```sh
git clone https://github.com/jeremy-rifkin/cpptrace.git
git checkout v0.4.1
git checkout v0.5.1
mkdir cpptrace/build
cd cpptrace/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$HOME/wherever
@ -661,7 +674,8 @@ The typical dependencies for cpptrace are:
Note: Newer libdwarf requires `-lzstd`, older libdwarf does not.
If you are linking statically, you will additionally need to specify `-DCPPTRACE_STATIC_DEFINE`.
> [!IMPORTANT]
> If you are linking statically, you will additionally need to specify `-DCPPTRACE_STATIC_DEFINE`.
Dependencies may differ if different back-ends are manually selected.
@ -703,7 +717,7 @@ make install
cd ~/scratch/cpptrace-test
git clone https://github.com/jeremy-rifkin/cpptrace.git
cd cpptrace
git checkout v0.4.1
git checkout v0.5.1
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=On -DCPPTRACE_USE_EXTERNAL_LIBDWARF=On -DCMAKE_PREFIX_PATH=~/scratch/cpptrace-test/resources -DCMAKE_INSTALL_PREFIX=~/scratch/cpptrace-test/resources
@ -723,7 +737,7 @@ cpptrace and its dependencies.
Cpptrace is available through conan at https://conan.io/center/recipes/cpptrace.
```
[requires]
cpptrace/0.4.1
cpptrace/0.5.1
[generators]
CMakeDeps
CMakeToolchain

View File

@ -118,7 +118,7 @@ namespace cpptrace {
return *this;
}
bool has_value() const noexcept {
return raw_value != std::numeric_limits<T>::max();
return raw_value != (std::numeric_limits<T>::max)();
}
T& value() noexcept {
return raw_value;
@ -133,7 +133,7 @@ namespace cpptrace {
std::swap(raw_value, other.raw_value);
}
void reset() noexcept {
raw_value = std::numeric_limits<T>::max();
raw_value = (std::numeric_limits<T>::max)();
}
bool operator==(const nullable& other) const noexcept {
return raw_value == other.raw_value;
@ -142,7 +142,7 @@ namespace cpptrace {
return raw_value != other.raw_value;
}
constexpr static nullable null() noexcept {
return { std::numeric_limits<T>::max() };
return { (std::numeric_limits<T>::max)() };
}
};

BIN
res/snippets.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -44,14 +44,7 @@ namespace detail {
frame.object_path = buffer;
}
}
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- to_frame_ptr(result.dlfo_link_map->l_addr)
+ base.unwrap_value();
} else {
base.drop_error();
}
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
}
return frame;
}

View File

@ -9,6 +9,7 @@
#include <iostream>
#include "../utils/common.hpp"
#include "../utils/utils.hpp"
namespace cpptrace {
namespace detail {
@ -36,7 +37,7 @@ namespace detail {
}
// else load file
file.seekg(0, std::ios::beg);
contents.resize(size);
contents.resize(to<std::size_t>(size));
if(!file.read(&contents[0], size)) {
// error ...
}

View File

@ -325,6 +325,13 @@ namespace dbghelp {
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
// The get_frame_object_info() ends up being inexpensive, at on my machine
// debug release
// uncached trace resolution (29 frames) 1.9-2.1 ms 1.4-1.8 ms
// cached trace resolution (29 frames) 1.1-1.2 ms 0.2-0.4 ms
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
// At some point it might make sense to make an option to control this.
auto object_frame = get_frame_object_info(addr);
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
@ -345,7 +352,7 @@ namespace dbghelp {
std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
return {
addr,
0,
object_frame.object_address,
{ static_cast<std::uint32_t>(line.LineNumber) },
nullable<std::uint32_t>::null(),
line.FileName,
@ -378,7 +385,7 @@ namespace dbghelp {
signature = std::regex_replace(signature, comma_re, ", ");
return {
addr,
0,
object_frame.object_address,
{ static_cast<std::uint32_t>(line.LineNumber) },
nullable<std::uint32_t>::null(),
line.FileName,
@ -388,7 +395,7 @@ namespace dbghelp {
} else {
return {
addr,
0,
object_frame.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
@ -397,7 +404,15 @@ namespace dbghelp {
};
}
} else {
return { addr, 0, nullable<std::uint32_t>::null(), nullable<std::uint32_t>::null(), "", "", false };
return {
addr,
object_frame.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
"",
false
};
}
}

View File

@ -38,7 +38,7 @@ namespace libbacktrace {
}
void error_callback(void*, const char* msg, int errnum) {
throw internal_error(stringf("Libbacktrace error: %s, code %d\n", msg, errnum));
throw internal_error(microfmt::format("Libbacktrace error: {}, code {}\n", msg, errnum));
}
backtrace_state* get_backtrace_state() {

View File

@ -60,18 +60,6 @@
namespace cpptrace {
namespace detail {
// Placed here instead of utils because it's used by error.hpp and utils.hpp
template<typename... T> 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,

View File

@ -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 internal_error(stringf("SymInitialize failed %llu", to_ull(GetLastError())));
throw internal_error(microfmt::format("SymInitialize failed {}", GetLastError()));
}
set.insert(proc);
}

View File

@ -28,7 +28,7 @@ namespace libdwarf {
char* msg = dwarf_errmsg(error);
(void)dbg;
// dwarf_dealloc_error(dbg, error);
throw internal_error(stringf("Cpptrace dwarf error %u %s\n", ev, msg));
throw internal_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));
}
}
@ -343,7 +343,7 @@ namespace libdwarf {
return;
}
Dwarf_Addr baseaddr = 0;
if(lowpc != std::numeric_limits<Dwarf_Addr>::max()) {
if(lowpc != (std::numeric_limits<Dwarf_Addr>::max)()) {
baseaddr = lowpc;
}
Dwarf_Ranges* ranges = nullptr;
@ -381,7 +381,7 @@ namespace libdwarf {
template<typename F>
// callback should return true to keep going
void dwarf_ranges(int version, optional<Dwarf_Addr> pc, F callback) const {
Dwarf_Addr lowpc = std::numeric_limits<Dwarf_Addr>::max();
Dwarf_Addr lowpc = (std::numeric_limits<Dwarf_Addr>::max)();
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
if(pc.has_value() && pc.unwrap() == lowpc) {
callback(lowpc, lowpc + 1);

View File

@ -7,6 +7,7 @@
#include <utility>
#include "common.hpp"
#include "microfmt.hpp"
#if IS_MSVC
#define CPPTRACE_PFUNC __FUNCSIG__
@ -62,8 +63,8 @@ namespace detail {
const char* name = assert_names[static_cast<std::underlying_type<assert_type>::type>(type)];
if(message == "") {
throw internal_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
@ -71,8 +72,8 @@ namespace detail {
);
} else {
throw internal_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
@ -88,15 +89,15 @@ namespace detail {
) {
if(message == "") {
throw internal_error(
stringf(
"Cpptrace panic %s:%d: %s\n",
microfmt::format(
"Cpptrace panic {}:{}: {}\n",
location.file, location.line, signature
)
);
} else {
throw internal_error(
stringf(
"Cpptrace panic %s:%d: %s: %s\n",
microfmt::format(
"Cpptrace panic {}:{}: {}: {}\n",
location.file, location.line, signature, message.c_str()
)
);

325
src/utils/microfmt.hpp Normal file
View File

@ -0,0 +1,325 @@
#ifndef MICROFMT_HPP
#define MICROFMT_HPP
// Copyright (c) 2024 Jeremy Rifkin; MIT License
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L
#include <string_view>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#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<typename It> 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<std::size_t>(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<int shift, int mask>
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<int>(int64_value);
case value_type::uint64_value: return static_cast<int>(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<std::uint64_t>(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(const char* begin, const char* end) {
int x = 0;
for(auto it = begin; it != end; it++) {
MICROFMT_ASSERT(isdigit(*it));
x *= 10;
x += *it - '0';
}
return x;
}
template<std::size_t N>
std::string format(const char* fmt_begin, const char* fmt_end, std::array<format_value, N> args) {
std::string str;
std::size_t arg_i = 0;
auto it = fmt_begin;
auto peek = [&] (std::size_t dist = 1) -> char { // 0 on failure
if(it != fmt_end) {
return *(it + dist);
} else {
return 0;
}
};
auto read_number = [&] () -> int { // -1 on failure
auto scan = it;
while(scan != fmt_end && isdigit(*scan)) {
scan++;
}
if(scan != it) {
int val = parse_int(it, scan);
it = scan;
return val;
} else {
return -1;
}
};
while(it != fmt_end) {
if(*it == '{') {
if(peek() == '{') {
// try to handle escape
str += '{';
it++;
} else {
// parse format string
it++;
MICROFMT_ASSERT(it != fmt_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<typename... Args>
#if defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L
std::string format(std::string_view fmt, Args&&... args) {
#else
std::string format(const std::string& fmt, Args&&... args) {
#endif
return detail::format<sizeof...(args)>(fmt.begin(), fmt.end(), {detail::format_value(args)...});
}
template<typename... Args>
std::string format(const char* fmt, Args&&... args) {
return detail::format<sizeof...(args)>(fmt, fmt + std::strlen(fmt), {detail::format_value(args)...});
}
template<typename S, typename... Args>
void print(const S& fmt, Args&&... args) {
std::cout<<format(fmt, args...);
}
template<typename S, typename... Args>
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
ostream<<format(fmt, args...);
}
}
#endif

View File

@ -19,6 +19,7 @@
#include "common.hpp"
#include "error.hpp"
#include "microfmt.hpp"
#if IS_WINDOWS
#include <windows.h>
@ -607,7 +608,9 @@ namespace detail {
}
inline void file_deleter(std::FILE* ptr) {
fclose(ptr);
if(ptr) {
fclose(ptr);
}
}
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;