]>
Commit | Line | Data |
---|---|---|
4a4412b9 | 1 | /* JSON trees |
a945c346 | 2 | Copyright (C) 2017-2024 Free Software Foundation, Inc. |
4a4412b9 DM |
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 | #include "config.h" | |
22 | #include "system.h" | |
23 | #include "coretypes.h" | |
24 | #include "json.h" | |
25 | #include "pretty-print.h" | |
26 | #include "math.h" | |
27 | #include "selftest.h" | |
28 | ||
29 | using namespace json; | |
30 | ||
30d9a3a6 DM |
31 | /* Print a JSON string to PP, escaping '"', control characters, |
32 | and embedded null bytes. | |
33 | The string is required to be UTF-8 encoded. */ | |
34 | ||
35 | static void | |
36 | print_escaped_json_string (pretty_printer *pp, | |
37 | const char *utf8_str, | |
38 | size_t len) | |
39 | { | |
40 | pp_character (pp, '"'); | |
41 | for (size_t i = 0; i != len; ++i) | |
42 | { | |
43 | char ch = utf8_str[i]; | |
44 | switch (ch) | |
45 | { | |
46 | case '"': | |
47 | pp_string (pp, "\\\""); | |
48 | break; | |
49 | case '\\': | |
50 | pp_string (pp, "\\\\"); | |
51 | break; | |
52 | case '\b': | |
53 | pp_string (pp, "\\b"); | |
54 | break; | |
55 | case '\f': | |
56 | pp_string (pp, "\\f"); | |
57 | break; | |
58 | case '\n': | |
59 | pp_string (pp, "\\n"); | |
60 | break; | |
61 | case '\r': | |
62 | pp_string (pp, "\\r"); | |
63 | break; | |
64 | case '\t': | |
65 | pp_string (pp, "\\t"); | |
66 | break; | |
67 | case '\0': | |
68 | pp_string (pp, "\\0"); | |
69 | break; | |
70 | default: | |
71 | pp_character (pp, ch); | |
72 | } | |
73 | } | |
74 | pp_character (pp, '"'); | |
75 | } | |
76 | ||
4a4412b9 DM |
77 | /* class json::value. */ |
78 | ||
79 | /* Dump this json::value tree to OUTF. | |
7f1e15f7 | 80 | |
7f1e15f7 DM |
81 | The key/value pairs of json::objects are printed in the order |
82 | in which the keys were originally inserted. */ | |
4a4412b9 DM |
83 | |
84 | void | |
3bd8241a | 85 | value::dump (FILE *outf, bool formatted) const |
4a4412b9 DM |
86 | { |
87 | pretty_printer pp; | |
88 | pp_buffer (&pp)->stream = outf; | |
3bd8241a | 89 | print (&pp, formatted); |
4a4412b9 DM |
90 | pp_flush (&pp); |
91 | } | |
92 | ||
93 | /* class json::object, a subclass of json::value, representing | |
7f1e15f7 | 94 | an ordered collection of key/value pairs. */ |
4a4412b9 DM |
95 | |
96 | /* json:object's dtor. */ | |
97 | ||
98 | object::~object () | |
99 | { | |
100 | for (map_t::iterator it = m_map.begin (); it != m_map.end (); ++it) | |
101 | { | |
102 | free (const_cast <char *>((*it).first)); | |
103 | delete ((*it).second); | |
104 | } | |
105 | } | |
106 | ||
107 | /* Implementation of json::value::print for json::object. */ | |
108 | ||
109 | void | |
3bd8241a | 110 | object::print (pretty_printer *pp, bool formatted) const |
4a4412b9 | 111 | { |
4a4412b9 | 112 | pp_character (pp, '{'); |
3bd8241a DM |
113 | if (formatted) |
114 | pp_indentation (pp) += 1; | |
7f1e15f7 DM |
115 | |
116 | /* Iterate in the order that the keys were inserted. */ | |
117 | unsigned i; | |
118 | const char *key; | |
119 | FOR_EACH_VEC_ELT (m_keys, i, key) | |
4a4412b9 | 120 | { |
7f1e15f7 | 121 | if (i > 0) |
3bd8241a DM |
122 | { |
123 | pp_string (pp, ","); | |
124 | if (formatted) | |
125 | { | |
126 | pp_newline (pp); | |
127 | pp_indent (pp); | |
128 | } | |
129 | else | |
130 | pp_space (pp); | |
131 | } | |
7f1e15f7 DM |
132 | map_t &mut_map = const_cast<map_t &> (m_map); |
133 | value *value = *mut_map.get (key); | |
30d9a3a6 | 134 | print_escaped_json_string (pp, key, strlen (key)); |
ca23341b | 135 | pp_string (pp, ": "); |
3bd8241a DM |
136 | const int indent = strlen (key) + 4; |
137 | if (formatted) | |
138 | pp_indentation (pp) += indent; | |
139 | value->print (pp, formatted); | |
140 | if (formatted) | |
141 | pp_indentation (pp) -= indent; | |
4a4412b9 | 142 | } |
3bd8241a DM |
143 | if (formatted) |
144 | pp_indentation (pp) -= 1; | |
4a4412b9 DM |
145 | pp_character (pp, '}'); |
146 | } | |
147 | ||
dad2580c | 148 | /* Set the json::value * for KEY, taking ownership of V |
4a4412b9 DM |
149 | (and taking a copy of KEY if necessary). */ |
150 | ||
151 | void | |
152 | object::set (const char *key, value *v) | |
153 | { | |
dad2580c DM |
154 | gcc_assert (key); |
155 | gcc_assert (v); | |
156 | ||
4a4412b9 DM |
157 | value **ptr = m_map.get (key); |
158 | if (ptr) | |
159 | { | |
160 | /* If the key is already present, delete the existing value | |
161 | and overwrite it. */ | |
162 | delete *ptr; | |
163 | *ptr = v; | |
164 | } | |
165 | else | |
7f1e15f7 DM |
166 | { |
167 | /* If the key wasn't already present, take a copy of the key, | |
168 | and store the value. */ | |
169 | char *owned_key = xstrdup (key); | |
170 | m_map.put (owned_key, v); | |
171 | m_keys.safe_push (owned_key); | |
172 | } | |
4a4412b9 DM |
173 | } |
174 | ||
30d3ba51 DM |
175 | /* Get the json::value * for KEY. |
176 | ||
177 | The object retains ownership of the value. */ | |
178 | ||
179 | value * | |
180 | object::get (const char *key) const | |
181 | { | |
182 | gcc_assert (key); | |
183 | ||
184 | value **ptr = const_cast <map_t &> (m_map).get (key); | |
185 | if (ptr) | |
186 | return *ptr; | |
187 | else | |
188 | return NULL; | |
189 | } | |
190 | ||
070944fd DM |
191 | /* Set value of KEY within this object to a JSON |
192 | string value based on UTF8_VALUE. */ | |
193 | ||
194 | void | |
195 | object::set_string (const char *key, const char *utf8_value) | |
196 | { | |
197 | set (key, new json::string (utf8_value)); | |
198 | } | |
199 | ||
200 | /* Set value of KEY within this object to a JSON | |
201 | integer value based on V. */ | |
202 | ||
203 | void | |
204 | object::set_integer (const char *key, long v) | |
205 | { | |
206 | set (key, new json::integer_number (v)); | |
207 | } | |
208 | ||
209 | /* Set value of KEY within this object to a JSON | |
210 | floating point value based on V. */ | |
211 | ||
212 | void | |
213 | object::set_float (const char *key, double v) | |
214 | { | |
215 | set (key, new json::float_number (v)); | |
216 | } | |
217 | ||
218 | /* Set value of KEY within this object to the JSON | |
219 | literal true or false, based on V. */ | |
220 | ||
221 | void | |
222 | object::set_bool (const char *key, bool v) | |
223 | { | |
224 | set (key, new json::literal (v)); | |
225 | } | |
226 | ||
4a4412b9 DM |
227 | /* class json::array, a subclass of json::value, representing |
228 | an ordered collection of values. */ | |
229 | ||
230 | /* json::array's dtor. */ | |
231 | ||
232 | array::~array () | |
233 | { | |
234 | unsigned i; | |
235 | value *v; | |
236 | FOR_EACH_VEC_ELT (m_elements, i, v) | |
237 | delete v; | |
238 | } | |
239 | ||
240 | /* Implementation of json::value::print for json::array. */ | |
241 | ||
242 | void | |
3bd8241a | 243 | array::print (pretty_printer *pp, bool formatted) const |
4a4412b9 DM |
244 | { |
245 | pp_character (pp, '['); | |
3bd8241a DM |
246 | if (formatted) |
247 | pp_indentation (pp) += 1; | |
4a4412b9 DM |
248 | unsigned i; |
249 | value *v; | |
250 | FOR_EACH_VEC_ELT (m_elements, i, v) | |
251 | { | |
252 | if (i) | |
3bd8241a DM |
253 | { |
254 | pp_string (pp, ","); | |
255 | if (formatted) | |
256 | { | |
257 | pp_newline (pp); | |
258 | pp_indent (pp); | |
259 | } | |
260 | else | |
261 | pp_space (pp); | |
262 | } | |
263 | v->print (pp, formatted); | |
4a4412b9 | 264 | } |
3bd8241a DM |
265 | if (formatted) |
266 | pp_indentation (pp) -= 1; | |
4a4412b9 DM |
267 | pp_character (pp, ']'); |
268 | } | |
269 | ||
dad2580c DM |
270 | /* Append non-NULL value V to a json::array, taking ownership of V. */ |
271 | ||
272 | void | |
273 | array::append (value *v) | |
274 | { | |
275 | gcc_assert (v); | |
276 | m_elements.safe_push (v); | |
277 | } | |
278 | ||
07622278 | 279 | /* class json::float_number, a subclass of json::value, wrapping a double. */ |
4a4412b9 | 280 | |
07622278 | 281 | /* Implementation of json::value::print for json::float_number. */ |
4a4412b9 DM |
282 | |
283 | void | |
3bd8241a DM |
284 | float_number::print (pretty_printer *pp, |
285 | bool formatted ATTRIBUTE_UNUSED) const | |
4a4412b9 DM |
286 | { |
287 | char tmp[1024]; | |
288 | snprintf (tmp, sizeof (tmp), "%g", m_value); | |
289 | pp_string (pp, tmp); | |
290 | } | |
291 | ||
07622278 ML |
292 | /* class json::integer_number, a subclass of json::value, wrapping a long. */ |
293 | ||
294 | /* Implementation of json::value::print for json::integer_number. */ | |
295 | ||
296 | void | |
3bd8241a DM |
297 | integer_number::print (pretty_printer *pp, |
298 | bool formatted ATTRIBUTE_UNUSED) const | |
07622278 ML |
299 | { |
300 | char tmp[1024]; | |
301 | snprintf (tmp, sizeof (tmp), "%ld", m_value); | |
302 | pp_string (pp, tmp); | |
303 | } | |
304 | ||
305 | ||
4a4412b9 DM |
306 | /* class json::string, a subclass of json::value. */ |
307 | ||
dad2580c DM |
308 | /* json::string's ctor. */ |
309 | ||
310 | string::string (const char *utf8) | |
311 | { | |
312 | gcc_assert (utf8); | |
313 | m_utf8 = xstrdup (utf8); | |
ee08aa9a LH |
314 | m_len = strlen (utf8); |
315 | } | |
316 | ||
317 | string::string (const char *utf8, size_t len) | |
318 | { | |
319 | gcc_assert (utf8); | |
320 | m_utf8 = XNEWVEC (char, len); | |
321 | m_len = len; | |
322 | memcpy (m_utf8, utf8, len); | |
dad2580c DM |
323 | } |
324 | ||
325 | /* Implementation of json::value::print for json::string. */ | |
326 | ||
4a4412b9 | 327 | void |
3bd8241a DM |
328 | string::print (pretty_printer *pp, |
329 | bool formatted ATTRIBUTE_UNUSED) const | |
4a4412b9 | 330 | { |
30d9a3a6 | 331 | print_escaped_json_string (pp, m_utf8, m_len); |
4a4412b9 DM |
332 | } |
333 | ||
334 | /* class json::literal, a subclass of json::value. */ | |
335 | ||
336 | /* Implementation of json::value::print for json::literal. */ | |
337 | ||
338 | void | |
3bd8241a DM |
339 | literal::print (pretty_printer *pp, |
340 | bool formatted ATTRIBUTE_UNUSED) const | |
4a4412b9 DM |
341 | { |
342 | switch (m_kind) | |
343 | { | |
344 | case JSON_TRUE: | |
345 | pp_string (pp, "true"); | |
346 | break; | |
347 | case JSON_FALSE: | |
348 | pp_string (pp, "false"); | |
349 | break; | |
350 | case JSON_NULL: | |
351 | pp_string (pp, "null"); | |
352 | break; | |
353 | default: | |
354 | gcc_unreachable (); | |
355 | } | |
356 | } | |
357 | ||
358 | \f | |
359 | #if CHECKING_P | |
360 | ||
361 | namespace selftest { | |
362 | ||
363 | /* Selftests. */ | |
364 | ||
365 | /* Verify that JV->print () prints EXPECTED_JSON. */ | |
366 | ||
367 | static void | |
3bd8241a DM |
368 | assert_print_eq (const location &loc, |
369 | const json::value &jv, | |
370 | bool formatted, | |
371 | const char *expected_json) | |
4a4412b9 DM |
372 | { |
373 | pretty_printer pp; | |
3bd8241a | 374 | jv.print (&pp, formatted); |
2220263f | 375 | ASSERT_STREQ_AT (loc, expected_json, pp_formatted_text (&pp)); |
4a4412b9 DM |
376 | } |
377 | ||
3bd8241a DM |
378 | #define ASSERT_PRINT_EQ(JV, FORMATTED, EXPECTED_JSON) \ |
379 | assert_print_eq (SELFTEST_LOCATION, JV, FORMATTED, EXPECTED_JSON) | |
2220263f | 380 | |
30d3ba51 DM |
381 | /* Verify that object::get works as expected. */ |
382 | ||
383 | static void | |
384 | test_object_get () | |
385 | { | |
386 | object obj; | |
387 | value *val = new json::string ("value"); | |
388 | obj.set ("foo", val); | |
389 | ASSERT_EQ (obj.get ("foo"), val); | |
390 | ASSERT_EQ (obj.get ("not-present"), NULL); | |
391 | } | |
392 | ||
7f1e15f7 | 393 | /* Verify that JSON objects are written correctly. */ |
4a4412b9 DM |
394 | |
395 | static void | |
396 | test_writing_objects () | |
397 | { | |
398 | object obj; | |
070944fd DM |
399 | obj.set_string ("foo", "bar"); |
400 | obj.set_string ("baz", "quux"); | |
30d9a3a6 DM |
401 | obj.set_string ("\"\\\b\f\n\r\t", "value for awkward key"); |
402 | ||
7f1e15f7 DM |
403 | /* This test relies on json::object writing out key/value pairs |
404 | in key-insertion order. */ | |
3bd8241a DM |
405 | ASSERT_PRINT_EQ (obj, true, |
406 | "{\"foo\": \"bar\",\n" | |
30d9a3a6 DM |
407 | " \"baz\": \"quux\",\n" |
408 | " \"\\\"\\\\\\b\\f\\n\\r\\t\": \"value for awkward key\"}"); | |
3bd8241a | 409 | ASSERT_PRINT_EQ (obj, false, |
30d9a3a6 DM |
410 | "{\"foo\": \"bar\", \"baz\": \"quux\"" |
411 | ", \"\\\"\\\\\\b\\f\\n\\r\\t\": \"value for awkward key\"}"); | |
4a4412b9 DM |
412 | } |
413 | ||
414 | /* Verify that JSON arrays are written correctly. */ | |
415 | ||
416 | static void | |
417 | test_writing_arrays () | |
418 | { | |
419 | array arr; | |
3bd8241a | 420 | ASSERT_PRINT_EQ (arr, true, "[]"); |
4a4412b9 DM |
421 | |
422 | arr.append (new json::string ("foo")); | |
3bd8241a | 423 | ASSERT_PRINT_EQ (arr, true, "[\"foo\"]"); |
4a4412b9 DM |
424 | |
425 | arr.append (new json::string ("bar")); | |
3bd8241a DM |
426 | ASSERT_PRINT_EQ (arr, true, |
427 | "[\"foo\",\n" | |
428 | " \"bar\"]"); | |
429 | ASSERT_PRINT_EQ (arr, false, | |
430 | "[\"foo\", \"bar\"]"); | |
4a4412b9 DM |
431 | } |
432 | ||
433 | /* Verify that JSON numbers are written correctly. */ | |
434 | ||
435 | static void | |
07622278 ML |
436 | test_writing_float_numbers () |
437 | { | |
3bd8241a DM |
438 | ASSERT_PRINT_EQ (float_number (0), true, "0"); |
439 | ASSERT_PRINT_EQ (float_number (42), true, "42"); | |
440 | ASSERT_PRINT_EQ (float_number (-100), true, "-100"); | |
441 | ASSERT_PRINT_EQ (float_number (123456789), true, "1.23457e+08"); | |
07622278 ML |
442 | } |
443 | ||
444 | static void | |
445 | test_writing_integer_numbers () | |
4a4412b9 | 446 | { |
3bd8241a DM |
447 | ASSERT_PRINT_EQ (integer_number (0), true, "0"); |
448 | ASSERT_PRINT_EQ (integer_number (42), true, "42"); | |
449 | ASSERT_PRINT_EQ (integer_number (-100), true, "-100"); | |
450 | ASSERT_PRINT_EQ (integer_number (123456789), true, "123456789"); | |
451 | ASSERT_PRINT_EQ (integer_number (-123456789), true, "-123456789"); | |
4a4412b9 DM |
452 | } |
453 | ||
454 | /* Verify that JSON strings are written correctly. */ | |
455 | ||
456 | static void | |
457 | test_writing_strings () | |
458 | { | |
459 | string foo ("foo"); | |
3bd8241a | 460 | ASSERT_PRINT_EQ (foo, true, "\"foo\""); |
4a4412b9 DM |
461 | |
462 | string contains_quotes ("before \"quoted\" after"); | |
3bd8241a | 463 | ASSERT_PRINT_EQ (contains_quotes, true, "\"before \\\"quoted\\\" after\""); |
ee08aa9a LH |
464 | |
465 | const char data[] = {'a', 'b', 'c', 'd', '\0', 'e', 'f'}; | |
466 | string not_terminated (data, 3); | |
3bd8241a | 467 | ASSERT_PRINT_EQ (not_terminated, true, "\"abc\""); |
ee08aa9a | 468 | string embedded_null (data, sizeof data); |
3bd8241a | 469 | ASSERT_PRINT_EQ (embedded_null, true, "\"abcd\\0ef\""); |
4a4412b9 DM |
470 | } |
471 | ||
9ed31860 | 472 | /* Verify that JSON literals are written correctly. */ |
4a4412b9 DM |
473 | |
474 | static void | |
475 | test_writing_literals () | |
476 | { | |
3bd8241a DM |
477 | ASSERT_PRINT_EQ (literal (JSON_TRUE), true, "true"); |
478 | ASSERT_PRINT_EQ (literal (JSON_FALSE), true, "false"); | |
479 | ASSERT_PRINT_EQ (literal (JSON_NULL), true, "null"); | |
c8fda30f | 480 | |
3bd8241a DM |
481 | ASSERT_PRINT_EQ (literal (true), true, "true"); |
482 | ASSERT_PRINT_EQ (literal (false), true, "false"); | |
483 | } | |
484 | ||
485 | /* Verify that nested values are formatted correctly when written. */ | |
486 | ||
487 | static void | |
488 | test_formatting () | |
489 | { | |
490 | object obj; | |
491 | object *child = new object; | |
492 | object *grandchild = new object; | |
493 | ||
494 | obj.set_string ("str", "bar"); | |
495 | obj.set ("child", child); | |
496 | obj.set_integer ("int", 42); | |
497 | ||
498 | child->set ("grandchild", grandchild); | |
499 | child->set_integer ("int", 1776); | |
500 | ||
501 | array *arr = new array; | |
502 | for (int i = 0; i < 3; i++) | |
503 | arr->append (new integer_number (i)); | |
504 | grandchild->set ("arr", arr); | |
505 | grandchild->set_integer ("int", 1066); | |
506 | ||
507 | /* This test relies on json::object writing out key/value pairs | |
508 | in key-insertion order. */ | |
509 | ASSERT_PRINT_EQ (obj, true, | |
510 | ("{\"str\": \"bar\",\n" | |
511 | " \"child\": {\"grandchild\": {\"arr\": [0,\n" | |
512 | " 1,\n" | |
513 | " 2],\n" | |
514 | " \"int\": 1066},\n" | |
515 | " \"int\": 1776},\n" | |
516 | " \"int\": 42}")); | |
517 | ASSERT_PRINT_EQ (obj, false, | |
518 | ("{\"str\": \"bar\", \"child\": {\"grandchild\":" | |
519 | " {\"arr\": [0, 1, 2], \"int\": 1066}," | |
520 | " \"int\": 1776}, \"int\": 42}")); | |
4a4412b9 DM |
521 | } |
522 | ||
523 | /* Run all of the selftests within this file. */ | |
524 | ||
525 | void | |
526 | json_cc_tests () | |
527 | { | |
30d3ba51 | 528 | test_object_get (); |
4a4412b9 DM |
529 | test_writing_objects (); |
530 | test_writing_arrays (); | |
07622278 ML |
531 | test_writing_float_numbers (); |
532 | test_writing_integer_numbers (); | |
4a4412b9 DM |
533 | test_writing_strings (); |
534 | test_writing_literals (); | |
3bd8241a | 535 | test_formatting (); |
4a4412b9 DM |
536 | } |
537 | ||
538 | } // namespace selftest | |
539 | ||
540 | #endif /* #if CHECKING_P */ |