Skip to content

Fix false GCC -Warray-bounds error with JSON_DIAGNOSTICS at -O3 - #5744

Merged
nlohmann merged 4 commits into
claude/fix-allocation-exception-safetyfrom
issue-5742-diagnostics-array-bounds
Oct 2, 2026
Merged

nlohmann merged 4 commits into
claude/fix-allocation-exception-safetyfrom
issue-5742-diagnostics-array-bounds

Conversation

@nlohmann

@nlohmann nlohmann commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Fixes #5742.

Note

Stacked on #5585 (Create a value before giving it its type), which contains the actual fix. This PR only adds the regression test.

Problem

With JSON_DIAGNOSTICS=1, GCC reports a false -Warray-bounds error at -O3 (so -DCMAKE_BUILD_TYPE=Release + -Werror fails). The warning is located in libstdc++'s stl_tree.h: GCC checks the std::map access in the value_t::object branch of the inlined set_parents() against the 32-byte allocation of a string.

Cause

external_constructor<value_t::string> set the type before allocating the string:

j.m_data.m_type = value_t::string;
j.m_data.m_value.string = j.template create<string_t>(str); // operator new

GCC has to assume that operator new could change any memory, including m_type. So when set_parents() reads m_type again afterwards, GCC cannot rule out the object branch. When the type is set after the value is created, GCC knows the value is a string and drops that branch. #5585 makes exactly this change in every place (for exception safety), so it fixes this warning too, and no pragma is needed.

Rewriting set_parents() itself does not help: an if/else instead of the switch, or an early return via is_structured(), still fails on every affected GCC.

Measured with the #5742 repro at -O3 -Werror=array-bounds (Docker images):

GCC develop develop, set_parents() as if/else #5585 / this PR
11.5 builds builds –
12.5 error error builds
13.5 error error builds
14.4 error error builds
15.3 error error builds
16.2 error error builds

Change

Public API

No breaking changes. This PR only adds a test; the library change is in #5585 and does not change the public API either.


This PR was written by Claude Code.

🤖 Generated with Claude Code

#5737 changed sources under include/ without regenerating single_include,
so the single header on develop is out of date. This commit only runs
make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
…DIAGNOSTICS

With JSON_DIAGNOSTICS, GCC 13 to 15 report a false -Warray-bounds error
at -O3 when set_parents() is inlined right after a non-container value
(e.g., a string) was created: the std::map access in the object branch
is checked against the string allocation although m_type rules that
branch out. GCC 15 fixed the pattern from #4819 but not this one.

Suppress -Warray-bounds around set_parents() only, and add a regression
test that is compiled with -O3 -Werror=array-bounds on GCC.

Fixes #5742

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Comment thread include/nlohmann/json.hpp
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @nlohmann , my additional 5 cents: all these cases starting with case value_t::null can be removed, because they do not introduce any additional logic, just keeping default: here is already good enough.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! The explicit cases are intentional: we compile with -Wswitch-enum (GCC, and via -Weverything on Clang), which warns about every unlisted enumerator even when there is a default:. Listing all value_t cases is the library-wide convention (see cmake/clang_flags.cmake), so that adding a new value type makes the compiler point at every switch that needs a decision — for set_parents(), that would catch a new container type silently losing its parent pointers. It's also unrelated to this fix, so I'd like to keep it as is.

(This reply was written by Claude Code.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you say here that you don't want to change it from the switch, but I wonder if using something like this would help avoid the bug:

if (m_data.m_type == value_t::array)
{
  for (auto& element : *m_data.m_value.array)
  {
    element.m_parent = this;
  }
}
else if (m_data.m_type == value_t::object)
{
  for (auto& element : *m_data.m_value.object)
  {
    element.second.m_parent = this;
  }
 }

I really don't like having to disable warnings in code to work around compiler bugs, especially with the repro sample previously working in 15.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing back on the pragma. You were right that there is a better fix, though the if/else rewrite is not it: I tried it and the warning stays on GCC 12.5, 13, 14, 15.3 and 16.2.

The cause is outside set_parents(). external_constructor<value_t::string> stored m_type = value_t::string before calling create<string_t>(). GCC has to assume that operator new could change m_type, so after inlining it couldn't rule out the object branch, however the branch is written, and checked the std::map access against the string's 32-byte allocation. Setting the type after creating the value fixes it on all of these GCC versions. #5585 already makes exactly that change everywhere (for exception safety).

So I've stacked this PR on #5585 and removed the pragma. This PR now only adds the -O3 -Werror=array-bounds regression test. That also answers your question about limiting the pragma to certain versions: there's no pragma left, and the affected range turned out to be GCC 12 to at least 16.2 anyway. Details and the measurements are in the updated description.

(This reply was written by Claude Code.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent.

@nlohmann nlohmann added the 🚀 ready to merge Ready to merge - just waiting for CI to complete. label Oct 1, 2026
@nlohmann nlohmann added this to the Release 3.13.0 milestone Oct 1, 2026
Comment thread include/nlohmann/json.hpp Outdated
Comment thread include/nlohmann/json.hpp
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you say here that you don't want to change it from the switch, but I wonder if using something like this would help avoid the bug:

if (m_data.m_type == value_t::array)
{
  for (auto& element : *m_data.m_value.array)
  {
    element.m_parent = this;
  }
}
else if (m_data.m_type == value_t::object)
{
  for (auto& element : *m_data.m_value.object)
  {
    element.second.m_parent = this;
  }
 }

I really don't like having to disable warnings in code to work around compiler bugs, especially with the repro sample previously working in 15.

…-diagnostics-array-bounds

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
The pragma around set_parents() is no longer needed: the base branch
(#5585) creates a value before setting its type. Before, the type of a new
string was stored first, so GCC had to assume that operator new could
change it again, kept the object branch of the inlined set_parents() alive,
and checked it against the string's allocation. Rewriting set_parents()
itself (if/else instead of switch) does not help.

Verified with Docker GCC 12.5, 13, 14, 15.3 and 16.2: the regression test
builds and passes without the pragma, and fails to build on develop.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann changed the base branch from develop to claude/fix-allocation-exception-safety October 2, 2026 06:28
@nlohmann nlohmann mentioned this pull request Oct 2, 2026
@nlohmann
nlohmann merged commit dd2dd45 into claude/fix-allocation-exception-safety Oct 2, 2026
93 of 162 checks passed
@nlohmann
nlohmann deleted the issue-5742-diagnostics-array-bounds branch October 2, 2026 09:24
nlohmann added a commit that referenced this pull request Oct 6, 2026
Squashed onto develop from:
- Create a value before giving it its type
- Skip the failed-allocation test when exceptions are disabled
- Keep the created pointer rather than an uninitialized json_value
- Skip the vector<bool> failed-allocation check for VS 2015 with iterator debugging
- Test the remaining to_json overloads with a failing allocation
- Skip the to_json allocation-failure section on VS 2015 Debug
- Fix false GCC -Warray-bounds error with JSON_DIAGNOSTICS at -O3 (#5744)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
nlohmann added a commit that referenced this pull request Oct 6, 2026
* Create a value before giving it its type

Squashed onto develop from:
- Create a value before giving it its type
- Skip the failed-allocation test when exceptions are disabled
- Keep the created pointer rather than an uninitialized json_value
- Skip the vector<bool> failed-allocation check for VS 2015 with iterator debugging
- Test the remaining to_json overloads with a failing allocation
- Skip the to_json allocation-failure section on VS 2015 Debug
- Fix false GCC -Warray-bounds error with JSON_DIAGNOSTICS at -O3 (#5744)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Store the new value with a helper in all to_json constructors

Every external_constructor<>::construct now creates the new value first and
hands it to basic_json::replace_value(), which destroys the old value, stores
the new one before setting its type (as elsewhere in this PR), sets the
parents, and checks the invariant.

The std::vector<bool> and std::valarray overloads use array_t's range
constructor, which the other array overloads already rely on. Range views
keep their loop, as begin() and end() of a view may have different types.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake L 🚀 ready to merge Ready to merge - just waiting for CI to complete. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release build fails when JSON diagnostics enabled

3 participants