diff --git a/src/json.hpp b/src/json.hpp index c40e004a..47046c03 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -8992,7 +8992,9 @@ basic_json_parser_63: @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number */ reference get_unchecked(pointer ptr) const { @@ -9002,18 +9004,27 @@ basic_json_parser_63: { case value_t::object: { + // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case value_t::array: { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + if (reference_token == "-") { + // explicityly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { + // convert array index to number; unchecked access ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); } break; @@ -9037,6 +9048,7 @@ basic_json_parser_63: { case value_t::object: { + // note: at performs range check ptr = &ptr->at(reference_token); break; } @@ -9045,12 +9057,20 @@ basic_json_parser_63: { if (reference_token == "-") { - throw std::out_of_range("cannot resolve reference token '-'"); + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); } - else + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); + throw std::domain_error("array index must not begin with '0'"); } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); break; } @@ -9080,6 +9100,7 @@ basic_json_parser_63: { case value_t::object: { + // use unchecked object access ptr = &ptr->operator[](reference_token); break; } @@ -9088,10 +9109,19 @@ basic_json_parser_63: { if (reference_token == "-") { + // "-" cannot be used for const access throw std::out_of_range("array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range"); } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); break; } @@ -9114,6 +9144,7 @@ basic_json_parser_63: { case value_t::object: { + // note: at performs range check ptr = &ptr->at(reference_token); break; } @@ -9122,10 +9153,19 @@ basic_json_parser_63: { if (reference_token == "-") { + // "-" always fails the range check throw std::out_of_range("array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range"); } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check ptr = &ptr->at(static_cast(std::stoi(reference_token))); break; } diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index e2ea78b6..ac11f08a 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -8302,7 +8302,9 @@ class basic_json @complexity Linear in the length of the JSON pointer. - @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number */ reference get_unchecked(pointer ptr) const { @@ -8312,18 +8314,27 @@ class basic_json { case value_t::object: { + // use unchecked object access ptr = &ptr->operator[](reference_token); break; } case value_t::array: { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + if (reference_token == "-") { + // explicityly treat "-" as index beyond the end ptr = &ptr->operator[](ptr->m_value.array->size()); } else { + // convert array index to number; unchecked access ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); } break; @@ -8347,6 +8358,7 @@ class basic_json { case value_t::object: { + // note: at performs range check ptr = &ptr->at(reference_token); break; } @@ -8355,12 +8367,20 @@ class basic_json { if (reference_token == "-") { - throw std::out_of_range("cannot resolve reference token '-'"); + // "-" always fails the range check + throw std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range"); } - else + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') { - ptr = &ptr->at(static_cast(std::stoi(reference_token))); + throw std::domain_error("array index must not begin with '0'"); } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); break; } @@ -8390,6 +8410,7 @@ class basic_json { case value_t::object: { + // use unchecked object access ptr = &ptr->operator[](reference_token); break; } @@ -8398,10 +8419,19 @@ class basic_json { if (reference_token == "-") { + // "-" cannot be used for const access throw std::out_of_range("array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range"); } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // use unchecked array access ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); break; } @@ -8424,6 +8454,7 @@ class basic_json { case value_t::object: { + // note: at performs range check ptr = &ptr->at(reference_token); break; } @@ -8432,10 +8463,19 @@ class basic_json { if (reference_token == "-") { + // "-" always fails the range check throw std::out_of_range("array index '-' (" + std::to_string(ptr->m_value.array->size()) + ") is out of range"); } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + throw std::domain_error("array index must not begin with '0'"); + } + + // note: at performs range check ptr = &ptr->at(static_cast(std::stoi(reference_token))); break; } diff --git a/test/unit.cpp b/test/unit.cpp index a3b9035d..f4d162e1 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -12196,6 +12196,7 @@ TEST_CASE("JSON pointers") SECTION("nonconst access") { json j = {1, 2, 3}; + const json j_const = j; // check reading access CHECK(j["/0"_json_pointer] == j[0]); @@ -12214,9 +12215,32 @@ TEST_CASE("JSON pointers") j["/5"_json_pointer] = 55; CHECK(j == json({1, 13, 3, 33, nullptr, 55})); + // error with leading 0 + CHECK_THROWS_AS(j["/01"_json_pointer], std::domain_error); + CHECK_THROWS_WITH(j["/01"_json_pointer], "array index must not begin with '0'"); + CHECK_THROWS_AS(j_const["/01"_json_pointer], std::domain_error); + CHECK_THROWS_WITH(j_const["/01"_json_pointer], "array index must not begin with '0'"); + CHECK_THROWS_AS(j.at("/01"_json_pointer), std::domain_error); + CHECK_THROWS_WITH(j.at("/01"_json_pointer), "array index must not begin with '0'"); + CHECK_THROWS_AS(j_const.at("/01"_json_pointer), std::domain_error); + CHECK_THROWS_WITH(j_const.at("/01"_json_pointer), "array index must not begin with '0'"); + + // error with incorrect numbers + CHECK_THROWS_AS(j["/one"_json_pointer] = 1, std::invalid_argument); + // assign to "-" j["/-"_json_pointer] = 99; CHECK(j == json({1, 13, 3, 33, nullptr, 55, 99})); + + // error when using "-" in const object + CHECK_THROWS_AS(j_const["/-"_json_pointer], std::out_of_range); + CHECK_THROWS_WITH(j_const["/-"_json_pointer], "array index '-' (3) is out of range"); + + // error when using "-" with at + CHECK_THROWS_AS(j.at("/-"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j.at("/-"_json_pointer), "array index '-' (7) is out of range"); + CHECK_THROWS_AS(j_const.at("/-"_json_pointer), std::out_of_range); + CHECK_THROWS_WITH(j_const.at("/-"_json_pointer), "array index '-' (3) is out of range"); } SECTION("const access")