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 */
|