blob: 4da2a18fe562c063a650e2609db1cbbf5e3073c8 [file] [log] [blame]
Nigel Taod0b16cb2020-03-14 10:15:54 +11001// Copyright 2020 The Wuffs Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// ----------------
16
17/*
18jsonfindptrs reads UTF-8 JSON from stdin and writes every node's JSON Pointer
19(RFC 6901) to stdout.
20
Nigel Taod60815c2020-03-26 14:32:35 +110021See the "const char* g_usage" string below for details.
Nigel Taod0b16cb2020-03-14 10:15:54 +110022
23----
24
25This program uses Wuffs' JSON decoder at a relatively high level, building
26in-memory representations of JSON 'things' (e.g. numbers, strings, objects).
27After the entire input has been converted, walking the tree prints the output
Nigel Taocf6c5782020-08-03 23:43:45 +100028(in sorted order). The wuffs_aux::DecodeJson library function converts the
29lower level token stream to higher level callbacks. This .cc file deals only
30with those callbacks, not with tokens per se.
Nigel Taod0b16cb2020-03-14 10:15:54 +110031
32This approach is centered around JSON things. Each JSON thing comprises one or
33more JSON tokens.
34
35An alternative, lower-level approach is in the sibling example/jsonptr program.
36Neither approach is better or worse per se, but when studying this program, be
37aware that there are multiple ways to use Wuffs' JSON decoder.
38
39The two programs, jsonfindptrs and jsonptr, also demonstrate different
40trade-offs with regard to JSON object duplicate keys. The JSON spec permits
41different implementations to allow or reject duplicate keys. It is not always
42clear which approach is safer. Rejecting them is certainly unambiguous, and
43security bugs can lurk in ambiguous corners of a file format, if two different
44implementations both silently accept a file but differ on how to interpret it.
45On the other hand, in the worst case, detecting duplicate keys requires O(N)
46memory, where N is the size of the (potentially untrusted) input.
47
48This program (jsonfindptrs) rejects duplicate keys.
49
50----
51
Nigel Tao50bfab92020-08-05 11:39:09 +100052To run:
Nigel Taod0b16cb2020-03-14 10:15:54 +110053
54$CXX jsonfindptrs.cc && ./a.out < ../../test/data/github-tags.json; rm -f a.out
55
56for a C++ compiler $CXX, such as clang++ or g++.
57*/
58
Nigel Tao721190a2020-04-03 22:25:21 +110059#if defined(__cplusplus) && (__cplusplus < 201103L)
60#error "This C++ program requires -std=c++11 or later"
61#endif
62
Nigel Taocf6c5782020-08-03 23:43:45 +100063#include <stdio.h>
Nigel Tao6b7ce302020-07-07 16:19:46 +100064
Nigel Taod0b16cb2020-03-14 10:15:54 +110065#include <iostream>
66#include <map>
67#include <string>
68#include <vector>
69
70// Wuffs ships as a "single file C library" or "header file library" as per
71// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
72//
73// To use that single file as a "foo.c"-like implementation, instead of a
74// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
75// compiling it.
76#define WUFFS_IMPLEMENTATION
77
78// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
79// release/c/etc.c whitelist which parts of Wuffs to build. That file contains
80// the entire Wuffs standard library, implementing a variety of codecs and file
81// formats. Without this macro definition, an optimizing compiler or linker may
82// very well discard Wuffs code for unused codecs, but listing the Wuffs
83// modules we use makes that process explicit. Preprocessing means that such
84// code simply isn't compiled.
85#define WUFFS_CONFIG__MODULES
Nigel Taocf6c5782020-08-03 23:43:45 +100086#define WUFFS_CONFIG__MODULE__AUX__BASE
87#define WUFFS_CONFIG__MODULE__AUX__JSON
Nigel Taod0b16cb2020-03-14 10:15:54 +110088#define WUFFS_CONFIG__MODULE__BASE
89#define WUFFS_CONFIG__MODULE__JSON
90
91// If building this program in an environment that doesn't easily accommodate
92// relative includes, you can use the script/inline-c-relative-includes.go
93// program to generate a stand-alone C++ file.
94#include "../../release/c/wuffs-unsupported-snapshot.c"
95
96#define TRY(error_msg) \
97 do { \
98 std::string z = error_msg; \
99 if (!z.empty()) { \
100 return z; \
101 } \
102 } while (false)
103
Nigel Taod60815c2020-03-26 14:32:35 +1100104static const char* g_usage =
Nigel Taod0b16cb2020-03-14 10:15:54 +1100105 "Usage: jsonfindptrs -flags input.json\n"
106 "\n"
107 "Flags:\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100108 " -d=NUM -max-output-depth=NUM\n"
Nigel Tao8aac6762020-08-12 22:47:45 +1000109 " -q=STR -query=STR\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000110 " -input-allow-comments\n"
111 " -input-allow-extra-comma\n"
112 " -input-allow-inf-nan-numbers\n"
Nigel Taoecadf722020-07-13 08:22:34 +1000113 " -strict-json-pointer-syntax\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100114 "\n"
115 "The input.json filename is optional. If absent, it reads from stdin.\n"
116 "\n"
117 "----\n"
118 "\n"
119 "jsonfindptrs reads UTF-8 JSON from stdin and writes every node's JSON\n"
120 "Pointer (RFC 6901) to stdout.\n"
121 "\n"
122 "For example, given RFC 6901 section 5's sample input\n"
123 "(https://tools.ietf.org/rfc/rfc6901.txt), this command:\n"
124 " jsonfindptrs rfc-6901-json-pointer.json\n"
125 "will print:\n"
126 " \n"
127 " /\n"
128 " / \n"
129 " /a~1b\n"
130 " /c%d\n"
131 " /e^f\n"
132 " /foo\n"
133 " /foo/0\n"
134 " /foo/1\n"
135 " /g|h\n"
136 " /i\\j\n"
137 " /k\"l\n"
138 " /m~0n\n"
139 "\n"
140 "The first three lines are (1) a 0-byte \"\", (2) a 1-byte \"/\" and (3)\n"
141 "a 2-byte \"/ \". Unlike a file system, the \"/\" JSON Pointer does not\n"
142 "identify the root. Instead, \"\" is the root and \"/\" is the child (the\n"
143 "value in a key-value pair) of the root whose key is the empty string.\n"
144 "Similarly, \"/xyz\" and \"/xyz/\" are two different nodes.\n"
145 "\n"
146 "----\n"
147 "\n"
148 "The JSON specification (https://json.org/) permits implementations that\n"
149 "allow duplicate keys, but this one does not. Conversely, it prints keys\n"
150 "in sorted order, but the overall output is not necessarily sorted\n"
151 "lexicographically. For example, \"/a/9\" would come before \"/a/10\",\n"
152 "and \"/b/c\", a child of \"/b\", would come before \"/b+\".\n"
153 "\n"
154 "This JSON implementation also rejects integer values outside ±M, where\n"
155 "M is ((1<<53)-1), also known as JavaScript's Number.MAX_SAFE_INTEGER.\n"
156 "\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000157 "The -input-allow-comments flag allows \"/*slash-star*/\" and\n"
158 "\"//slash-slash\" C-style comments within JSON input.\n"
159 "\n"
160 "The -input-allow-extra-comma flag allows input like \"[1,2,]\", with a\n"
Nigel Taoc766bb72020-07-09 12:59:32 +1000161 "comma after the final element of a JSON list or dictionary.\n"
162 "\n"
Nigel Tao0a5940d2020-08-07 13:15:41 +1000163 "The -input-allow-inf-nan-numbers flag allows non-finite floating point\n"
164 "numbers (infinities and not-a-numbers) within JSON input.\n"
165 "\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100166 "----\n"
167 "\n"
Nigel Taoecadf722020-07-13 08:22:34 +1000168 "The -strict-json-pointer-syntax flag restricts the output lines to\n"
169 "exactly RFC 6901, with only two escape sequences: \"~0\" and \"~1\" for\n"
170 "\"~\" and \"/\". Without this flag, this program also lets \"~n\" and\n"
171 "\"~r\" escape the New Line and Carriage Return ASCII control characters,\n"
172 "which can work better with line oriented Unix tools that assume exactly\n"
173 "one value (i.e. one JSON Pointer string) per line. With this flag, it\n"
174 "fails if the input JSON's keys contain \"\\u000A\" or \"\\u000D\".\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100175 "\n"
176 "----\n"
177 "\n"
178 "The JSON specification permits implementations to set their own maximum\n"
179 "input depth. This JSON implementation sets it to 1024.\n"
180 "\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100181 "The -d=NUM or -max-output-depth=NUM flag gives the maximum (inclusive)\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100182 "output depth. JSON containers ([] arrays and {} objects) can hold other\n"
Nigel Tao94440cf2020-04-02 22:28:24 +1100183 "containers. A bare -d or -max-output-depth is equivalent to -d=1,\n"
Nigel Taod0b16cb2020-03-14 10:15:54 +1100184 "analogous to the Unix ls command. The flag's absence is equivalent to an\n"
185 "unlimited output depth, analogous to the Unix find command (and hence\n"
186 "the name of this program: jsonfindptrs).";
187
188// ----
189
Nigel Taocf6c5782020-08-03 23:43:45 +1000190std::vector<uint32_t> g_quirks;
191
Nigel Taod0b16cb2020-03-14 10:15:54 +1100192struct {
193 int remaining_argc;
194 char** remaining_argv;
195
Nigel Taod0b16cb2020-03-14 10:15:54 +1100196 bool strict_json_pointer_syntax;
Nigel Tao0a0c7d62020-08-18 23:31:27 +1000197
198 uint32_t max_output_depth;
199
200 char* query_c_string;
Nigel Taod60815c2020-03-26 14:32:35 +1100201} g_flags = {0};
Nigel Taod0b16cb2020-03-14 10:15:54 +1100202
203std::string //
204parse_flags(int argc, char** argv) {
Nigel Taod60815c2020-03-26 14:32:35 +1100205 g_flags.max_output_depth = 0xFFFFFFFF;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100206
207 int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name.
208 for (; c < argc; c++) {
209 char* arg = argv[c];
210 if (*arg++ != '-') {
211 break;
212 }
213
214 // A double-dash "--foo" is equivalent to a single-dash "-foo". As special
215 // cases, a bare "-" is not a flag (some programs may interpret it as
216 // stdin) and a bare "--" means to stop parsing flags.
217 if (*arg == '\x00') {
218 break;
219 } else if (*arg == '-') {
220 arg++;
221 if (*arg == '\x00') {
222 c++;
223 break;
224 }
225 }
226
Nigel Tao94440cf2020-04-02 22:28:24 +1100227 if (!strcmp(arg, "d") || !strcmp(arg, "max-output-depth")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100228 g_flags.max_output_depth = 1;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100229 continue;
Nigel Tao94440cf2020-04-02 22:28:24 +1100230 } else if (!strncmp(arg, "d=", 2) ||
Nigel Taod0b16cb2020-03-14 10:15:54 +1100231 !strncmp(arg, "max-output-depth=", 16)) {
232 while (*arg++ != '=') {
233 }
234 wuffs_base__result_u64 u = wuffs_base__parse_number_u64(
Nigel Tao6b7ce302020-07-07 16:19:46 +1000235 wuffs_base__make_slice_u8((uint8_t*)arg, strlen(arg)),
236 WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100237 if (wuffs_base__status__is_ok(&u.status) && (u.value <= 0xFFFFFFFF)) {
Nigel Taod60815c2020-03-26 14:32:35 +1100238 g_flags.max_output_depth = (uint32_t)(u.value);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100239 continue;
240 }
Nigel Taod60815c2020-03-26 14:32:35 +1100241 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100242 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000243 if (!strcmp(arg, "input-allow-comments")) {
244 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_BLOCK);
245 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_LINE);
246 continue;
247 }
248 if (!strcmp(arg, "input-allow-extra-comma")) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000249 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_EXTRA_COMMA);
Nigel Taoc766bb72020-07-09 12:59:32 +1000250 continue;
251 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000252 if (!strcmp(arg, "input-allow-inf-nan-numbers")) {
253 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS);
254 continue;
255 }
Nigel Tao8aac6762020-08-12 22:47:45 +1000256 if (!strncmp(arg, "q=", 2) || !strncmp(arg, "query=", 6)) {
257 while (*arg++ != '=') {
258 }
259 g_flags.query_c_string = arg;
260 continue;
261 }
Nigel Taoecadf722020-07-13 08:22:34 +1000262 if (!strcmp(arg, "strict-json-pointer-syntax")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100263 g_flags.strict_json_pointer_syntax = true;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100264 continue;
265 }
266
Nigel Taod60815c2020-03-26 14:32:35 +1100267 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100268 }
269
Nigel Taod60815c2020-03-26 14:32:35 +1100270 g_flags.remaining_argc = argc - c;
271 g_flags.remaining_argv = argv + c;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100272 return "";
273}
274
Nigel Tao6b7ce302020-07-07 16:19:46 +1000275// ----
Nigel Taod0b16cb2020-03-14 10:15:54 +1100276
Nigel Taod0b16cb2020-03-14 10:15:54 +1100277class JsonThing {
278 public:
Nigel Taod0b16cb2020-03-14 10:15:54 +1100279 using Vector = std::vector<JsonThing>;
280
281 // We use a std::map in this example program to avoid dependencies outside of
282 // the C++ standard library. If you're copy/pasting this JsonThing code,
283 // consider a more efficient data structure such as an absl::btree_map.
284 //
285 // See CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance
286 // with Data Structures" at https://www.youtube.com/watch?v=fHNmRkzxHWs
287 using Map = std::map<std::string, JsonThing>;
288
289 enum class Kind {
290 Null,
291 Bool,
292 Int64,
293 Float64,
294 String,
295 Array,
296 Object,
297 } kind = Kind::Null;
298
299 struct Value {
300 bool b = false;
301 int64_t i = 0;
302 double f = 0;
303 std::string s;
304 Vector a;
305 Map o;
306 } value;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100307};
308
Nigel Taod0b16cb2020-03-14 10:15:54 +1100309// ----
310
311std::string //
312escape(std::string s) {
313 for (char& c : s) {
314 if ((c == '~') || (c == '/') || (c == '\n') || (c == '\r')) {
315 goto escape_needed;
316 }
317 }
318 return s;
319
320escape_needed:
321 std::string e;
322 e.reserve(8 + s.length());
323 for (char& c : s) {
324 switch (c) {
325 case '~':
326 e += "~0";
327 break;
328 case '/':
329 e += "~1";
330 break;
331 case '\n':
Nigel Taod60815c2020-03-26 14:32:35 +1100332 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100333 return "";
334 }
335 e += "~n";
336 break;
337 case '\r':
Nigel Taod60815c2020-03-26 14:32:35 +1100338 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100339 return "";
340 }
341 e += "~r";
342 break;
343 default:
344 e += c;
345 break;
346 }
347 }
348 return e;
349}
350
351std::string //
352print_json_pointers(JsonThing& jt, std::string s, uint32_t depth) {
Nigel Taofeded882020-08-29 10:32:12 +1000353 std::cout << s << '\n';
Nigel Taod60815c2020-03-26 14:32:35 +1100354 if (depth++ >= g_flags.max_output_depth) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100355 return "";
356 }
357
358 switch (jt.kind) {
359 case JsonThing::Kind::Array:
360 s += "/";
361 for (size_t i = 0; i < jt.value.a.size(); i++) {
362 TRY(print_json_pointers(jt.value.a[i], s + std::to_string(i), depth));
363 }
364 break;
365 case JsonThing::Kind::Object:
366 s += "/";
367 for (auto& kv : jt.value.o) {
368 std::string e = escape(kv.first);
369 if (e.empty() && !kv.first.empty()) {
370 return "main: unsupported \"\\u000A\" or \"\\u000D\" in object key";
371 }
372 TRY(print_json_pointers(kv.second, s + e, depth));
373 }
374 break;
Nigel Tao18ef5b42020-03-16 10:37:47 +1100375 default:
376 break;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100377 }
378 return "";
379}
380
Nigel Taocf6c5782020-08-03 23:43:45 +1000381// ----
382
383class Callbacks : public wuffs_aux::DecodeJsonCallbacks {
384 public:
385 struct Entry {
386 Entry(JsonThing&& jt)
387 : thing(std::move(jt)), has_map_key(false), map_key() {}
388
389 JsonThing thing;
390 bool has_map_key;
391 std::string map_key;
392 };
393
394 Callbacks() = default;
395
396 std::string Append(JsonThing&& jt) {
397 if (m_stack.empty()) {
398 m_stack.push_back(Entry(std::move(jt)));
399 return "";
400 }
401 Entry& top = m_stack.back();
402 switch (top.thing.kind) {
403 case JsonThing::Kind::Array:
404 top.thing.value.a.push_back(std::move(jt));
405 return "";
406 case JsonThing::Kind::Object:
407 if (top.has_map_key) {
408 top.has_map_key = false;
409 auto iter = top.thing.value.o.find(top.map_key);
410 if (iter != top.thing.value.o.end()) {
411 return "main: duplicate key: " + top.map_key;
412 }
413 top.thing.value.o.insert(
414 iter, JsonThing::Map::value_type(std::move(top.map_key),
415 std::move(jt)));
416 return "";
417 } else if (jt.kind == JsonThing::Kind::String) {
418 top.has_map_key = true;
419 top.map_key = std::move(jt.value.s);
420 return "";
421 }
422 return "main: internal error: non-string map key";
Nigel Tao7fd3bb62020-08-24 21:34:53 +1000423 default:
424 return "main: internal error: non-container stack entry";
Nigel Taocf6c5782020-08-03 23:43:45 +1000425 }
Nigel Taocf6c5782020-08-03 23:43:45 +1000426 }
427
Nigel Taoca5da1f2020-08-10 15:26:29 +1000428 std::string AppendNull() override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000429 JsonThing jt;
430 jt.kind = JsonThing::Kind::Null;
431 return Append(std::move(jt));
432 }
433
Nigel Taoca5da1f2020-08-10 15:26:29 +1000434 std::string AppendBool(bool val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000435 JsonThing jt;
436 jt.kind = JsonThing::Kind::Bool;
437 jt.value.b = val;
438 return Append(std::move(jt));
439 }
440
Nigel Taoca5da1f2020-08-10 15:26:29 +1000441 std::string AppendI64(int64_t val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000442 JsonThing jt;
443 jt.kind = JsonThing::Kind::Int64;
444 jt.value.i = val;
445 return Append(std::move(jt));
446 }
447
Nigel Taoca5da1f2020-08-10 15:26:29 +1000448 std::string AppendF64(double val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000449 JsonThing jt;
450 jt.kind = JsonThing::Kind::Float64;
451 jt.value.f = val;
452 return Append(std::move(jt));
453 }
454
Nigel Taoca5da1f2020-08-10 15:26:29 +1000455 std::string AppendTextString(std::string&& val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000456 JsonThing jt;
457 jt.kind = JsonThing::Kind::String;
458 jt.value.s = std::move(val);
459 return Append(std::move(jt));
460 }
461
Nigel Taoca5da1f2020-08-10 15:26:29 +1000462 std::string Push(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000463 if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
464 JsonThing jt;
465 jt.kind = JsonThing::Kind::Array;
466 m_stack.push_back(std::move(jt));
467 return "";
468 } else if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
469 JsonThing jt;
470 jt.kind = JsonThing::Kind::Object;
471 m_stack.push_back(std::move(jt));
472 return "";
473 }
474 return "main: internal error: bad push";
475 }
476
Nigel Taoca5da1f2020-08-10 15:26:29 +1000477 std::string Pop(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000478 if (m_stack.empty()) {
479 return "main: internal error: bad pop";
480 }
481 JsonThing jt = std::move(m_stack.back().thing);
482 m_stack.pop_back();
483 return Append(std::move(jt));
484 }
485
Nigel Taoca5da1f2020-08-10 15:26:29 +1000486 void Done(wuffs_aux::DecodeJsonResult& result,
487 wuffs_aux::sync_io::Input& input,
488 wuffs_aux::IOBuffer& buffer) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000489 if (!result.error_message.empty()) {
490 return;
491 } else if (m_stack.size() != 1) {
492 result.error_message = "main: internal error: bad depth";
493 return;
494 }
495 result.error_message = print_json_pointers(m_stack.back().thing, "", 0);
496 }
497
498 private:
499 std::vector<Entry> m_stack;
500};
501
502// ----
503
Nigel Taod0b16cb2020-03-14 10:15:54 +1100504std::string //
505main1(int argc, char** argv) {
506 TRY(parse_flags(argc, argv));
Nigel Taob3438432020-08-13 00:06:56 +1000507 if (!g_flags.strict_json_pointer_syntax) {
508 g_quirks.push_back(WUFFS_JSON__QUIRK_JSON_POINTER_ALLOW_TILDE_R_TILDE_N);
509 }
Nigel Taod0b16cb2020-03-14 10:15:54 +1100510
Nigel Taocf6c5782020-08-03 23:43:45 +1000511 FILE* in = stdin;
Nigel Taod60815c2020-03-26 14:32:35 +1100512 if (g_flags.remaining_argc > 1) {
513 return g_usage;
514 } else if (g_flags.remaining_argc == 1) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000515 in = fopen(g_flags.remaining_argv[0], "r");
516 if (!in) {
517 return std::string("main: cannot read input file");
Nigel Taod0b16cb2020-03-14 10:15:54 +1100518 }
519 }
520
Nigel Tao2742a4f2020-08-17 00:02:49 +1000521 Callbacks callbacks;
522 wuffs_aux::sync_io::FileInput input(in);
Nigel Taocf6c5782020-08-03 23:43:45 +1000523 return wuffs_aux::DecodeJson(
Nigel Tao2742a4f2020-08-17 00:02:49 +1000524 callbacks, input,
Nigel Tao8aac6762020-08-12 22:47:45 +1000525 wuffs_base__make_slice_u32(g_quirks.data(), g_quirks.size()),
526 (g_flags.query_c_string ? g_flags.query_c_string : ""))
Nigel Taocf6c5782020-08-03 23:43:45 +1000527 .error_message;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100528}
529
530// ----
531
532int //
533compute_exit_code(std::string status_msg) {
534 if (status_msg.empty()) {
535 return 0;
536 }
Nigel Taofeded882020-08-29 10:32:12 +1000537 std::cerr << status_msg << '\n';
Nigel Taod0b16cb2020-03-14 10:15:54 +1100538 // Return an exit code of 1 for regular (forseen) errors, e.g. badly
539 // formatted or unsupported input.
540 //
541 // Return an exit code of 2 for internal (exceptional) errors, e.g. defensive
542 // run-time checks found that an internal invariant did not hold.
543 //
544 // Automated testing, including badly formatted inputs, can therefore
545 // discriminate between expected failure (exit code 1) and unexpected failure
546 // (other non-zero exit codes). Specifically, exit code 2 for internal
547 // invariant violation, exit code 139 (which is 128 + SIGSEGV on x86_64
548 // linux) for a segmentation fault (e.g. null pointer dereference).
549 return (status_msg.find("internal error:") != std::string::npos) ? 2 : 1;
550}
551
552int //
553main(int argc, char** argv) {
554 std::string z = main1(argc, argv);
555 int exit_code = compute_exit_code(z);
556 return exit_code;
557}