Repository navigation
Conversation
#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>
| case value_t::number_unsigned: | ||
| case value_t::number_float: | ||
| case value_t::binary: | ||
| case value_t::discarded: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.)
| case value_t::number_unsigned: | ||
| case value_t::number_float: | ||
| case value_t::binary: | ||
| case value_t::discarded: |
There was a problem hiding this comment.
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>
dd2dd45
into
claude/fix-allocation-exception-safety
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>
* 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>
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-boundserror at-O3(so-DCMAKE_BUILD_TYPE=Release+-Werrorfails). The warning is located in libstdc++'sstl_tree.h: GCC checks thestd::mapaccess in thevalue_t::objectbranch of the inlinedset_parents()against the 32-byte allocation of a string.Cause
external_constructor<value_t::string>set the type before allocating the string:GCC has to assume that
operator newcould change any memory, includingm_type. So whenset_parents()readsm_typeagain 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: anif/elseinstead of theswitch, or an early return viais_structured(), still fails on every affected GCC.Measured with the #5742 repro at
-O3 -Werror=array-bounds(Docker images):set_parents()asif/elseChange
tests/src/unit-diagnostics-optimized.cppwith both known patterns (gcc 14.2 bug: array subscript out of bounds withJSON_DIAGNOSTICS#4819 and Release build fails when JSON diagnostics enabled #5742). On GCC,tests/CMakeLists.txtcompiles it with-O3 -Werror=array-bounds, so the test fails to build if the warning returns. Verified with GCC 12 to 16: the test fails to build against develop (GCC 13 to 16) and builds and passes on this branch.-Warray-boundsaroundset_parents()with a pragma. That is removed again; the history keeps it, so no force push was needed.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