Skip to content

Depth counter scope double destruction bug with MSVC and modules. #288

@DhirenTheHeadlights

Description

@DhirenTheHeadlights

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

  1. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions