Skip to content

Commit

Permalink
Merge pull request #2203 from t-b/use-unsigned-indizies-for-array-ind…
Browse files Browse the repository at this point in the history
…ex-in-json-pointer

Use unsigned indizies for array index in json pointer
  • Loading branch information
nlohmann committed Jun 23, 2020
2 parents 29ad217 + ecbb275 commit 4bfe4ad
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 34 deletions.
35 changes: 23 additions & 12 deletions include/nlohmann/detail/json_pointer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm> // all_of
#include <cassert> // assert
#include <cctype> // isdigit
#include <limits> // max
#include <numeric> // accumulate
#include <string> // string
#include <utility> // move
Expand Down Expand Up @@ -325,10 +326,15 @@ class json_pointer
@return integer representation of @a s
@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index begins not with a digit
@throw out_of_range.404 if string @a s could not be converted to an integer
@throw out_of_range.410 if an array index exceeds size_type
*/
static int array_index(const std::string& s)
static typename BasicJsonType::size_type array_index(const std::string& s)
{
using size_type = typename BasicJsonType::size_type;

// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 and s[0] == '0'))
{
Expand All @@ -344,10 +350,10 @@ class json_pointer
}

std::size_t processed_chars = 0;
int res = 0;
unsigned long long res = 0;
JSON_TRY
{
res = std::stoi(s, &processed_chars);
res = std::stoull(s, &processed_chars);
}
JSON_CATCH(std::out_of_range&)
{
Expand All @@ -360,7 +366,14 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
}

return res;
// only triggered on special platforms (like 32bit), see also
// https://github.com/nlohmann/json/pull/2203
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)()))
{
JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE
}

return static_cast<size_type>(res);
}

json_pointer top() const
Expand Down Expand Up @@ -419,7 +432,7 @@ class json_pointer
case detail::value_t::array:
{
// create an entry in the array
result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
result = &result->operator[](array_index(reference_token));
break;
}

Expand Down Expand Up @@ -497,8 +510,7 @@ class json_pointer
else
{
// convert array index to number; unchecked access
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->operator[](array_index(reference_token));
}
break;
}
Expand Down Expand Up @@ -542,7 +554,7 @@ class json_pointer
}

// note: at performs range check
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->at(array_index(reference_token));
break;
}

Expand Down Expand Up @@ -592,8 +604,7 @@ class json_pointer
}

// use unchecked array access
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->operator[](array_index(reference_token));
break;
}

Expand Down Expand Up @@ -636,7 +647,7 @@ class json_pointer
}

// note: at performs range check
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->at(array_index(reference_token));
break;
}

Expand Down Expand Up @@ -700,7 +711,7 @@ class json_pointer
}
}

const auto idx = static_cast<size_type>(array_index(reference_token));
const auto idx = array_index(reference_token);
if (idx >= ptr->size())
{
// index out of range
Expand Down
4 changes: 2 additions & 2 deletions include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8207,7 +8207,7 @@ class basic_json
else
{
const auto idx = json_pointer::array_index(last_path);
if (JSON_HEDLEY_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
if (JSON_HEDLEY_UNLIKELY(idx > parent.size()))
{
// avoid undefined behavior
JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
Expand Down Expand Up @@ -8250,7 +8250,7 @@ class basic_json
else if (parent.is_array())
{
// note erase performs range check
parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
parent.erase(json_pointer::array_index(last_path));
}
};

Expand Down
39 changes: 25 additions & 14 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11036,6 +11036,7 @@ class json_reverse_iterator : public std::reverse_iterator<Base>
#include <algorithm> // all_of
#include <cassert> // assert
#include <cctype> // isdigit
#include <limits> // max
#include <numeric> // accumulate
#include <string> // string
#include <utility> // move
Expand Down Expand Up @@ -11361,10 +11362,15 @@ class json_pointer

@return integer representation of @a s

@throw parse_error.106 if an array index begins with '0'
@throw parse_error.109 if an array index begins not with a digit
@throw out_of_range.404 if string @a s could not be converted to an integer
@throw out_of_range.410 if an array index exceeds size_type
*/
static int array_index(const std::string& s)
static typename BasicJsonType::size_type array_index(const std::string& s)
{
using size_type = typename BasicJsonType::size_type;

// error condition (cf. RFC 6901, Sect. 4)
if (JSON_HEDLEY_UNLIKELY(s.size() > 1 and s[0] == '0'))
{
Expand All @@ -11380,10 +11386,10 @@ class json_pointer
}

std::size_t processed_chars = 0;
int res = 0;
unsigned long long res = 0;
JSON_TRY
{
res = std::stoi(s, &processed_chars);
res = std::stoull(s, &processed_chars);
}
JSON_CATCH(std::out_of_range&)
{
Expand All @@ -11396,7 +11402,14 @@ class json_pointer
JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + s + "'"));
}

return res;
// only triggered on special platforms (like 32bit), see also
// https://github.com/nlohmann/json/pull/2203
if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)()))
{
JSON_THROW(detail::out_of_range::create(410, "array index " + s + " exceeds size_type")); // LCOV_EXCL_LINE
}

return static_cast<size_type>(res);
}

json_pointer top() const
Expand Down Expand Up @@ -11455,7 +11468,7 @@ class json_pointer
case detail::value_t::array:
{
// create an entry in the array
result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
result = &result->operator[](array_index(reference_token));
break;
}

Expand Down Expand Up @@ -11533,8 +11546,7 @@ class json_pointer
else
{
// convert array index to number; unchecked access
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->operator[](array_index(reference_token));
}
break;
}
Expand Down Expand Up @@ -11578,7 +11590,7 @@ class json_pointer
}

// note: at performs range check
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->at(array_index(reference_token));
break;
}

Expand Down Expand Up @@ -11628,8 +11640,7 @@ class json_pointer
}

// use unchecked array access
ptr = &ptr->operator[](
static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->operator[](array_index(reference_token));
break;
}

Expand Down Expand Up @@ -11672,7 +11683,7 @@ class json_pointer
}

// note: at performs range check
ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
ptr = &ptr->at(array_index(reference_token));
break;
}

Expand Down Expand Up @@ -11736,7 +11747,7 @@ class json_pointer
}
}

const auto idx = static_cast<size_type>(array_index(reference_token));
const auto idx = array_index(reference_token);
if (idx >= ptr->size())
{
// index out of range
Expand Down Expand Up @@ -24001,7 +24012,7 @@ class basic_json
else
{
const auto idx = json_pointer::array_index(last_path);
if (JSON_HEDLEY_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
if (JSON_HEDLEY_UNLIKELY(idx > parent.size()))
{
// avoid undefined behavior
JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range"));
Expand Down Expand Up @@ -24044,7 +24055,7 @@ class basic_json
else if (parent.is_array())
{
// note erase performs range check
parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
parent.erase(json_pointer::array_index(last_path));
}
};

Expand Down
29 changes: 23 additions & 6 deletions test/src/unit-json_pointer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,29 @@ TEST_CASE("JSON pointers")
CHECK_THROWS_WITH(j_const["/1+1"_json_pointer] == 1,
"[json.exception.out_of_range.404] unresolved reference token '1+1'");

CHECK_THROWS_AS(j["/111111111111111111111111"_json_pointer] = 1, json::out_of_range&);
CHECK_THROWS_WITH(j["/111111111111111111111111"_json_pointer] = 1,
"[json.exception.out_of_range.404] unresolved reference token '111111111111111111111111'");
CHECK_THROWS_AS(j_const["/111111111111111111111111"_json_pointer] == 1, json::out_of_range&);
CHECK_THROWS_WITH(j_const["/111111111111111111111111"_json_pointer] == 1,
"[json.exception.out_of_range.404] unresolved reference token '111111111111111111111111'");
{
auto too_large_index = std::to_string((std::numeric_limits<unsigned long long>::max)()) + "1";
json::json_pointer jp(std::string("/") + too_large_index);
std::string throw_msg = std::string("[json.exception.out_of_range.404] unresolved reference token '") + too_large_index + "'";

CHECK_THROWS_AS(j[jp] = 1, json::out_of_range&);
CHECK_THROWS_WITH(j[jp] = 1, throw_msg.c_str());
CHECK_THROWS_AS(j_const[jp] == 1, json::out_of_range&);
CHECK_THROWS_WITH(j_const[jp] == 1, throw_msg.c_str());
}

if (sizeof(typename json::size_type) < sizeof(unsigned long long))
{
auto size_type_max_uul = static_cast<unsigned long long>((std::numeric_limits<json::size_type>::max)());
auto too_large_index = std::to_string(size_type_max_uul);
json::json_pointer jp(std::string("/") + too_large_index);
std::string throw_msg = std::string("[json.exception.out_of_range.410] array index ") + too_large_index + " exceeds size_type";

CHECK_THROWS_AS(j[jp] = 1, json::out_of_range&);
CHECK_THROWS_WITH(j[jp] = 1, throw_msg.c_str());
CHECK_THROWS_AS(j_const[jp] == 1, json::out_of_range&);
CHECK_THROWS_WITH(j_const[jp] == 1, throw_msg.c_str());
}

CHECK_THROWS_AS(j.at("/one"_json_pointer) = 1, json::parse_error&);
CHECK_THROWS_WITH(j.at("/one"_json_pointer) = 1,
Expand Down

0 comments on commit 4bfe4ad

Please sign in to comment.