LCOV - code coverage report
Current view: top level - gcc - json-pointer-parsing.cc (source / functions) Coverage Total Hit
Test: gcc.info Lines: 99.0 % 197 195
Test Date: 2026-05-11 19:44:49 Functions: 100.0 % 12 12
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /* JSON Pointer parsing (RFC 6901).
       2              :    Copyright (C) 2026 Free Software Foundation, Inc.
       3              :    Contributed by David Malcolm <dmalcolm@redhat.com>.
       4              : 
       5              : This file is part of GCC.
       6              : 
       7              : GCC is free software; you can redistribute it and/or modify it under
       8              : the terms of the GNU General Public License as published by the Free
       9              : Software Foundation; either version 3, or (at your option) any later
      10              : version.
      11              : 
      12              : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
      13              : WARRANTY; without even the implied warranty of MERCHANTABILITY or
      14              : FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      15              : for more details.
      16              : 
      17              : You should have received a copy of the GNU General Public License
      18              : along with GCC; see the file COPYING3.  If not see
      19              : <http://www.gnu.org/licenses/>.  */
      20              : 
      21              : #define INCLUDE_MAP
      22              : #define INCLUDE_STRING
      23              : #include "config.h"
      24              : #include "system.h"
      25              : #include "coretypes.h"
      26              : #include "json-pointer-parsing.h"
      27              : #include "pretty-print.h"
      28              : #include "pretty-print-markup-json.h"
      29              : #include "selftest.h"
      30              : 
      31              : /* Implementation of json::pointer parsing.  */
      32              : 
      33              : namespace {
      34              : 
      35              : class json_pointer_parser
      36              : {
      37              : public:
      38              :   json::pointer::parser_result_t
      39              :   parse_utf8_string (const char *utf8_json_pointer,
      40              :                      const json::value *root_val);
      41              : 
      42              : private:
      43              :   std::unique_ptr<json::pointer::error>
      44              :   make_error (const char *fmt, ...);
      45              : 
      46              :   json::result<size_t, std::unique_ptr<json::pointer::error>>
      47              :   parse_array_index (const std::string &reftoken);
      48              : };
      49              : 
      50              : } // anonymous namespace
      51              : 
      52              : /* Parse JSON pointer.  */
      53              : 
      54              : json::pointer::parser_result_t
      55          132 : json_pointer_parser::parse_utf8_string (const char *utf8_json_pointer,
      56              :                                         const json::value *root_val)
      57              : {
      58          132 :   const char *ch_iter = utf8_json_pointer;
      59          132 :   const json::value *cur_val = root_val;
      60              : 
      61          216 :   while (*ch_iter)
      62              :     {
      63              :       // Try to consume a reference-token
      64          136 :       if (*ch_iter != '/')
      65            4 :         return make_error ("malformed JSON Pointer: expected %qs; got %qc",
      66            4 :                            "/", *ch_iter);
      67          132 :       ch_iter++;
      68          132 :       std::string reftoken;
      69          428 :       while (char ch = *ch_iter)
      70              :         {
      71              :           /* End at end of string, or unescaped '/'.  */
      72          316 :           if (ch == '/' || ch == '\0')
      73              :             break;
      74          304 :           ch_iter++;
      75          304 :           if (ch == '~')
      76              :             {
      77           32 :               switch (*ch_iter)
      78              :                 {
      79           16 :                 case '0':
      80           16 :                   reftoken += '~';
      81           16 :                   ch_iter++;
      82           16 :                   break;
      83            8 :                 case '1':
      84            8 :                   reftoken += '/';
      85            8 :                   ch_iter++;
      86            8 :                   break;
      87            8 :                 default:
      88            8 :                   if (*ch_iter)
      89            4 :                     return make_error(("malformed JSON Pointer:"
      90              :                                        " expected %qs or %qs after %qs;"
      91              :                                        " got %qc"),
      92            4 :                                       "0", "1", "~", *ch_iter);
      93              :                   else
      94            4 :                     return make_error (("malformed JSON Pointer:"
      95              :                                         " expected %qs or %qs after %qs"),
      96            4 :                                        "0", "1", "~");
      97              :                 }
      98              :             }
      99              :           else
     100          272 :             reftoken += ch;
     101              :         }
     102          124 :       switch (cur_val->get_kind ())
     103              :         {
     104            0 :         default:
     105            0 :           gcc_unreachable ();
     106              : 
     107           84 :         case json::JSON_OBJECT:
     108           84 :           {
     109           84 :             const json::object *cur_obj
     110              :               = static_cast<const json::object *> (cur_val);
     111           84 :             if (const json::value *child = cur_obj->get (reftoken.c_str ()))
     112              :               cur_val = child;
     113              :             else
     114              :               {
     115           12 :                 pp_markup::quoted_json_pointer obj_pointer (*cur_val);
     116           12 :                 return make_error ("unknown member %qs within object %e",
     117           12 :                                    reftoken.c_str (), &obj_pointer);
     118           12 :               }
     119              :           }
     120              :           break;
     121           24 :         case json::JSON_ARRAY:
     122           24 :           {
     123           24 :             auto array_idx_res = parse_array_index (reftoken);
     124           24 :             if (array_idx_res.m_err)
     125            8 :               return std::move (array_idx_res.m_err);
     126              : 
     127           16 :             const json::array *cur_arr
     128              :               = static_cast<const json::array *> (cur_val);
     129           16 :             if (array_idx_res.m_val < cur_arr->size ())
     130           12 :               cur_val = (*cur_arr)[array_idx_res.m_val];
     131              :             else
     132              :               {
     133            4 :                 pp_markup::quoted_json_pointer array_pointer (*cur_val);
     134            4 :                 return make_error
     135            4 :                   ("array index %li out of range for array %e",
     136              :                    array_idx_res.m_val,
     137            4 :                    &array_pointer);
     138            4 :               }
     139           12 :           }
     140           12 :           break;
     141              : 
     142            8 :         case json::JSON_INTEGER:
     143            8 :         case json::JSON_FLOAT:
     144            8 :           {
     145            8 :             pp_markup::quoted_json_pointer cur_val_ptr (*cur_val);
     146            8 :             return make_error
     147            8 :               (("expected object or array for reference token %qs;"
     148              :                 " %e is a number"),
     149              :                reftoken.c_str (),
     150            8 :                &cur_val_ptr);
     151            8 :           }
     152            4 :         case json::JSON_STRING:
     153            4 :           {
     154            4 :             pp_markup::quoted_json_pointer cur_val_ptr (*cur_val);
     155            4 :             return make_error
     156            4 :               (("expected object or array for reference token %qs;"
     157              :                 " %e is a string"),
     158              :                reftoken.c_str (),
     159            4 :                &cur_val_ptr);
     160            4 :           }
     161            4 :         case json::JSON_TRUE:
     162            4 :         case json::JSON_FALSE:
     163            4 :         case json::JSON_NULL:
     164            4 :           {
     165            4 :             pp_markup::quoted_json_pointer cur_val_ptr (*cur_val);
     166            4 :             return make_error
     167            4 :               (("expected object or array for reference token %qs;"
     168              :                 " %e is a JSON literal"),
     169              :                reftoken.c_str (),
     170            4 :                &cur_val_ptr);
     171            4 :           }
     172              :         }
     173          132 :     }
     174              : 
     175           80 :   return cur_val;
     176              : }
     177              : 
     178              : std::unique_ptr<json::pointer::error>
     179           52 : json_pointer_parser::make_error (const char *fmt, ...)
     180              : {
     181           52 :   va_list ap;
     182           52 :   va_start (ap, fmt);
     183           52 :   auto err = std::make_unique<json::pointer::error>
     184           52 :     (pretty_print_token_buffer (fmt, &ap));
     185           52 :   va_end (ap);
     186           52 :   return err;
     187              : }
     188              : 
     189              : /* Parse array-index: '0', or decimal digits without a leading '0'.  */
     190              : 
     191              : json::result<size_t, std::unique_ptr<json::pointer::error>>
     192           24 : json_pointer_parser::parse_array_index (const std::string &reftoken)
     193              : {
     194           24 :   if (reftoken == "0")
     195           12 :     return 0;
     196              : 
     197              :   /* Decimal digits without a leading '0'.  */
     198           12 :   if (reftoken[0] < '1' || reftoken[0] > '9')
     199            4 :     return make_error ("malformed JSON Pointer: bad array index: %qs",
     200            4 :                        reftoken.c_str ());
     201              : 
     202            8 :   size_t result = 0;
     203           16 :   for (auto digit : reftoken)
     204              :     {
     205           12 :       result *= 10;
     206           12 :       if (digit < '0' || digit > '9')
     207            4 :         return make_error ("malformed JSON Pointer: bad array index: %qs",
     208            4 :                            reftoken.c_str ());
     209            8 :       result += digit - '0';
     210              :     }
     211            4 :   return result;
     212              : }
     213              : 
     214              : json::pointer::parser_result_t
     215          132 : json::pointer::parse_utf8_string (const char *utf8_json_pointer,
     216              :                                   const json::value *root_val)
     217              : {
     218          132 :   json_pointer_parser p;
     219          132 :   return p.parse_utf8_string (utf8_json_pointer, root_val);
     220              : }
     221              : 
     222              : 
     223              : #if CHECKING_P
     224              : 
     225              : namespace selftest {
     226              : 
     227              : /* Implementation detail of ASSERT_PARSE_JSON_POINTER_EQ.  */
     228              : 
     229              : static void
     230           36 : assert_parse_json_pointer_eq (const location &loc,
     231              :                               const char *utf8_json_pointer,
     232              :                               const json::value *root_val,
     233              :                               const json::value *expected_jv)
     234              : {
     235           36 :   auto res = json::pointer::parse_utf8_string (utf8_json_pointer, root_val);
     236           36 :   ASSERT_EQ_AT (loc, res.m_err, nullptr);
     237           36 :   ASSERT_EQ_AT (loc, res.m_val, expected_jv);
     238           36 : }
     239              : 
     240              : /* Assert that JSON_POINTER, a const char *, is a valid JSON pointer into
     241              :    ROOT_VAL, and equals EXPECTED_JV.  */
     242              : #define ASSERT_PARSE_JSON_POINTER_EQ(JSON_POINTER, ROOT_VAL, EXPECTED_JV) \
     243              :   assert_parse_json_pointer_eq ((SELFTEST_LOCATION), (JSON_POINTER), \
     244              :                                 (ROOT_VAL), (EXPECTED_JV))
     245              : 
     246              : /* Implementation detail of ASSERT_DUMP_FROM_JSON_POINTER_STREQ.  */
     247              : 
     248              : static void
     249           44 : assert_dump_from_json_pointer_streq (const location &loc,
     250              :                                      const json::value *root_val,
     251              :                                      const char *utf8_json_pointer,
     252              :                                      const char *expected_dump)
     253              : {
     254           44 :   auto res = json::pointer::parse_utf8_string (utf8_json_pointer, root_val);
     255           44 :   ASSERT_EQ_AT (loc, res.m_err, nullptr);
     256              : 
     257           44 :   pretty_printer pp;
     258           44 :   res.m_val->print (&pp, false);
     259           44 :   ASSERT_STREQ_AT (loc, pp_formatted_text (&pp), expected_dump);
     260           44 : }
     261              : 
     262              : /* Assert that JSON_POINTER (a const char *) successfully looks up a value
     263              :    relative to ROOT_VAL, and that the resulting value dumps as
     264              :    EXPECTED_DUMP.  */
     265              : #define ASSERT_DUMP_FROM_JSON_POINTER_STREQ(ROOT_VAL, JSON_POINTER, EXPECTED_DUMP) \
     266              :   assert_dump_from_json_pointer_streq ((SELFTEST_LOCATION), (ROOT_VAL), \
     267              :                                        (JSON_POINTER), (EXPECTED_DUMP))
     268              : 
     269              : /* Implementation detail of ASSERT_JSON_POINTER_ERR.  */
     270              : 
     271              : static void
     272           52 : assert_json_pointer_err (const location &loc,
     273              :                          const char *utf8_json_pointer,
     274              :                          const json::value *root_val,
     275              :                          const char *expected_err)
     276              : {
     277           52 :   auto res = json::pointer::parse_utf8_string (utf8_json_pointer, root_val);
     278           52 :   ASSERT_EQ_AT (loc, res.m_val, nullptr);
     279           52 :   ASSERT_NE_AT (loc, res.m_err, nullptr);
     280           52 :   std::string err_str = res.m_err->m_tokens.to_string ();
     281           52 :   ASSERT_STREQ_AT (loc, err_str.c_str (), expected_err);
     282           52 : }
     283              : 
     284              : /* Assert that JSON_POINTER (a const char *) fails to look up a value
     285              :    relative to ROOT_VAL, and that the resulting error expressed as a string
     286              :    is EXPECTED_ERR.  */
     287              : #define ASSERT_JSON_POINTER_ERR(JSON_POINTER, ROOT_VAL, EXPECTED_ERR) \
     288              :   assert_json_pointer_err ((SELFTEST_LOCATION), (JSON_POINTER),       \
     289              :                            (ROOT_VAL), (EXPECTED_ERR))
     290              : 
     291              : /* Selftests.  */
     292              : 
     293              : static void
     294            4 : test_simple ()
     295              : {
     296            4 :   json::object obj;
     297            4 :   auto js_bar = obj.set_string ("foo", "bar");
     298            4 :   auto baz = std::make_unique<json::array> ();
     299            4 :   json::array *js_baz = baz.get ();
     300            4 :   auto js_str0 = baz->append_string ("x");
     301            4 :   auto js_str1 = baz->append_string ("y");
     302            4 :   obj.set ("baz", std::move (baz));
     303              : 
     304            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("", &obj, &obj);
     305            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/foo", &obj, js_bar);
     306            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/baz", &obj, js_baz);
     307            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/baz/0", &obj, js_str0);
     308            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/baz/1", &obj, js_str1);
     309            4 : }
     310              : 
     311              : /* Verify that JSON Pointers are correctly escaped.  */
     312              : 
     313              : static void
     314            4 : test_escaping_1 ()
     315              : {
     316            4 :   json::object obj;
     317            4 :   auto js_a_slash_b = obj.set_integer ("a/b", 1);
     318            4 :   auto js_m_tilde_n = obj.set_integer ("m~n", 8);
     319            4 :   auto js_tilde_1 = obj.set_integer ("~1", 9);
     320              : 
     321            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/a~1b", &obj, js_a_slash_b);
     322            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/m~0n", &obj, js_m_tilde_n);
     323            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("/~01", &obj, js_tilde_1);
     324            4 : }
     325              : 
     326              : static void
     327            4 : test_escaping_2 ()
     328              : {
     329              :   /* The example from RFC 6901 section 5.  */
     330            4 :   char *path = locate_file ("json-pointer.json");
     331            4 :   char *js_doc = selftest::read_file (SELFTEST_LOCATION, path);
     332            4 :   free (path);
     333            4 :   auto res = json::parse_utf8_string (js_doc, true, nullptr);
     334            4 :   ASSERT_EQ (res.m_err, nullptr);
     335            4 :   free (js_doc);
     336              : 
     337            4 :   auto root = res.m_val.get ();
     338            4 :   ASSERT_PARSE_JSON_POINTER_EQ ("", root, root);
     339            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/foo",   "[\"bar\", \"baz\"]");
     340            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/foo/0", "\"bar\"");
     341            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/",      "0");
     342            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/a~1b",  "1");
     343            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/c%d" ,  "2");
     344            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/e^f" ,  "3");
     345            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/g|h" ,  "4");
     346            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/i\\j",  "5");
     347            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/k\"l",  "6");
     348            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/ "   ,  "7");
     349            4 :   ASSERT_DUMP_FROM_JSON_POINTER_STREQ (root, "/m~0n",  "8");
     350            4 : }
     351              : 
     352              : static void
     353            4 : test_errors ()
     354              : {
     355            4 :   json::object obj;
     356            4 :   json::array arr;
     357            4 :   ASSERT_JSON_POINTER_ERR ("foo", &obj,
     358              :                            "malformed JSON Pointer: expected '/'; got 'f'");
     359            4 :   ASSERT_JSON_POINTER_ERR ("/~", &obj,
     360              :                            "malformed JSON Pointer: expected '0' or '1' after '~'");
     361            4 :   ASSERT_JSON_POINTER_ERR ("/~~", &obj,
     362              :                            "malformed JSON Pointer: expected '0' or '1' after '~';"
     363              :                            " got '~'");
     364            4 :   ASSERT_JSON_POINTER_ERR ("/foo", &obj,
     365              :                            "unknown member 'foo' within object ''");
     366            4 :   ASSERT_JSON_POINTER_ERR ("/0", &obj,
     367              :                            "unknown member '0' within object ''");
     368            4 :   ASSERT_JSON_POINTER_ERR ("/0", &arr,
     369              :                            "array index 0 out of range for array ''");
     370            4 :   ASSERT_JSON_POINTER_ERR ("/-1", &arr,
     371              :                            "malformed JSON Pointer: bad array index: '-1'");
     372            4 :   ASSERT_JSON_POINTER_ERR ("/8a", &arr,
     373              :                            "malformed JSON Pointer: bad array index: '8a'");
     374              : 
     375            4 :   {
     376            4 :     json::integer_number js_int (42);
     377            4 :     json::float_number js_float (42);
     378            4 :     json::string js_str ("foo");
     379            4 :     json::literal js_true (json::JSON_TRUE);
     380            4 :     ASSERT_JSON_POINTER_ERR
     381              :       ("/foo", &js_int,
     382              :        "expected object or array for reference token 'foo';"
     383              :        " '' is a number");
     384            4 :     ASSERT_JSON_POINTER_ERR
     385              :       ("/foo", &js_float,
     386              :        "expected object or array for reference token 'foo';"
     387              :        " '' is a number");
     388            4 :     ASSERT_JSON_POINTER_ERR
     389              :       ("/foo", &js_str,
     390              :        "expected object or array for reference token 'foo';"
     391              :        " '' is a string");
     392            4 :     ASSERT_JSON_POINTER_ERR
     393              :       ("/foo", &js_true,
     394              :        "expected object or array for reference token 'foo';"
     395              :        " '' is a JSON literal");
     396            4 :   }
     397              : 
     398              :   /* RFC 6901 section 4 "Evaluation" has:
     399              :      "the string '~01' correctly becomes '~1' after transformation".  */
     400            4 :   ASSERT_JSON_POINTER_ERR ("/~01", &obj,
     401              :                            "unknown member '~1' within object ''");
     402            4 : }
     403              : 
     404              : /* Run all of the selftests within this file.  */
     405              : 
     406              : void
     407            4 : json_pointer_parsing_cc_tests ()
     408              : {
     409            4 :   test_simple ();
     410            4 :   test_escaping_1 ();
     411            4 :   test_escaping_2 ();
     412            4 :   test_errors ();
     413            4 : }
     414              : 
     415              : } // namespace selftest
     416              : 
     417              : #endif /* #if CHECKING_P */
        

Generated by: LCOV version 2.4-beta

LCOV profile is generated on x86_64 machine using following configure options: configure --disable-bootstrap --enable-coverage=opt --enable-languages=c,c++,fortran,go,jit,lto,rust,m2 --enable-host-shared. GCC test suite is run with the built compiler.