xrpld
Loading...
Searching...
No Matches
json_reader.cpp
1#include <xrpl/json/json_reader.h>
2
3#include <xrpl/basics/contract.h>
4#include <xrpl/json/json_value.h>
5
6#include <fast_float/fast_float.h> // IWYU pragma: keep
7#include <fast_float/parse_number.h>
8
9#include <algorithm>
10#include <cctype>
11#include <cstdint>
12#include <cstdio>
13#include <istream>
14#include <stdexcept>
15#include <string>
16#include <system_error>
17
18namespace json {
19// Implementation of class Reader
20// ////////////////////////////////
21
22static std::string
23codePointToUTF8(unsigned int cp)
24{
25 std::string result;
26
27 // based on description from http://en.wikipedia.org/wiki/UTF-8
28
29 if (cp <= 0x7f)
30 {
31 result.resize(1);
32 result[0] = static_cast<char>(cp);
33 }
34 else if (cp <= 0x7FF)
35 {
36 result.resize(2);
37 result[1] = static_cast<char>(0x80 | (0x3f & cp));
38 result[0] = static_cast<char>(0xC0 | (0x1f & (cp >> 6)));
39 }
40 else if (cp <= 0xFFFF)
41 {
42 result.resize(3);
43 result[2] = static_cast<char>(0x80 | (0x3f & cp));
44 result[1] = 0x80 | static_cast<char>((0x3f & (cp >> 6)));
45 result[0] = 0xE0 | static_cast<char>((0xf & (cp >> 12)));
46 }
47 else if (cp <= 0x10FFFF)
48 {
49 result.resize(4);
50 result[3] = static_cast<char>(0x80 | (0x3f & cp));
51 result[2] = static_cast<char>(0x80 | (0x3f & (cp >> 6)));
52 result[1] = static_cast<char>(0x80 | (0x3f & (cp >> 12)));
53 result[0] = static_cast<char>(0xF0 | (0x7 & (cp >> 18)));
54 }
55
56 return result;
57}
58
59// Class Reader
60// //////////////////////////////////////////////////////////////////
61
62bool
63Reader::parse(std::string const& document, Value& root)
64{
65 document_ = document;
66 char const* begin = document_.c_str();
67 char const* end = begin + document_.length();
68 return parse(begin, end, root);
69}
70
71bool
73{
74 // std::istream_iterator<char> begin(sin);
75 // std::istream_iterator<char> end;
76 // Those would allow streamed input from a file, if parse() were a
77 // template function.
78
79 // Since std::string is reference-counted, this at least does not
80 // create an extra copy.
81 std::string doc;
82 std::getline(sin, doc, (char)EOF);
83 return parse(doc, root);
84}
85
86bool
87Reader::parse(char const* beginDoc, char const* endDoc, Value& root)
88{
89 begin_ = beginDoc;
90 end_ = endDoc;
92 lastValueEnd_ = nullptr;
93 lastValue_ = nullptr;
94 errors_.clear();
95
96 while (!nodes_.empty())
97 nodes_.pop();
98
99 nodes_.push(&root);
100 bool const successful = readValue(0);
101 Token token{};
102 skipCommentTokens(token);
103
104 if (!root.isNull() && !root.isArray() && !root.isObject())
105 {
106 // Set error location to start of doc, ideally should be first token
107 // found in doc
108 token.type = TokenType::Error;
109 token.start = beginDoc;
110 token.end = endDoc;
111 addError("A valid JSON document must be either an array or an object value.", token);
112 return false;
113 }
114
115 return successful;
116}
117
118bool
119Reader::readValue(unsigned depth)
120{
121 Token token{};
122 skipCommentTokens(token);
123 if (depth > kNestLimit)
124 return addError("Syntax error: maximum nesting depth exceeded", token);
125 bool successful = true;
126
127 switch (token.type)
128 {
130 successful = readObject(token, depth);
131 break;
132
134 successful = readArray(token, depth);
135 break;
136
138 successful = decodeNumber(token);
139 break;
140
142 successful = decodeDouble(token);
143 break;
144
146 successful = decodeString(token);
147 break;
148
149 case TokenType::True:
150 currentValue() = true;
151 break;
152
153 case TokenType::False:
154 currentValue() = false;
155 break;
156
157 case TokenType::Null:
158 currentValue() = Value();
159 break;
160
161 default:
162 return addError("Syntax error: value, object or array expected.", token);
163 }
164
165 return successful;
166}
167
168void
170{
171 do
172 {
173 readToken(token);
174 } while (token.type == TokenType::Comment);
175}
176
177bool
178Reader::expectToken(TokenType type, Token& token, char const* message)
179{
180 readToken(token);
181
182 if (token.type != type)
183 return addError(message, token);
184
185 return true;
186}
187
188bool
190{
191 skipSpaces();
192 token.start = current_;
193 Char const c = getNextChar();
194 bool ok = true;
195
196 switch (c)
197 {
198 case '{':
200 break;
201
202 case '}':
204 break;
205
206 case '[':
208 break;
209
210 case ']':
212 break;
213
214 case '"':
215 token.type = TokenType::String;
216 ok = readString();
217 break;
218
219 case '/':
220 token.type = TokenType::Comment;
221 ok = readComment();
222 break;
223
224 case '0':
225 case '1':
226 case '2':
227 case '3':
228 case '4':
229 case '5':
230 case '6':
231 case '7':
232 case '8':
233 case '9':
234 case '-':
235 token.type = readNumber();
236 break;
237
238 case 't':
239 token.type = TokenType::True;
240 ok = match("rue", 3);
241 break;
242
243 case 'f':
244 token.type = TokenType::False;
245 ok = match("alse", 4); // cspell:disable-line
246 break;
247
248 case 'n':
249 token.type = TokenType::Null;
250 ok = match("ull", 3);
251 break;
252
253 case ',':
255 break;
256
257 case ':':
259 break;
260
261 case 0:
263 break;
264
265 default:
266 ok = false;
267 break;
268 }
269
270 if (!ok)
271 token.type = TokenType::Error;
272
273 token.end = current_;
274 return true;
275}
276
277void
279{
280 while (current_ != end_)
281 {
282 Char const c = *current_;
283
284 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
285 {
286 ++current_;
287 }
288 else
289 {
290 break;
291 }
292 }
293}
294
295bool
296Reader::match(Location pattern, int patternLength)
297{
298 if (end_ - current_ < patternLength)
299 return false;
300
301 int index = patternLength;
302
303 while ((index--) != 0)
304 {
305 if (current_[index] != pattern[index])
306 return false;
307 }
308
309 current_ += patternLength;
310 return true;
311}
312
313bool
315{
316 Char const c = getNextChar();
317
318 if (c == '*')
319 return readCStyleComment();
320
321 if (c == '/')
322 return readCppStyleComment();
323
324 return false;
325}
326
327bool
329{
330 while (current_ != end_)
331 {
332 Char const c = getNextChar();
333
334 if (c == '*' && *current_ == '/')
335 break;
336 }
337
338 return getNextChar() == '/';
339}
340
341bool
343{
344 while (current_ != end_)
345 {
346 Char const c = getNextChar();
347
348 if (c == '\r' || c == '\n')
349 break;
350 }
351
352 return true;
353}
354
357{
358 static char const kExtendedTokens[] = {'.', 'e', 'E', '+', '-'};
359
361
362 if (current_ != end_)
363 {
364 if (*current_ == '-')
365 ++current_;
366
367 while (current_ != end_)
368 {
369 if (std::isdigit(static_cast<unsigned char>(*current_)) == 0)
370 {
371 auto ret = std::ranges::find(kExtendedTokens, *current_);
372
373 if (ret == std::end(kExtendedTokens))
374 break;
375
376 type = TokenType::Double;
377 }
378
379 ++current_;
380 }
381 }
382
383 return type;
384}
385
386bool
388{
389 Char c = 0;
390
391 while (current_ != end_)
392 {
393 c = getNextChar();
394
395 if (c == '\\')
396 {
397 getNextChar();
398 }
399 else if (c == '"')
400 {
401 break;
402 }
403 }
404
405 return c == '"';
406}
407
408bool
409Reader::readObject(Token& tokenStart, unsigned depth)
410{
411 Token tokenName{};
412 std::string name;
414
415 while (readToken(tokenName))
416 {
417 bool initialTokenOk = true;
418
419 while (tokenName.type == TokenType::Comment && initialTokenOk)
420 initialTokenOk = readToken(tokenName);
421
422 if (!initialTokenOk)
423 break;
424
425 if (tokenName.type == TokenType::ObjectEnd && name.empty()) // empty object
426 return true;
427
428 if (tokenName.type != TokenType::String)
429 break;
430
431 name = "";
432
433 if (!decodeString(tokenName, name))
435
436 Token colon{};
437
438 if (!readToken(colon) || colon.type != TokenType::MemberSeparator)
439 {
440 return addErrorAndRecover(
441 "Missing ':' after object member name", colon, TokenType::ObjectEnd);
442 }
443
444 // Reject duplicate names
445 if (currentValue().isMember(name))
446 return addError("Key '" + name + "' appears twice.", tokenName);
447
448 Value& value = currentValue()[name];
449 nodes_.push(&value);
450 bool const ok = readValue(depth + 1);
451 nodes_.pop();
452
453 if (!ok) // error already set
455
456 Token comma{};
457
458 if (!readToken(comma) ||
460 comma.type != TokenType::Comment))
461 {
462 return addErrorAndRecover(
463 "Missing ',' or '}' in object declaration", comma, TokenType::ObjectEnd);
464 }
465
466 bool finalizeTokenOk = true;
467
468 while (comma.type == TokenType::Comment && finalizeTokenOk)
469 finalizeTokenOk = readToken(comma);
470
471 if (comma.type == TokenType::ObjectEnd)
472 return true;
473 }
474
475 return addErrorAndRecover("Missing '}' or object member name", tokenName, TokenType::ObjectEnd);
476}
477
478bool
479Reader::readArray(Token& tokenStart, unsigned depth)
480{
482 skipSpaces();
483
484 if (*current_ == ']') // empty array
485 {
486 Token endArray{};
487 readToken(endArray);
488 return true;
489 }
490
491 int index = 0;
492
493 while (true)
494 {
495 Value& value = currentValue()[index++];
496 nodes_.push(&value);
497 bool ok = readValue(depth + 1);
498 nodes_.pop();
499
500 if (!ok) // error already set
502
503 Token token{};
504 // Accept Comment after last item in the array.
505 ok = readToken(token);
506
507 while (token.type == TokenType::Comment && ok)
508 {
509 ok = readToken(token);
510 }
511
512 bool const badTokenType =
514
515 if (!ok || badTokenType)
516 {
517 return addErrorAndRecover(
518 "Missing ',' or ']' in array declaration", token, TokenType::ArrayEnd);
519 }
520
521 if (token.type == TokenType::ArrayEnd)
522 break;
523 }
524
525 return true;
526}
527
528bool
530{
531 Location current = token.start;
532 bool const isNegative = *current == '-';
533
534 if (isNegative)
535 ++current;
536
537 if (current == token.end)
538 {
539 return addError(
540 "'" + std::string(token.start, token.end) + "' is not a valid number.", token);
541 }
542
543 // The existing Json integers are 32-bit so using a 64-bit value here avoids
544 // overflows in the conversion code below.
545 std::int64_t value = 0;
546
547 static_assert(
548 sizeof(value) > sizeof(Value::kMaxUInt),
549 "The JSON integer overflow logic will need to be reworked.");
550
551 while (current < token.end && (value <= Value::kMaxUInt))
552 {
553 Char const c = *current++;
554
555 if (c < '0' || c > '9')
556 {
557 return addError(
558 "'" + std::string(token.start, token.end) + "' is not a number.", token);
559 }
560
561 value = (value * 10) + (c - '0');
562 }
563
564 // More tokens left -> input is larger than largest possible return value
565 if (current != token.end)
566 {
567 return addError(
568 "'" + std::string(token.start, token.end) + "' exceeds the allowable range.", token);
569 }
570
571 if (isNegative)
572 {
573 value = -value;
574
575 if (value < Value::kMinInt || value > Value::kMaxInt)
576 {
577 return addError(
578 "'" + std::string(token.start, token.end) + "' exceeds the allowable range.",
579 token);
580 }
581
582 currentValue() = static_cast<Value::Int>(value);
583 }
584 else
585 {
586 if (value > Value::kMaxUInt)
587 {
588 return addError(
589 "'" + std::string(token.start, token.end) + "' exceeds the allowable range.",
590 token);
591 }
592
593 // If it's representable as a signed integer, construct it as one.
594 if (value <= Value::kMaxInt)
595 {
596 currentValue() = static_cast<Value::Int>(value);
597 }
598 else
599 {
600 currentValue() = static_cast<Value::UInt>(value);
601 }
602 }
603
604 return true;
605}
606
607bool
609{
610 // Sanity check to avoid buffer overflow exploits.
611 if (token.end < token.start)
612 {
613 return addError("Unable to parse token length", token);
614 }
615
616 double value = 0;
617 auto const [ptr, ec] = fast_float::from_chars(token.start, token.end, value);
618
619 // Reject anything from_chars could not turn into a finite double:
620 // - ec != std::errc{}: no valid conversion, or an out-of-range magnitude
621 // (e.g. 1e400).
622 // - ptr != token.end: readNumber() is permissive about which characters
623 // it collects into a token (it will, for example, keep a '+' mid-token),
624 // but from_chars() will stop at the first character it cannot parse.
625 if (ec != std::errc{} || ptr != token.end)
626 return addError("'" + std::string(token.start, token.end) + "' is not a number.", token);
627
628 currentValue() = value;
629 return true;
630}
631
632bool
634{
635 std::string decoded;
636
637 if (!decodeString(token, decoded))
638 return false;
639
640 currentValue() = decoded;
641 return true;
642}
643
644bool
646{
647 decoded.reserve(token.end - token.start - 2);
648 Location current = token.start + 1; // skip '"'
649 Location end = token.end - 1; // do not include '"'
650
651 while (current != end)
652 {
653 Char const c = *current++;
654
655 if (c == '"')
656 {
657 break;
658 }
659 if (c == '\\')
660 {
661 if (current == end)
662 return addError("Empty escape sequence in string", token, current);
663
664 Char const escape = *current++;
665
666 switch (escape)
667 {
668 case '"':
669 decoded += '"';
670 break;
671
672 case '/':
673 decoded += '/';
674 break;
675
676 case '\\':
677 decoded += '\\';
678 break;
679
680 case 'b':
681 decoded += '\b';
682 break;
683
684 case 'f':
685 decoded += '\f';
686 break;
687
688 case 'n':
689 decoded += '\n';
690 break;
691
692 case 'r':
693 decoded += '\r';
694 break;
695
696 case 't':
697 decoded += '\t';
698 break;
699
700 case 'u': {
701 unsigned int unicode = 0;
702
703 if (!decodeUnicodeCodePoint(token, current, end, unicode))
704 return false;
705
706 decoded += codePointToUTF8(unicode);
707 }
708 break;
709
710 default:
711 return addError("Bad escape sequence in string", token, current);
712 }
713 }
714 else
715 {
716 decoded += c;
717 }
718 }
719
720 return true;
721}
722
723bool
724Reader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, unsigned int& unicode)
725{
726 if (!decodeUnicodeEscapeSequence(token, current, end, unicode))
727 return false;
728
729 if (unicode >= 0xD800 && unicode <= 0xDBFF)
730 {
731 // surrogate pairs
732 if (end - current < 6)
733 {
734 return addError(
735 "additional six characters expected to parse unicode surrogate "
736 "pair.",
737 token,
738 current);
739 }
740
741 unsigned int surrogatePair = 0;
742
743 if (*current != '\\' || *(current + 1) != 'u')
744 {
745 return addError(
746 "expecting another \\u token to begin the second half of a unicode surrogate pair",
747 token,
748 current);
749 }
750
751 current += 2; // skip two characters checked above
752
753 if (!decodeUnicodeEscapeSequence(token, current, end, surrogatePair))
754 return false;
755
756 unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
757 }
758
759 return true;
760}
761
762bool
764 Token& token,
765 Location& current,
766 Location end,
767 unsigned int& unicode)
768{
769 if (end - current < 4)
770 {
771 return addError(
772 "Bad unicode escape sequence in string: four digits expected.", token, current);
773 }
774
775 unicode = 0;
776
777 for (int index = 0; index < 4; ++index)
778 {
779 Char const c = *current++;
780 unicode *= 16;
781
782 if (c >= '0' && c <= '9')
783 {
784 unicode += c - '0';
785 }
786 else if (c >= 'a' && c <= 'f')
787 {
788 unicode += c - 'a' + 10;
789 }
790 else if (c >= 'A' && c <= 'F')
791 {
792 unicode += c - 'A' + 10;
793 }
794 else
795 {
796 return addError(
797 "Bad unicode escape sequence in string: hexadecimal digit "
798 "expected.",
799 token,
800 current);
801 }
802 }
803
804 return true;
805}
806
807bool
808Reader::addError(std::string const& message, Token& token, Location extra)
809{
810 ErrorInfo info;
811 info.token = token;
812 info.message = message;
813 info.extra = extra;
814 errors_.push_back(info);
815 return false;
816}
817
818bool
820{
821 int const errorCount = int(errors_.size());
822 Token skip{};
823
824 while (true)
825 {
826 if (!readToken(skip))
827 errors_.resize(errorCount); // discard errors caused by recovery
828
829 if (skip.type == skipUntilToken || skip.type == TokenType::EndOfStream)
830 break;
831 }
832
833 errors_.resize(errorCount);
834 return false;
835}
836
837bool
838Reader::addErrorAndRecover(std::string const& message, Token& token, TokenType skipUntilToken)
839{
840 addError(message, token);
841 return recoverFromError(skipUntilToken);
842}
843
844Value&
846{
847 return *(nodes_.top());
848}
849
852{
853 if (current_ == end_)
854 return 0;
855
856 return *current_++;
857}
858
859void
860Reader::getLocationLineAndColumn(Location location, int& line, int& column) const
861{
862 Location current = begin_;
863 Location lastLineStart = current;
864 line = 0;
865
866 while (current < location && current != end_)
867 {
868 Char const c = *current++;
869
870 if (c == '\r')
871 {
872 if (*current == '\n')
873 ++current;
874
875 lastLineStart = current;
876 ++line;
877 }
878 else if (c == '\n')
879 {
880 lastLineStart = current;
881 ++line;
882 }
883 }
884
885 // column & line start at 1
886 column = int(location - lastLineStart) + 1;
887 ++line;
888}
889
892{
893 int line = 0, column = 0;
894 getLocationLineAndColumn(location, line, column);
895 return "Line " + std::to_string(line) + ", Column " + std::to_string(column);
896}
897
900{
901 std::string formattedMessage;
902
903 for (auto const& error : errors_)
904 {
905 formattedMessage += "* " + getLocationLineAndColumn(error.token.start) + "\n";
906 formattedMessage += " " + error.message + "\n";
907
908 if (error.extra != nullptr)
909 formattedMessage += "See " + getLocationLineAndColumn(error.extra) + " for detail.\n";
910 }
911
912 return formattedMessage;
913}
914
917{
918 json::Reader reader;
919 bool const ok = reader.parse(sin, root);
920
921 // XRPL_ASSERT(ok, "json::operator>>() : parse succeeded");
922 if (!ok)
924
925 return sin;
926}
927
928} // namespace json
Unserialize a JSON document into a Value.
Definition json_reader.h:20
Location current_
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
bool recoverFromError(TokenType skipUntilToken)
bool decodeUnicodeCodePoint(Token &token, Location &current, Location end, unsigned int &unicode)
bool readObject(Token &token, unsigned depth)
std::string getFormattedErrorMessages() const
Returns a user friendly string that list errors in the parsed document.
void skipCommentTokens(Token &token)
bool addError(std::string const &message, Token &token, Location extra=nullptr)
bool decodeDouble(Token &token)
Location end_
static constexpr unsigned kNestLimit
Definition json_reader.h:80
bool readArray(Token &token, unsigned depth)
Value * lastValue_
Reader::TokenType readNumber()
void getLocationLineAndColumn(Location location, int &line, int &column) const
Char const * Location
Definition json_reader.h:23
bool decodeNumber(Token &token)
Value & currentValue()
Location begin_
std::string document_
bool readValue(unsigned depth)
bool readCStyleComment()
bool readCppStyleComment()
bool expectToken(TokenType type, Token &token, char const *message)
Location lastValueEnd_
bool decodeUnicodeEscapeSequence(Token &token, Location &current, Location end, unsigned int &unicode)
bool addErrorAndRecover(std::string const &message, Token &token, TokenType skipUntilToken)
bool match(Location pattern, int patternLength)
bool readToken(Token &token)
bool decodeString(Token &token)
Represents a JSON value.
Definition json_value.h:117
static constexpr Int kMaxInt
Definition json_value.h:130
json::Int Int
Definition json_value.h:125
json::UInt UInt
Definition json_value.h:124
static constexpr UInt kMaxUInt
Definition json_value.h:131
T empty(T... args)
T end(T... args)
T find(T... args)
T getline(T... args)
JSON (JavaScript Object Notation).
Definition json_errors.h:5
std::istream & operator>>(std::istream &, Value &)
Read from 'sin' into 'root'.
static std::string codePointToUTF8(unsigned int cp)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T reserve(T... args)
T resize(T... args)
LedgerEntryType type
Definition Keylet.h:22
T to_string(T... args)