-
-
Notifications
You must be signed in to change notification settings - Fork 190
Description
Environment
toml++ version and/or commit hash:
master branch (latest as of Feb 2026, fetched via CMake FetchContent)
Compiler:
MSVC 19.x (Visual Studio 2022/2026 Preview)
C++ standard mode:
C++23
Target arch:
x64
Library configuration overrides:
TOMLPLUSPLUS_BUILD_MODULES=ON
TOML_DISABLE_NOEXCEPT_NOEXCEPT=1
Describe the bug
When using toml++ as a C++20 module with MSVC, the depth_counter_scope RAII struct in parser.inl has its
destructor called twice for a single constructor call. This causes an unsigned integer underflow in
nested_values, leading to a spurious "exceeded maximum nested value depth" parse error when parsing completely valid
TOML files.
The issue occurs consistently when parsing TOML with more than 2 key-value pairs. Simple TOML like x = 1 parses
fine, but any real-world config file fails.
Error message:
Error while parsing value: exceeded maximum nested value depth of 256 (TOML_MAX_NESTED_VALUES)
Steps to reproduce
- Set up toml++ as a C++20 module via CMake:
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
GIT_TAG master
)
set(TOMLPLUSPLUS_BUILD_MODULES ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(tomlplusplus)
2. Import and parse any TOML with 3+ key-value pairs:
import tomlplusplus;
auto table = toml::parse(R"(
[Graphics]
x = 1
y = 2
z = 3
)");
3. Observe the spurious parse error.
Additional information
Root cause analysis:
Adding debug prints to the depth_counter_scope constructor and destructor reveals the double-destruction:
[DEPTH] ctor this=00000084397FB948 before=0, after=1
[DEPTH] dtor this=00000084397FB948 before=1, after=0 <-- First destruction (correct)
[DEPTH] dtor this=00000084397FB948 before=0, after=SIZE_MAX <-- SECOND destruction, SAME this pointer!
The same object (identical this pointer 00000084397FB948) has its destructor called twice, causing nested_values to
underflow from 0 to SIZE_MAX (18446744073709551615).
Workaround:
Adding a destruction guard flag to depth_counter_scope in parser.inl fixes the issue:
struct depth_counter_scope
{
size_t& depth_;
bool destroyed_ = false;
TOML_NODISCARD_CTOR
explicit depth_counter_scope(size_t& depth) noexcept
: depth_{ depth }
{
depth_++;
}
~depth_counter_scope() noexcept
{
if (!destroyed_)
{
destroyed_ = true;
depth_--;
}
}
TOML_DELETE_DEFAULTS(depth_counter_scope);
};
Notes:
- The issue does NOT occur with the header-only version (TOML_HEADER_ONLY=1) - only with the modules build
- This appears to be an MSVC codegen bug with C++20 modules, but a workaround in toml++ would help users until Microsoft fixes it