]>
Commit | Line | Data |
---|---|---|
ff28b140 TL |
1 | /* |
2 | * json.cpp | |
3 | * | |
4 | * Home page of code is: https://www.smartmontools.org | |
5 | * | |
6 | * Copyright (C) 2017-18 Christian Franke | |
7 | * | |
8 | * SPDX-License-Identifier: GPL-2.0-or-later | |
9 | */ | |
10 | ||
11 | #include "config.h" | |
12 | #define __STDC_FORMAT_MACROS 1 // enable PRI* for C++ | |
13 | ||
14 | #include "json.h" | |
15 | ||
16 | const char * json_cvsid = "$Id: json.cpp 4830 2018-11-02 21:21:48Z chrfranke $" | |
17 | JSON_H_CVSID; | |
18 | ||
19 | #include "sg_unaligned.h" | |
20 | #include "utility.h" // uint128_*() | |
21 | ||
22 | #include <inttypes.h> | |
23 | #include <stdexcept> | |
24 | ||
25 | static void jassert_failed(int line, const char * expr) | |
26 | { | |
27 | char msg[128]; | |
28 | // Avoid __FILE__ as it may break reproducible builds | |
29 | snprintf(msg, sizeof(msg), "json.cpp(%d): Assertion failed: %s", line, expr); | |
30 | throw std::logic_error(msg); | |
31 | } | |
32 | ||
33 | #define jassert(expr) (!(expr) ? jassert_failed(__LINE__, #expr) : (void)0) | |
34 | ||
35 | static void check_key(const char * key) | |
36 | { | |
37 | // Limit: object keys should be valid identifiers (lowercase only) | |
38 | char c = key[0]; | |
39 | jassert('a' <= c && c <= 'z'); | |
40 | for (int i = 1; (c = key[i]); i++) | |
41 | jassert(('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || (c == '_')); | |
42 | } | |
43 | ||
44 | json::ref::ref(json & js, const char * key) | |
45 | : m_js(js) | |
46 | { | |
47 | check_key(key); | |
48 | m_path.push_back(node_info(key)); | |
49 | } | |
50 | ||
51 | json::ref::ref(const ref & base, const char * key) | |
52 | : m_js(base.m_js), m_path(base.m_path) | |
53 | { | |
54 | check_key(key); | |
55 | m_path.push_back(node_info(key)); | |
56 | } | |
57 | ||
58 | json::ref::ref(const ref & base, int index) | |
59 | : m_js(base.m_js), m_path(base.m_path) | |
60 | { | |
61 | jassert(0 <= index && index < 10000); // Limit: large arrays not supported | |
62 | m_path.push_back(node_info(index)); | |
63 | } | |
64 | ||
65 | json::ref::ref(const ref & base, const char * /*dummy*/, const char * key_suffix) | |
66 | : m_js(base.m_js), m_path(base.m_path) | |
67 | { | |
68 | int n = (int)m_path.size(), i; | |
69 | for (i = n; --i >= 0; ) { | |
70 | std::string & base_key = m_path[i].key; | |
71 | if (base_key.empty()) | |
72 | continue; // skip array | |
73 | base_key += key_suffix; | |
74 | break; | |
75 | } | |
76 | jassert(i >= 0); // Limit: top level element must be an object | |
77 | } | |
78 | ||
79 | void json::ref::operator=(bool value) | |
80 | { | |
81 | m_js.set_bool(m_path, value); | |
82 | } | |
83 | ||
84 | void json::ref::operator=(long long value) | |
85 | { | |
86 | m_js.set_int64(m_path, (int64_t)value); | |
87 | } | |
88 | ||
89 | void json::ref::operator=(unsigned long long value) | |
90 | { | |
91 | m_js.set_uint64(m_path, (uint64_t)value); | |
92 | } | |
93 | ||
94 | void json::ref::operator=(int value) | |
95 | { | |
96 | operator=((long long)value); | |
97 | } | |
98 | ||
99 | void json::ref::operator=(unsigned value) | |
100 | { | |
101 | operator=((unsigned long long)value); | |
102 | } | |
103 | ||
104 | void json::ref::operator=(long value) | |
105 | { | |
106 | operator=((long long)value); | |
107 | } | |
108 | ||
109 | void json::ref::operator=(unsigned long value) | |
110 | { | |
111 | operator=((unsigned long long)value); | |
112 | } | |
113 | ||
114 | void json::ref::operator=(const std::string & value) | |
115 | { | |
116 | m_js.set_string(m_path, value); | |
117 | } | |
118 | ||
119 | void json::ref::operator=(const char * value) | |
120 | { | |
121 | jassert(value); // Limit: null not supported | |
122 | operator=(std::string(value)); | |
123 | } | |
124 | ||
125 | void json::ref::set_uint128(uint64_t value_hi, uint64_t value_lo) | |
126 | { | |
127 | if (!value_hi) | |
128 | operator=((unsigned long long)value_lo); | |
129 | else | |
130 | m_js.set_uint128(m_path, value_hi, value_lo); | |
131 | } | |
132 | ||
133 | bool json::ref::set_if_safe_uint64(uint64_t value) | |
134 | { | |
135 | if (!is_safe_uint(value)) | |
136 | return false; | |
137 | operator=((unsigned long long)value); | |
138 | return true; | |
139 | } | |
140 | ||
141 | bool json::ref::set_if_safe_uint128(uint64_t value_hi, uint64_t value_lo) | |
142 | { | |
143 | if (value_hi) | |
144 | return false; | |
145 | return set_if_safe_uint64(value_lo); | |
146 | } | |
147 | ||
148 | bool json::ref::set_if_safe_le128(const void * pvalue) | |
149 | { | |
150 | return set_if_safe_uint128(sg_get_unaligned_le64((const uint8_t *)pvalue + 8), | |
151 | sg_get_unaligned_le64( pvalue )); | |
152 | } | |
153 | ||
154 | void json::ref::set_unsafe_uint64(uint64_t value) | |
155 | { | |
156 | // Output as number "KEY" | |
157 | operator=((unsigned long long)value); | |
158 | if (!m_js.m_verbose && is_safe_uint(value)) | |
159 | return; | |
160 | // Output as string "KEY_s" | |
161 | char s[32]; | |
162 | snprintf(s, sizeof(s), "%" PRIu64, value); | |
163 | with_suffix("_s") = s; | |
164 | } | |
165 | ||
166 | void json::ref::set_unsafe_uint128(uint64_t value_hi, uint64_t value_lo) | |
167 | { | |
168 | if (!m_js.m_verbose && !value_hi) | |
169 | set_unsafe_uint64(value_lo); | |
170 | else { | |
171 | // Output as number "KEY", string "KEY_s" and LE byte array "KEY_le[]" | |
172 | m_js.m_uint128_output = true; | |
173 | set_uint128(value_hi, value_lo); | |
174 | char s[64]; | |
175 | with_suffix("_s") = uint128_hilo_to_str(s, value_hi, value_lo); | |
176 | ref le = with_suffix("_le"); | |
177 | for (int i = 0; i < 8; i++) { | |
178 | uint64_t v = (value_lo >> (i << 3)); | |
179 | if (!v && !value_hi) | |
180 | break; | |
181 | le[i] = v & 0xff; | |
182 | } | |
183 | for (int i = 0; i < 8; i++) { | |
184 | uint64_t v = value_hi >> (i << 3); | |
185 | if (!v) | |
186 | break; | |
187 | le[8 + i] = v & 0xff; | |
188 | } | |
189 | } | |
190 | } | |
191 | ||
192 | void json::ref::set_unsafe_le128(const void * pvalue) | |
193 | { | |
194 | set_unsafe_uint128(sg_get_unaligned_le64((const uint8_t *)pvalue + 8), | |
195 | sg_get_unaligned_le64( pvalue )); | |
196 | } | |
197 | ||
198 | json::node::node() | |
199 | : type(nt_unset), | |
200 | intval(0), | |
201 | intval_hi(0) | |
202 | { | |
203 | } | |
204 | ||
205 | json::node::node(const std::string & key_) | |
206 | : type(nt_unset), | |
207 | intval(0), | |
208 | intval_hi(0), | |
209 | key(key_) | |
210 | { | |
211 | } | |
212 | ||
213 | json::node::~node() | |
214 | { | |
215 | for (size_t i = 0; i < childs.size(); i++) | |
216 | delete childs[i]; | |
217 | } | |
218 | ||
219 | json::node::const_iterator::const_iterator(const json::node * node_p, bool sorted) | |
220 | : m_node_p(node_p), | |
221 | m_use_map(sorted && node_p->type == nt_object), | |
222 | m_child_idx(0) | |
223 | { | |
224 | if (m_use_map) | |
225 | m_key_iter = node_p->key2index.begin(); | |
226 | } | |
227 | ||
228 | bool json::node::const_iterator::at_end() const | |
229 | { | |
230 | if (m_use_map) | |
231 | return (m_key_iter == m_node_p->key2index.end()); | |
232 | else | |
233 | return (m_child_idx >= m_node_p->childs.size()); | |
234 | } | |
235 | ||
236 | unsigned json::node::const_iterator::array_index() const | |
237 | { | |
238 | jassert(m_node_p->type == nt_array); | |
239 | return m_child_idx; | |
240 | } | |
241 | ||
242 | void json::node::const_iterator::operator++() | |
243 | { | |
244 | if (m_use_map) | |
245 | ++m_key_iter; | |
246 | else | |
247 | ++m_child_idx; | |
248 | } | |
249 | ||
250 | const json::node * json::node::const_iterator::operator*() const | |
251 | { | |
252 | if (m_use_map) | |
253 | return m_node_p->childs[m_key_iter->second]; | |
254 | else | |
255 | return m_node_p->childs[m_child_idx]; | |
256 | } | |
257 | ||
258 | json::node * json::find_or_create_node(const json::node_path & path, node_type type) | |
259 | { | |
260 | node * p = &m_root_node; | |
261 | for (unsigned i = 0; i < path.size(); i++) { | |
262 | const node_info & pi = path[i]; | |
263 | if (!pi.key.empty()) { | |
264 | // Object | |
265 | if (p->type == nt_unset) | |
266 | p->type = nt_object; | |
267 | else | |
268 | jassert(p->type == nt_object); // Limit: type change not supported | |
269 | // Existing or new object element? | |
270 | node::keymap::iterator ni = p->key2index.find(pi.key); | |
271 | node * p2; | |
272 | if (ni != p->key2index.end()) { | |
273 | // Object element exists | |
274 | p2 = p->childs[ni->second]; | |
275 | } | |
276 | else { | |
277 | // Create new object element | |
278 | p->key2index[pi.key] = (unsigned)p->childs.size(); | |
279 | p2 = new node(pi.key); | |
280 | p->childs.push_back(p2); | |
281 | } | |
282 | jassert(p2 && p2->key == pi.key); | |
283 | p = p2; | |
284 | } | |
285 | ||
286 | else { | |
287 | // Array | |
288 | if (p->type == nt_unset) | |
289 | p->type = nt_array; | |
290 | else | |
291 | jassert(p->type == nt_array); // Limit: type change not supported | |
292 | node * p2; | |
293 | // Existing or new array element? | |
294 | if (pi.index < (int)p->childs.size()) { | |
295 | // Array index exists | |
296 | p2 = p->childs[pi.index]; | |
297 | if (!p2) // Already created ? | |
298 | p->childs[pi.index] = p2 = new node; | |
299 | } | |
300 | else { | |
301 | // Grow array, fill gap, create new element | |
302 | p->childs.resize(pi.index + 1); | |
303 | p->childs[pi.index] = p2 = new node; | |
304 | } | |
305 | jassert(p2 && p2->key.empty()); | |
306 | p = p2; | |
307 | } | |
308 | } | |
309 | ||
310 | if ( p->type == nt_unset | |
311 | || ( nt_int <= p->type && p->type <= nt_uint128 | |
312 | && nt_int <= type && type <= nt_uint128)) | |
313 | p->type = type; | |
314 | else | |
315 | jassert(p->type == type); // Limit: type change not supported | |
316 | return p; | |
317 | } | |
318 | ||
319 | json::json() | |
320 | : m_enabled(false), | |
321 | m_verbose(false), | |
322 | m_uint128_output(false) | |
323 | { | |
324 | } | |
325 | ||
326 | void json::set_bool(const node_path & path, bool value) | |
327 | { | |
328 | if (!m_enabled) | |
329 | return; | |
330 | find_or_create_node(path, nt_bool)->intval = (value ? 1 : 0); | |
331 | } | |
332 | ||
333 | void json::set_int64(const node_path & path, int64_t value) | |
334 | { | |
335 | if (!m_enabled) | |
336 | return; | |
337 | find_or_create_node(path, nt_int)->intval = (uint64_t)value; | |
338 | } | |
339 | ||
340 | void json::set_uint64(const node_path & path, uint64_t value) | |
341 | { | |
342 | if (!m_enabled) | |
343 | return; | |
344 | find_or_create_node(path, nt_uint)->intval = value; | |
345 | } | |
346 | ||
347 | void json::set_uint128(const node_path & path, uint64_t value_hi, uint64_t value_lo) | |
348 | { | |
349 | if (!m_enabled) | |
350 | return; | |
351 | node * p = find_or_create_node(path, nt_uint128); | |
352 | p->intval_hi = value_hi; | |
353 | p->intval = value_lo; | |
354 | } | |
355 | ||
356 | void json::set_string(const node_path & path, const std::string & value) | |
357 | { | |
358 | if (!m_enabled) | |
359 | return; | |
360 | find_or_create_node(path, nt_string)->strval = value; | |
361 | } | |
362 | ||
363 | static void print_string(FILE * f, const char * s) | |
364 | { | |
365 | putc('"', f); | |
366 | for (int i = 0; s[i]; i++) { | |
367 | char c = s[i]; | |
368 | if (c == '"' || c == '\\') | |
369 | putc('\\', f); | |
370 | else if (c == '\t') { | |
371 | putc('\\', f); c = 't'; | |
372 | } | |
373 | else if ((unsigned char)c < ' ') | |
374 | c = '?'; // Not ' '-'~', '\t' or UTF-8 | |
375 | putc(c, f); | |
376 | } | |
377 | putc('"', f); | |
378 | } | |
379 | ||
380 | void json::print_json(FILE * f, bool pretty, bool sorted, const node * p, int level) | |
381 | { | |
382 | if (!p->key.empty()) | |
383 | fprintf(f, "\"%s\":%s", p->key.c_str(), (pretty ? " " : "")); | |
384 | ||
385 | switch (p->type) { | |
386 | case nt_object: | |
387 | case nt_array: | |
388 | putc((p->type == nt_object ? '{' : '['), f); | |
389 | if (!p->childs.empty()) { | |
390 | bool first = true; | |
391 | for (node::const_iterator it(p, sorted); !it.at_end(); ++it) { | |
392 | if (!first) | |
393 | putc(',', f); | |
394 | if (pretty) | |
395 | fprintf(f, "\n%*s", (level + 1) * 2, ""); | |
396 | const node * p2 = *it; | |
397 | if (!p2) { | |
398 | // Unset element of sparse array | |
399 | jassert(p->type == nt_array); | |
400 | fputs("null", f); | |
401 | } | |
402 | else { | |
403 | // Recurse | |
404 | print_json(f, pretty, sorted, p2, level + 1); | |
405 | } | |
406 | first = false; | |
407 | } | |
408 | if (pretty) | |
409 | fprintf(f, "\n%*s", level * 2, ""); | |
410 | } | |
411 | putc((p->type == nt_object ? '}' : ']'), f); | |
412 | break; | |
413 | ||
414 | case nt_bool: | |
415 | fputs((p->intval ? "true" : "false"), f); | |
416 | break; | |
417 | ||
418 | case nt_int: | |
419 | fprintf(f, "%" PRId64, (int64_t)p->intval); | |
420 | break; | |
421 | ||
422 | case nt_uint: | |
423 | fprintf(f, "%" PRIu64, p->intval); | |
424 | break; | |
425 | ||
426 | case nt_uint128: | |
427 | { | |
428 | char buf[64]; | |
429 | fputs(uint128_hilo_to_str(buf, p->intval_hi, p->intval), f); | |
430 | } | |
431 | break; | |
432 | ||
433 | case nt_string: | |
434 | print_string(f, p->strval.c_str()); | |
435 | break; | |
436 | ||
437 | default: jassert(false); | |
438 | } | |
439 | } | |
440 | ||
441 | void json::print_flat(FILE * f, bool sorted, const node * p, std::string & path) | |
442 | { | |
443 | switch (p->type) { | |
444 | case nt_object: | |
445 | case nt_array: | |
446 | fprintf(f, "%s = %s;\n", path.c_str(), (p->type == nt_object ? "{}" : "[]")); | |
447 | if (!p->childs.empty()) { | |
448 | unsigned len = path.size(); | |
449 | for (node::const_iterator it(p, sorted); !it.at_end(); ++it) { | |
450 | const node * p2 = *it; | |
451 | if (p->type == nt_array) { | |
452 | char buf[10]; snprintf(buf, sizeof(buf), "[%u]", it.array_index()); | |
453 | path += buf; | |
454 | } | |
455 | else { | |
456 | path += '.'; path += p2->key; | |
457 | } | |
458 | if (!p2) { | |
459 | // Unset element of sparse array | |
460 | jassert(p->type == nt_array); | |
461 | fprintf(f, "%s = null;\n", path.c_str()); | |
462 | } | |
463 | else { | |
464 | // Recurse | |
465 | print_flat(f, sorted, p2, path); | |
466 | } | |
467 | path.erase(len); | |
468 | } | |
469 | } | |
470 | break; | |
471 | ||
472 | case nt_bool: | |
473 | fprintf(f, "%s = %s;\n", path.c_str(), (p->intval ? "true" : "false")); | |
474 | break; | |
475 | ||
476 | case nt_int: | |
477 | fprintf(f, "%s = %" PRId64 ";\n", path.c_str(), (int64_t)p->intval); | |
478 | break; | |
479 | ||
480 | case nt_uint: | |
481 | fprintf(f, "%s = %" PRIu64 ";\n", path.c_str(), p->intval); | |
482 | break; | |
483 | ||
484 | case nt_uint128: | |
485 | { | |
486 | char buf[64]; | |
487 | fprintf(f, "%s = %s;\n", path.c_str(), | |
488 | uint128_hilo_to_str(buf, p->intval_hi, p->intval)); | |
489 | } | |
490 | break; | |
491 | ||
492 | case nt_string: | |
493 | fprintf(f, "%s = ", path.c_str()); | |
494 | print_string(f, p->strval.c_str()); | |
495 | fputs(";\n", f); | |
496 | break; | |
497 | ||
498 | default: jassert(false); | |
499 | } | |
500 | } | |
501 | ||
502 | void json::print(FILE * f, const print_options & options) const | |
503 | { | |
504 | if (m_root_node.type == nt_unset) | |
505 | return; | |
506 | jassert(m_root_node.type == nt_object); | |
507 | ||
508 | if (!options.flat) { | |
509 | print_json(f, options.pretty, options.sorted, &m_root_node, 0); | |
510 | if (options.pretty) | |
511 | putc('\n', f); | |
512 | } | |
513 | else { | |
514 | std::string path("json"); | |
515 | print_flat(f, options.sorted, &m_root_node, path); | |
516 | } | |
517 | } |