From 0835eb293ffa80bab9591fe55656d57d76805f40 Mon Sep 17 00:00:00 2001
From: Niels <niels.lohmann@gmail.com>
Date: Sun, 17 Apr 2016 18:54:54 +0200
Subject: [PATCH] improved RFC compliance and code coverage

---
 src/json.hpp      | 48 +++++++++++++++++++++++++++++++++++++++++++----
 src/json.hpp.re2c | 48 +++++++++++++++++++++++++++++++++++++++++++----
 test/unit.cpp     | 24 ++++++++++++++++++++++++
 3 files changed, 112 insertions(+), 8 deletions(-)

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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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")