diff --git a/.github/workflows/check_amalgamation.yml b/.github/workflows/check_amalgamation.yml index d81a80b18..2f234d8be 100644 --- a/.github/workflows/check_amalgamation.yml +++ b/.github/workflows/check_amalgamation.yml @@ -2,16 +2,23 @@ name: "Check amalgamation" on: pull_request: + # also check develop itself: a PR can be merged before its own run of this + # workflow completes (e.g. while it is still queued), leaving single_include + # stale on develop without any failing check + push: + branches: + - develop concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read jobs: save: + if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - name: Harden Runner @@ -43,11 +50,11 @@ jobs: with: egress-policy: audit - - name: Checkout pull request + - name: Checkout pull request or pushed commit uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: main - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} persist-credentials: false - name: Checkout tools diff --git a/.github/workflows/comment_check_amalgamation.yml b/.github/workflows/comment_check_amalgamation.yml index 4667329d2..d5bd23fc8 100644 --- a/.github/workflows/comment_check_amalgamation.yml +++ b/.github/workflows/comment_check_amalgamation.yml @@ -10,7 +10,8 @@ permissions: jobs: comment: - if: ${{ github.event.workflow_run.conclusion == 'failure' }} + # push runs on develop have no PR to comment on (and no "pr" artifact) + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} runs-on: ubuntu-latest permissions: contents: read diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index df5c88523..746c2acbf 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -42,6 +42,10 @@ namespace detail * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an * allocated value (e.g., a string). See bug issue * https://github.com/nlohmann/json/issues/2865 for more information. + * + * A value that has to be allocated is created before the old one is destroyed: + * were it the other way around, an exception while creating the new value would + * leave j with the type of the new value, but the pointer to the destroyed old one. */ template struct external_constructor; @@ -65,18 +69,20 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { + const typename BasicJsonType::json_value value(s); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value = s; + j.m_data.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { + const typename BasicJsonType::json_value value(std::move(s)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value = std::move(s); + j.m_data.m_value = value; j.assert_invariant(); } @@ -85,9 +91,10 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + auto* created = j.template create(str); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value.string = j.template create(str); + j.m_data.m_value.string = created; j.assert_invariant(); } }; @@ -98,18 +105,20 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { + const typename BasicJsonType::json_value value(b); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::binary; - j.m_data.m_value = typename BasicJsonType::binary_t(b); + j.m_data.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { + const typename BasicJsonType::json_value value(std::move(b)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::binary; - j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b)); + j.m_data.m_value = value; j.assert_invariant(); } }; @@ -159,9 +168,10 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { + const typename BasicJsonType::json_value value(arr); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = arr; + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -169,9 +179,10 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { + const typename BasicJsonType::json_value value(std::move(arr)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = std::move(arr); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -187,9 +198,10 @@ struct external_constructor using std::begin; using std::end; + auto* created = j.template create(begin(arr), end(arr)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value.array = j.template create(begin(arr), end(arr)); + j.m_data.m_value.array = created; j.set_parents(); j.assert_invariant(); } @@ -197,15 +209,17 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& arr) { - j.m_data.m_value.destroy(j.m_data.m_type); - j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; - j.m_data.m_value.array->reserve(arr.size()); + typename BasicJsonType::array_t elements; + elements.reserve(arr.size()); for (const bool x : arr) { - j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); + elements.push_back(x); } + const typename BasicJsonType::json_value value(std::move(elements)); + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value; + j.set_parents(); j.assert_invariant(); } @@ -213,11 +227,12 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { + typename BasicJsonType::array_t elements(arr.size()); + std::copy(std::begin(arr), std::end(arr), elements.begin()); + const typename BasicJsonType::json_value value(std::move(elements)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; - j.m_data.m_value.array->resize(arr.size()); - std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin()); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -229,14 +244,16 @@ struct external_constructor enable_if_t>::value, int> = 0> static void construct(BasicJsonType& j, CompatibleArrayType && arr) { - j.m_data.m_value.destroy(j.m_data.m_type); - j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; + typename BasicJsonType::array_t elements; for (auto&& x : std::forward(arr)) { - j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); + elements.push_back(x); } + const typename BasicJsonType::json_value value(std::move(elements)); + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value; + j.set_parents(); j.assert_invariant(); } #endif @@ -248,9 +265,10 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { + const typename BasicJsonType::json_value value(obj); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value = obj; + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -258,9 +276,10 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { + const typename BasicJsonType::json_value value(std::move(obj)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value = std::move(obj); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -272,9 +291,10 @@ struct external_constructor using std::begin; using std::end; + auto* created = j.template create(begin(obj), end(obj)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value.object = j.template create(begin(obj), end(obj)); + j.m_data.m_value.object = created; j.set_parents(); j.assert_invariant(); } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index f975657db..71247c417 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1906,8 +1906,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_an_object) { // the initializer list is a list of pairs -> create an object - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; for (auto& element_ref : init) { @@ -1929,8 +1929,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } #endif // the initializer list describes an array -> create an array - m_data.m_type = value_t::array; m_data.m_value.array = create(init.begin(), init.end()); + m_data.m_type = value_t::array; } set_parents(); @@ -1943,8 +1943,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(const typename binary_t::container_type& init) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = init; + res.m_data.m_type = value_t::binary; return res; } @@ -1954,8 +1954,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = binary_t(init, subtype); + res.m_data.m_type = value_t::binary; return res; } @@ -1965,8 +1965,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(typename binary_t::container_type&& init) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = std::move(init); + res.m_data.m_type = value_t::binary; return res; } @@ -1976,8 +1976,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = binary_t(std::move(init), subtype); + res.m_data.m_type = value_t::binary; return res; } @@ -3023,8 +3023,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value.array = create(); + m_data.m_type = value_t::array; assert_invariant(); } @@ -3090,8 +3090,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value.object = create(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -3143,8 +3143,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value.object = create(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -4094,8 +4094,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -4127,8 +4127,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -4159,8 +4159,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; assert_invariant(); } @@ -4215,8 +4215,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -4240,8 +4240,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; assert_invariant(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 849645b41..6406c39c1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -6655,6 +6655,10 @@ namespace detail * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an * allocated value (e.g., a string). See bug issue * https://github.com/nlohmann/json/issues/2865 for more information. + * + * A value that has to be allocated is created before the old one is destroyed: + * were it the other way around, an exception while creating the new value would + * leave j with the type of the new value, but the pointer to the destroyed old one. */ template struct external_constructor; @@ -6678,18 +6682,20 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) { + const typename BasicJsonType::json_value value(s); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value = s; + j.m_data.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) { + const typename BasicJsonType::json_value value(std::move(s)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value = std::move(s); + j.m_data.m_value = value; j.assert_invariant(); } @@ -6698,9 +6704,10 @@ struct external_constructor int > = 0 > static void construct(BasicJsonType& j, const CompatibleStringType& str) { + auto* created = j.template create(str); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::string; - j.m_data.m_value.string = j.template create(str); + j.m_data.m_value.string = created; j.assert_invariant(); } }; @@ -6711,18 +6718,20 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b) { + const typename BasicJsonType::json_value value(b); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::binary; - j.m_data.m_value = typename BasicJsonType::binary_t(b); + j.m_data.m_value = value; j.assert_invariant(); } template static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b) { + const typename BasicJsonType::json_value value(std::move(b)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::binary; - j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b)); + j.m_data.m_value = value; j.assert_invariant(); } }; @@ -6772,9 +6781,10 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) { + const typename BasicJsonType::json_value value(arr); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = arr; + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6782,9 +6792,10 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { + const typename BasicJsonType::json_value value(std::move(arr)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = std::move(arr); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6800,9 +6811,10 @@ struct external_constructor using std::begin; using std::end; + auto* created = j.template create(begin(arr), end(arr)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value.array = j.template create(begin(arr), end(arr)); + j.m_data.m_value.array = created; j.set_parents(); j.assert_invariant(); } @@ -6810,15 +6822,17 @@ struct external_constructor template static void construct(BasicJsonType& j, const std::vector& arr) { - j.m_data.m_value.destroy(j.m_data.m_type); - j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; - j.m_data.m_value.array->reserve(arr.size()); + typename BasicJsonType::array_t elements; + elements.reserve(arr.size()); for (const bool x : arr) { - j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); + elements.push_back(x); } + const typename BasicJsonType::json_value value(std::move(elements)); + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value; + j.set_parents(); j.assert_invariant(); } @@ -6826,11 +6840,12 @@ struct external_constructor enable_if_t::value, int> = 0> static void construct(BasicJsonType& j, const std::valarray& arr) { + typename BasicJsonType::array_t elements(arr.size()); + std::copy(std::begin(arr), std::end(arr), elements.begin()); + const typename BasicJsonType::json_value value(std::move(elements)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; - j.m_data.m_value.array->resize(arr.size()); - std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin()); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6842,14 +6857,16 @@ struct external_constructor enable_if_t>::value, int> = 0> static void construct(BasicJsonType& j, CompatibleArrayType && arr) { - j.m_data.m_value.destroy(j.m_data.m_type); - j.m_data.m_type = value_t::array; - j.m_data.m_value = value_t::array; + typename BasicJsonType::array_t elements; for (auto&& x : std::forward(arr)) { - j.m_data.m_value.array->push_back(x); - j.set_parent(j.m_data.m_value.array->back()); + elements.push_back(x); } + const typename BasicJsonType::json_value value(std::move(elements)); + j.m_data.m_value.destroy(j.m_data.m_type); + j.m_data.m_type = value_t::array; + j.m_data.m_value = value; + j.set_parents(); j.assert_invariant(); } #endif @@ -6861,9 +6878,10 @@ struct external_constructor template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { + const typename BasicJsonType::json_value value(obj); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value = obj; + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6871,9 +6889,10 @@ struct external_constructor template static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { + const typename BasicJsonType::json_value value(std::move(obj)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value = std::move(obj); + j.m_data.m_value = value; j.set_parents(); j.assert_invariant(); } @@ -6885,9 +6904,10 @@ struct external_constructor using std::begin; using std::end; + auto* created = j.template create(begin(obj), end(obj)); j.m_data.m_value.destroy(j.m_data.m_type); j.m_data.m_type = value_t::object; - j.m_data.m_value.object = j.template create(begin(obj), end(obj)); + j.m_data.m_value.object = created; j.set_parents(); j.assert_invariant(); } @@ -28584,8 +28604,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec if (is_an_object) { // the initializer list is a list of pairs -> create an object - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; for (auto& element_ref : init) { @@ -28607,8 +28627,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec } #endif // the initializer list describes an array -> create an array - m_data.m_type = value_t::array; m_data.m_value.array = create(init.begin(), init.end()); + m_data.m_type = value_t::array; } set_parents(); @@ -28621,8 +28641,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(const typename binary_t::container_type& init) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = init; + res.m_data.m_type = value_t::binary; return res; } @@ -28632,8 +28652,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = binary_t(init, subtype); + res.m_data.m_type = value_t::binary; return res; } @@ -28643,8 +28663,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(typename binary_t::container_type&& init) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = std::move(init); + res.m_data.m_type = value_t::binary; return res; } @@ -28654,8 +28674,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype) { auto res = basic_json(); - res.m_data.m_type = value_t::binary; res.m_data.m_value = binary_t(std::move(init), subtype); + res.m_data.m_type = value_t::binary; return res; } @@ -29701,8 +29721,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value.array = create(); + m_data.m_type = value_t::array; assert_invariant(); } @@ -29768,8 +29788,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value.object = create(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -29821,8 +29841,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // implicitly convert a null value to an empty object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value.object = create(); + m_data.m_type = value_t::object; assert_invariant(); } @@ -30772,8 +30792,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -30805,8 +30825,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -30837,8 +30857,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; assert_invariant(); } @@ -30893,8 +30913,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an array if (is_null()) { - m_data.m_type = value_t::array; m_data.m_value = value_t::array; + m_data.m_type = value_t::array; assert_invariant(); } @@ -30918,8 +30938,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec // transform a null object into an object if (is_null()) { - m_data.m_type = value_t::object; m_data.m_value = value_t::object; + m_data.m_type = value_t::object; assert_invariant(); } diff --git a/tests/src/unit-allocator.cpp b/tests/src/unit-allocator.cpp index f99abd2fc..46e9d7a46 100644 --- a/tests/src/unit-allocator.cpp +++ b/tests/src/unit-allocator.cpp @@ -12,6 +12,11 @@ #include using nlohmann::json; +#include +#if JSON_HAS_RANGES + #include +#endif + namespace { // special test case to check if memory is leaked if constructor throws @@ -592,3 +597,173 @@ TEST_CASE("bad my_allocator::construct") j["test"].push_back("should not leak"); } } + +// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave +// next_construct_fails set for the next allocation outside a check +#if !defined(JSON_NOEXCEPTION) +TEST_CASE("a failed allocation leaves the value unchanged") +{ + // create JSON type using the throwing allocator + using my_json = nlohmann::basic_json; + + // Each of these creates a string, array, object, or binary value. The + // value must be created before the type is changed: otherwise, a failed + // creation left a value of the new type without anything behind it (an + // assertion in its destructor, a null pointer everywhere else) or, when + // an old value was destroyed first, with a pointer to that destroyed one. + + SECTION("creating a binary value") + { + const std::vector bytes = {1, 2, 3}; + my_json _; + + next_construct_fails = true; + CHECK_THROWS_AS(_ = my_json::binary(bytes), std::bad_alloc&); + next_construct_fails = true; + CHECK_THROWS_AS(_ = my_json::binary(bytes, 42), std::bad_alloc&); + next_construct_fails = true; + CHECK_THROWS_AS(_ = my_json::binary(std::vector(bytes)), std::bad_alloc&); + next_construct_fails = true; + CHECK_THROWS_AS(_ = my_json::binary(std::vector(bytes), 42), std::bad_alloc&); + next_construct_fails = false; + } + + SECTION("turning a null value into an array or object") + { + my_json j; + + next_construct_fails = true; + CHECK_THROWS_AS(j[0], std::bad_alloc&); + CHECK(j.is_null()); + + next_construct_fails = true; + CHECK_THROWS_AS(j["key"], std::bad_alloc&); + CHECK(j.is_null()); + +#ifdef JSON_HAS_CPP_17 + next_construct_fails = true; + CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&); + CHECK(j.is_null()); +#endif + + next_construct_fails = true; + CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&); + CHECK(j.is_null()); + + const my_json one = 1; + next_construct_fails = true; + CHECK_THROWS_AS(j.push_back(one), std::bad_alloc&); + CHECK(j.is_null()); + + next_construct_fails = true; + CHECK_THROWS_AS(j.push_back(my_json::object_t::value_type("key", 1)), std::bad_alloc&); + CHECK(j.is_null()); + + next_construct_fails = true; + CHECK_THROWS_AS(j.emplace_back(1), std::bad_alloc&); + CHECK(j.is_null()); + + next_construct_fails = true; + CHECK_THROWS_AS(j.emplace("key", 1), std::bad_alloc&); + CHECK(j.is_null()); + + const my_json object = {{"key", 1}}; + next_construct_fails = true; + CHECK_THROWS_AS(j.update(object), std::bad_alloc&); + CHECK(j.is_null()); + + next_construct_fails = false; + } + + // With iterator debugging, VS 2015's containers construct a proxy with the + // allocator in constructors that cannot report its failure, so a failing + // allocator crashes this section there (SIGSEGV with VS 2015 Debug x86). +#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0) + SECTION("converting into an existing value") + { + // to_json replaces the value it is given; the old one must survive a + // failed creation of the new one + my_json j = "old"; + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::string("new")), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::vector {1, 2}), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::vector {true, false}), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::map {{"a", 1}}), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, my_json::binary_t({1, 2})), std::bad_alloc&); + CHECK(j == "old"); + + // the overloads for lvalues of the value types, for the value types + // themselves, and for the remaining compatible types + const std::string string = "new"; + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, string), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, "new"), std::bad_alloc&); + CHECK(j == "old"); + + // to_json only moves a binary value that it converted from another + // container type, which my_json's std::vector is not + using binary_constructor = nlohmann::detail::external_constructor; + next_construct_fails = true; + CHECK_THROWS_AS(binary_constructor::construct(j, my_json::binary_t({1, 2})), std::bad_alloc&); + CHECK(j == "old"); + + my_json::array_t array = {1, 2}; + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, array), std::bad_alloc&); + CHECK(j == "old"); + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::move(array)), std::bad_alloc&); + CHECK(j == "old"); + + my_json::object_t object = {{"a", 1}}; + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, object), std::bad_alloc&); + CHECK(j == "old"); + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::move(object)), std::bad_alloc&); + CHECK(j == "old"); + + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray {1, 2}), std::bad_alloc&); + CHECK(j == "old"); + +#if JSON_HAS_RANGES && !defined(__MINGW32__) + const std::vector numbers = {1, 2}; + next_construct_fails = true; + CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) + { + return true; + })), std::bad_alloc&); + CHECK(j == "old"); +#endif + + next_construct_fails = false; + nlohmann::to_json(j, std::vector {1, 2}); + CHECK(j == my_json({1, 2})); + } +#endif +} +#endif