blob: a9fe5de60ddbf544448c0cb117950bbbb35f51ab [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
196 uint32_t max_output_depth;
Nigel Tao8aac6762020-08-12 22:47:45 +1000197 char* query_c_string;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100198 bool strict_json_pointer_syntax;
Nigel Taod60815c2020-03-26 14:32:35 +1100199} g_flags = {0};
Nigel Taod0b16cb2020-03-14 10:15:54 +1100200
201std::string //
202parse_flags(int argc, char** argv) {
Nigel Taod60815c2020-03-26 14:32:35 +1100203 g_flags.max_output_depth = 0xFFFFFFFF;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100204
205 int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name.
206 for (; c < argc; c++) {
207 char* arg = argv[c];
208 if (*arg++ != '-') {
209 break;
210 }
211
212 // A double-dash "--foo" is equivalent to a single-dash "-foo". As special
213 // cases, a bare "-" is not a flag (some programs may interpret it as
214 // stdin) and a bare "--" means to stop parsing flags.
215 if (*arg == '\x00') {
216 break;
217 } else if (*arg == '-') {
218 arg++;
219 if (*arg == '\x00') {
220 c++;
221 break;
222 }
223 }
224
Nigel Tao94440cf2020-04-02 22:28:24 +1100225 if (!strcmp(arg, "d") || !strcmp(arg, "max-output-depth")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100226 g_flags.max_output_depth = 1;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100227 continue;
Nigel Tao94440cf2020-04-02 22:28:24 +1100228 } else if (!strncmp(arg, "d=", 2) ||
Nigel Taod0b16cb2020-03-14 10:15:54 +1100229 !strncmp(arg, "max-output-depth=", 16)) {
230 while (*arg++ != '=') {
231 }
232 wuffs_base__result_u64 u = wuffs_base__parse_number_u64(
Nigel Tao6b7ce302020-07-07 16:19:46 +1000233 wuffs_base__make_slice_u8((uint8_t*)arg, strlen(arg)),
234 WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100235 if (wuffs_base__status__is_ok(&u.status) && (u.value <= 0xFFFFFFFF)) {
Nigel Taod60815c2020-03-26 14:32:35 +1100236 g_flags.max_output_depth = (uint32_t)(u.value);
Nigel Taod0b16cb2020-03-14 10:15:54 +1100237 continue;
238 }
Nigel Taod60815c2020-03-26 14:32:35 +1100239 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100240 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000241 if (!strcmp(arg, "input-allow-comments")) {
242 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_BLOCK);
243 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_COMMENT_LINE);
244 continue;
245 }
246 if (!strcmp(arg, "input-allow-extra-comma")) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000247 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_EXTRA_COMMA);
Nigel Taoc766bb72020-07-09 12:59:32 +1000248 continue;
249 }
Nigel Tao0a5940d2020-08-07 13:15:41 +1000250 if (!strcmp(arg, "input-allow-inf-nan-numbers")) {
251 g_quirks.push_back(WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS);
252 continue;
253 }
Nigel Tao8aac6762020-08-12 22:47:45 +1000254 if (!strncmp(arg, "q=", 2) || !strncmp(arg, "query=", 6)) {
255 while (*arg++ != '=') {
256 }
257 g_flags.query_c_string = arg;
258 continue;
259 }
Nigel Taoecadf722020-07-13 08:22:34 +1000260 if (!strcmp(arg, "strict-json-pointer-syntax")) {
Nigel Taod60815c2020-03-26 14:32:35 +1100261 g_flags.strict_json_pointer_syntax = true;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100262 continue;
263 }
264
Nigel Taod60815c2020-03-26 14:32:35 +1100265 return g_usage;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100266 }
267
Nigel Taod60815c2020-03-26 14:32:35 +1100268 g_flags.remaining_argc = argc - c;
269 g_flags.remaining_argv = argv + c;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100270 return "";
271}
272
Nigel Tao6b7ce302020-07-07 16:19:46 +1000273// ----
Nigel Taod0b16cb2020-03-14 10:15:54 +1100274
Nigel Taod0b16cb2020-03-14 10:15:54 +1100275class JsonThing {
276 public:
Nigel Taod0b16cb2020-03-14 10:15:54 +1100277 using Vector = std::vector<JsonThing>;
278
279 // We use a std::map in this example program to avoid dependencies outside of
280 // the C++ standard library. If you're copy/pasting this JsonThing code,
281 // consider a more efficient data structure such as an absl::btree_map.
282 //
283 // See CppCon 2014: Chandler Carruth "Efficiency with Algorithms, Performance
284 // with Data Structures" at https://www.youtube.com/watch?v=fHNmRkzxHWs
285 using Map = std::map<std::string, JsonThing>;
286
287 enum class Kind {
288 Null,
289 Bool,
290 Int64,
291 Float64,
292 String,
293 Array,
294 Object,
295 } kind = Kind::Null;
296
297 struct Value {
298 bool b = false;
299 int64_t i = 0;
300 double f = 0;
301 std::string s;
302 Vector a;
303 Map o;
304 } value;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100305};
306
Nigel Taod0b16cb2020-03-14 10:15:54 +1100307// ----
308
309std::string //
310escape(std::string s) {
311 for (char& c : s) {
312 if ((c == '~') || (c == '/') || (c == '\n') || (c == '\r')) {
313 goto escape_needed;
314 }
315 }
316 return s;
317
318escape_needed:
319 std::string e;
320 e.reserve(8 + s.length());
321 for (char& c : s) {
322 switch (c) {
323 case '~':
324 e += "~0";
325 break;
326 case '/':
327 e += "~1";
328 break;
329 case '\n':
Nigel Taod60815c2020-03-26 14:32:35 +1100330 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100331 return "";
332 }
333 e += "~n";
334 break;
335 case '\r':
Nigel Taod60815c2020-03-26 14:32:35 +1100336 if (g_flags.strict_json_pointer_syntax) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100337 return "";
338 }
339 e += "~r";
340 break;
341 default:
342 e += c;
343 break;
344 }
345 }
346 return e;
347}
348
349std::string //
350print_json_pointers(JsonThing& jt, std::string s, uint32_t depth) {
351 std::cout << s << std::endl;
Nigel Taod60815c2020-03-26 14:32:35 +1100352 if (depth++ >= g_flags.max_output_depth) {
Nigel Taod0b16cb2020-03-14 10:15:54 +1100353 return "";
354 }
355
356 switch (jt.kind) {
357 case JsonThing::Kind::Array:
358 s += "/";
359 for (size_t i = 0; i < jt.value.a.size(); i++) {
360 TRY(print_json_pointers(jt.value.a[i], s + std::to_string(i), depth));
361 }
362 break;
363 case JsonThing::Kind::Object:
364 s += "/";
365 for (auto& kv : jt.value.o) {
366 std::string e = escape(kv.first);
367 if (e.empty() && !kv.first.empty()) {
368 return "main: unsupported \"\\u000A\" or \"\\u000D\" in object key";
369 }
370 TRY(print_json_pointers(kv.second, s + e, depth));
371 }
372 break;
Nigel Tao18ef5b42020-03-16 10:37:47 +1100373 default:
374 break;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100375 }
376 return "";
377}
378
Nigel Taocf6c5782020-08-03 23:43:45 +1000379// ----
380
381class Callbacks : public wuffs_aux::DecodeJsonCallbacks {
382 public:
383 struct Entry {
384 Entry(JsonThing&& jt)
385 : thing(std::move(jt)), has_map_key(false), map_key() {}
386
387 JsonThing thing;
388 bool has_map_key;
389 std::string map_key;
390 };
391
392 Callbacks() = default;
393
394 std::string Append(JsonThing&& jt) {
395 if (m_stack.empty()) {
396 m_stack.push_back(Entry(std::move(jt)));
397 return "";
398 }
399 Entry& top = m_stack.back();
400 switch (top.thing.kind) {
401 case JsonThing::Kind::Array:
402 top.thing.value.a.push_back(std::move(jt));
403 return "";
404 case JsonThing::Kind::Object:
405 if (top.has_map_key) {
406 top.has_map_key = false;
407 auto iter = top.thing.value.o.find(top.map_key);
408 if (iter != top.thing.value.o.end()) {
409 return "main: duplicate key: " + top.map_key;
410 }
411 top.thing.value.o.insert(
412 iter, JsonThing::Map::value_type(std::move(top.map_key),
413 std::move(jt)));
414 return "";
415 } else if (jt.kind == JsonThing::Kind::String) {
416 top.has_map_key = true;
417 top.map_key = std::move(jt.value.s);
418 return "";
419 }
420 return "main: internal error: non-string map key";
421 }
422 return "main: internal error: non-container stack entry";
423 }
424
Nigel Taoca5da1f2020-08-10 15:26:29 +1000425 std::string AppendNull() override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000426 JsonThing jt;
427 jt.kind = JsonThing::Kind::Null;
428 return Append(std::move(jt));
429 }
430
Nigel Taoca5da1f2020-08-10 15:26:29 +1000431 std::string AppendBool(bool val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000432 JsonThing jt;
433 jt.kind = JsonThing::Kind::Bool;
434 jt.value.b = val;
435 return Append(std::move(jt));
436 }
437
Nigel Taoca5da1f2020-08-10 15:26:29 +1000438 std::string AppendI64(int64_t val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000439 JsonThing jt;
440 jt.kind = JsonThing::Kind::Int64;
441 jt.value.i = val;
442 return Append(std::move(jt));
443 }
444
Nigel Taoca5da1f2020-08-10 15:26:29 +1000445 std::string AppendF64(double val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000446 JsonThing jt;
447 jt.kind = JsonThing::Kind::Float64;
448 jt.value.f = val;
449 return Append(std::move(jt));
450 }
451
Nigel Taoca5da1f2020-08-10 15:26:29 +1000452 std::string AppendTextString(std::string&& val) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000453 JsonThing jt;
454 jt.kind = JsonThing::Kind::String;
455 jt.value.s = std::move(val);
456 return Append(std::move(jt));
457 }
458
Nigel Taoca5da1f2020-08-10 15:26:29 +1000459 std::string Push(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000460 if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) {
461 JsonThing jt;
462 jt.kind = JsonThing::Kind::Array;
463 m_stack.push_back(std::move(jt));
464 return "";
465 } else if (flags & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT) {
466 JsonThing jt;
467 jt.kind = JsonThing::Kind::Object;
468 m_stack.push_back(std::move(jt));
469 return "";
470 }
471 return "main: internal error: bad push";
472 }
473
Nigel Taoca5da1f2020-08-10 15:26:29 +1000474 std::string Pop(uint32_t flags) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000475 if (m_stack.empty()) {
476 return "main: internal error: bad pop";
477 }
478 JsonThing jt = std::move(m_stack.back().thing);
479 m_stack.pop_back();
480 return Append(std::move(jt));
481 }
482
Nigel Taoca5da1f2020-08-10 15:26:29 +1000483 void Done(wuffs_aux::DecodeJsonResult& result,
484 wuffs_aux::sync_io::Input& input,
485 wuffs_aux::IOBuffer& buffer) override {
Nigel Taocf6c5782020-08-03 23:43:45 +1000486 if (!result.error_message.empty()) {
487 return;
488 } else if (m_stack.size() != 1) {
489 result.error_message = "main: internal error: bad depth";
490 return;
491 }
492 result.error_message = print_json_pointers(m_stack.back().thing, "", 0);
493 }
494
495 private:
496 std::vector<Entry> m_stack;
497};
498
499// ----
500
Nigel Taod0b16cb2020-03-14 10:15:54 +1100501std::string //
502main1(int argc, char** argv) {
503 TRY(parse_flags(argc, argv));
Nigel Taob3438432020-08-13 00:06:56 +1000504 if (!g_flags.strict_json_pointer_syntax) {
505 g_quirks.push_back(WUFFS_JSON__QUIRK_JSON_POINTER_ALLOW_TILDE_R_TILDE_N);
506 }
Nigel Taod0b16cb2020-03-14 10:15:54 +1100507
Nigel Taocf6c5782020-08-03 23:43:45 +1000508 FILE* in = stdin;
Nigel Taod60815c2020-03-26 14:32:35 +1100509 if (g_flags.remaining_argc > 1) {
510 return g_usage;
511 } else if (g_flags.remaining_argc == 1) {
Nigel Taocf6c5782020-08-03 23:43:45 +1000512 in = fopen(g_flags.remaining_argv[0], "r");
513 if (!in) {
514 return std::string("main: cannot read input file");
Nigel Taod0b16cb2020-03-14 10:15:54 +1100515 }
516 }
517
Nigel Tao2742a4f2020-08-17 00:02:49 +1000518 Callbacks callbacks;
519 wuffs_aux::sync_io::FileInput input(in);
Nigel Taocf6c5782020-08-03 23:43:45 +1000520 return wuffs_aux::DecodeJson(
Nigel Tao2742a4f2020-08-17 00:02:49 +1000521 callbacks, input,
Nigel Tao8aac6762020-08-12 22:47:45 +1000522 wuffs_base__make_slice_u32(g_quirks.data(), g_quirks.size()),
523 (g_flags.query_c_string ? g_flags.query_c_string : ""))
Nigel Taocf6c5782020-08-03 23:43:45 +1000524 .error_message;
Nigel Taod0b16cb2020-03-14 10:15:54 +1100525}
526
527// ----
528
529int //
530compute_exit_code(std::string status_msg) {
531 if (status_msg.empty()) {
532 return 0;
533 }
534 std::cerr << status_msg << std::endl;
535 // Return an exit code of 1 for regular (forseen) errors, e.g. badly
536 // formatted or unsupported input.
537 //
538 // Return an exit code of 2 for internal (exceptional) errors, e.g. defensive
539 // run-time checks found that an internal invariant did not hold.
540 //
541 // Automated testing, including badly formatted inputs, can therefore
542 // discriminate between expected failure (exit code 1) and unexpected failure
543 // (other non-zero exit codes). Specifically, exit code 2 for internal
544 // invariant violation, exit code 139 (which is 128 + SIGSEGV on x86_64
545 // linux) for a segmentation fault (e.g. null pointer dereference).
546 return (status_msg.find("internal error:") != std::string::npos) ? 2 : 1;
547}
548
549int //
550main(int argc, char** argv) {
551 std::string z = main1(argc, argv);
552 int exit_code = compute_exit_code(z);
553 return exit_code;
554}