| // Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors | |
| // Copyright (C) 2016 InfoTeCS JSC. All rights reserved. | |
| // Distributed under MIT license, or public domain if desired and | |
| // recognized in your jurisdiction. | |
| // See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE | |
| #if !defined(JSON_IS_AMALGAMATION) | |
| #include "json_tool.h" | |
| #include <json/assertions.h> | |
| #include <json/reader.h> | |
| #include <json/value.h> | |
| #endif // if !defined(JSON_IS_AMALGAMATION) | |
| #include <algorithm> | |
| #include <cassert> | |
| #include <cmath> | |
| #include <cstring> | |
| #include <iostream> | |
| #include <istream> | |
| #include <iterator> | |
| #include <limits> | |
| #include <memory> | |
| #include <set> | |
| #include <sstream> | |
| #include <utility> | |
| #include <cstdio> | |
| #if defined(_MSC_VER) | |
| #if !defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) | |
| #define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1 | |
| #endif //_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES | |
| #endif //_MSC_VER | |
| #if defined(_MSC_VER) | |
| // Disable warning about strdup being deprecated. | |
| #pragma warning(disable : 4996) | |
| #endif | |
| // Define JSONCPP_DEPRECATED_STACK_LIMIT as an appropriate integer at compile | |
| // time to change the stack limit | |
| #if !defined(JSONCPP_DEPRECATED_STACK_LIMIT) | |
| #define JSONCPP_DEPRECATED_STACK_LIMIT 256 | |
| #endif | |
| static size_t const stackLimit_g = | |
| JSONCPP_DEPRECATED_STACK_LIMIT; // see readValue() | |
| namespace Json { | |
| using CharReaderPtr = std::unique_ptr<CharReader>; | |
| // Implementation of class Features | |
| // //////////////////////////////// | |
| Features::Features() = default; | |
| Features Features::all() { return {}; } | |
| Features Features::strictMode() { | |
| Features features; | |
| features.allowComments_ = false; | |
| features.strictRoot_ = true; | |
| features.allowDroppedNullPlaceholders_ = false; | |
| features.allowNumericKeys_ = false; | |
| return features; | |
| } | |
| // Implementation of class Reader | |
| // //////////////////////////////// | |
| bool Reader::containsNewLine(Reader::Location begin, Reader::Location end) { | |
| return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); | |
| } | |
| // Class Reader | |
| // ////////////////////////////////////////////////////////////////// | |
| Reader::Reader() : features_(Features::all()) {} | |
| Reader::Reader(const Features& features) : features_(features) {} | |
| bool Reader::parse(const std::string& document, Value& root, | |
| bool collectComments) { | |
| document_.assign(document.begin(), document.end()); | |
| const char* begin = document_.c_str(); | |
| const char* end = begin + document_.length(); | |
| return parse(begin, end, root, collectComments); | |
| } | |
| bool Reader::parse(std::istream& is, Value& root, bool collectComments) { | |
| document_.assign(std::istreambuf_iterator<char>(is), | |
| std::istreambuf_iterator<char>()); | |
| return parse(document_.data(), document_.data() + document_.size(), root, | |
| collectComments); | |
| } | |
| bool Reader::parse(const char* beginDoc, const char* endDoc, Value& root, | |
| bool collectComments) { | |
| if (!features_.allowComments_) { | |
| collectComments = false; | |
| } | |
| begin_ = beginDoc; | |
| end_ = endDoc; | |
| collectComments_ = collectComments; | |
| current_ = begin_; | |
| lastValueEnd_ = nullptr; | |
| lastValue_ = nullptr; | |
| commentsBefore_.clear(); | |
| errors_.clear(); | |
| while (!nodes_.empty()) | |
| nodes_.pop(); | |
| nodes_.push(&root); | |
| bool successful = readValue(); | |
| Token token; | |
| readTokenSkippingComments(token); | |
| if (collectComments_ && !commentsBefore_.empty()) | |
| root.setComment(commentsBefore_, commentAfter); | |
| if (features_.strictRoot_) { | |
| if (!root.isArray() && !root.isObject()) { | |
| // Set error location to start of doc, ideally should be first token found | |
| // in doc | |
| token.type_ = tokenError; | |
| token.start_ = beginDoc; | |
| token.end_ = endDoc; | |
| addError( | |
| "A valid JSON document must be either an array or an object value.", | |
| token); | |
| return false; | |
| } | |
| } | |
| return successful; | |
| } | |
| bool Reader::readValue() { | |
| // readValue() may call itself only if it calls readObject() or ReadArray(). | |
| // These methods execute nodes_.push() just before and nodes_.pop)() just | |
| // after calling readValue(). parse() executes one nodes_.push(), so > instead | |
| // of >=. | |
| if (nodes_.size() > stackLimit_g) | |
| #if JSON_USE_EXCEPTION | |
| throwRuntimeError("Exceeded stackLimit in readValue()."); | |
| #else | |
| // throwRuntimeError aborts. Don't abort here. | |
| return false; | |
| #endif | |
| Token token; | |
| readTokenSkippingComments(token); | |
| bool successful = true; | |
| if (collectComments_ && !commentsBefore_.empty()) { | |
| currentValue().setComment(commentsBefore_, commentBefore); | |
| commentsBefore_.clear(); | |
| } | |
| switch (token.type_) { | |
| case tokenObjectBegin: | |
| successful = readObject(token); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| case tokenArrayBegin: | |
| successful = readArray(token); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| case tokenNumber: | |
| successful = decodeNumber(token); | |
| break; | |
| case tokenString: | |
| successful = decodeString(token); | |
| break; | |
| case tokenTrue: { | |
| Value v(true); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenFalse: { | |
| Value v(false); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenNull: { | |
| Value v; | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenArraySeparator: | |
| case tokenObjectEnd: | |
| case tokenArrayEnd: | |
| if (features_.allowDroppedNullPlaceholders_) { | |
| // "Un-read" the current token and mark the current value as a null | |
| // token. | |
| current_--; | |
| Value v; | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(current_ - begin_ - 1); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| } // Else, fall through... | |
| default: | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return addError("Syntax error: value, object or array expected.", token); | |
| } | |
| if (collectComments_) { | |
| lastValueEnd_ = current_; | |
| lastValue_ = ¤tValue(); | |
| } | |
| return successful; | |
| } | |
| bool Reader::readTokenSkippingComments(Token& token) { | |
| bool success = readToken(token); | |
| if (features_.allowComments_) { | |
| while (success && token.type_ == tokenComment) { | |
| success = readToken(token); | |
| } | |
| } | |
| return success; | |
| } | |
| bool Reader::readToken(Token& token) { | |
| skipSpaces(); | |
| token.start_ = current_; | |
| Char c = getNextChar(); | |
| bool ok = true; | |
| switch (c) { | |
| case '{': | |
| token.type_ = tokenObjectBegin; | |
| break; | |
| case '}': | |
| token.type_ = tokenObjectEnd; | |
| break; | |
| case '[': | |
| token.type_ = tokenArrayBegin; | |
| break; | |
| case ']': | |
| token.type_ = tokenArrayEnd; | |
| break; | |
| case '"': | |
| token.type_ = tokenString; | |
| ok = readString(); | |
| break; | |
| case '/': | |
| token.type_ = tokenComment; | |
| ok = readComment(); | |
| break; | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': | |
| case '-': | |
| token.type_ = tokenNumber; | |
| readNumber(); | |
| break; | |
| case 't': | |
| token.type_ = tokenTrue; | |
| ok = match("rue", 3); | |
| break; | |
| case 'f': | |
| token.type_ = tokenFalse; | |
| ok = match("alse", 4); | |
| break; | |
| case 'n': | |
| token.type_ = tokenNull; | |
| ok = match("ull", 3); | |
| break; | |
| case ',': | |
| token.type_ = tokenArraySeparator; | |
| break; | |
| case ':': | |
| token.type_ = tokenMemberSeparator; | |
| break; | |
| case 0: | |
| token.type_ = tokenEndOfStream; | |
| break; | |
| default: | |
| ok = false; | |
| break; | |
| } | |
| if (!ok) | |
| token.type_ = tokenError; | |
| token.end_ = current_; | |
| return ok; | |
| } | |
| void Reader::skipSpaces() { | |
| while (current_ != end_) { | |
| Char c = *current_; | |
| if (c == ' ' || c == '\t' || c == '\r' || c == '\n') | |
| ++current_; | |
| else | |
| break; | |
| } | |
| } | |
| bool Reader::match(const Char* pattern, int patternLength) { | |
| if (end_ - current_ < patternLength) | |
| return false; | |
| int index = patternLength; | |
| while (index--) | |
| if (current_[index] != pattern[index]) | |
| return false; | |
| current_ += patternLength; | |
| return true; | |
| } | |
| bool Reader::readComment() { | |
| Location commentBegin = current_ - 1; | |
| Char c = getNextChar(); | |
| bool successful = false; | |
| if (c == '*') | |
| successful = readCStyleComment(); | |
| else if (c == '/') | |
| successful = readCppStyleComment(); | |
| if (!successful) | |
| return false; | |
| if (collectComments_) { | |
| CommentPlacement placement = commentBefore; | |
| if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { | |
| if (c != '*' || !containsNewLine(commentBegin, current_)) | |
| placement = commentAfterOnSameLine; | |
| } | |
| addComment(commentBegin, current_, placement); | |
| } | |
| return true; | |
| } | |
| String Reader::normalizeEOL(Reader::Location begin, Reader::Location end) { | |
| String normalized; | |
| normalized.reserve(static_cast<size_t>(end - begin)); | |
| Reader::Location current = begin; | |
| while (current != end) { | |
| char c = *current++; | |
| if (c == '\r') { | |
| if (current != end && *current == '\n') | |
| // convert dos EOL | |
| ++current; | |
| // convert Mac EOL | |
| normalized += '\n'; | |
| } else { | |
| normalized += c; | |
| } | |
| } | |
| return normalized; | |
| } | |
| void Reader::addComment(Location begin, Location end, | |
| CommentPlacement placement) { | |
| assert(collectComments_); | |
| const String& normalized = normalizeEOL(begin, end); | |
| if (placement == commentAfterOnSameLine) { | |
| assert(lastValue_ != nullptr); | |
| lastValue_->setComment(normalized, placement); | |
| } else { | |
| commentsBefore_ += normalized; | |
| } | |
| } | |
| bool Reader::readCStyleComment() { | |
| while ((current_ + 1) < end_) { | |
| Char c = getNextChar(); | |
| if (c == '*' && *current_ == '/') | |
| break; | |
| } | |
| return getNextChar() == '/'; | |
| } | |
| bool Reader::readCppStyleComment() { | |
| while (current_ != end_) { | |
| Char c = getNextChar(); | |
| if (c == '\n') | |
| break; | |
| if (c == '\r') { | |
| // Consume DOS EOL. It will be normalized in addComment. | |
| if (current_ != end_ && *current_ == '\n') | |
| getNextChar(); | |
| // Break on Moc OS 9 EOL. | |
| break; | |
| } | |
| } | |
| return true; | |
| } | |
| void Reader::readNumber() { | |
| Location p = current_; | |
| char c = '0'; // stopgap for already consumed character | |
| // integral part | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| // fractional part | |
| if (c == '.') { | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| } | |
| // exponential part | |
| if (c == 'e' || c == 'E') { | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| if (c == '+' || c == '-') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| } | |
| } | |
| bool Reader::readString() { | |
| Char c = '\0'; | |
| while (current_ != end_) { | |
| c = getNextChar(); | |
| if (c == '\\') | |
| getNextChar(); | |
| else if (c == '"') | |
| break; | |
| } | |
| return c == '"'; | |
| } | |
| bool Reader::readObject(Token& token) { | |
| Token tokenName; | |
| String name; | |
| Value init(objectValue); | |
| currentValue().swapPayload(init); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| while (readTokenSkippingComments(tokenName)) { | |
| if (tokenName.type_ == tokenObjectEnd && name.empty()) // empty object | |
| return true; | |
| name.clear(); | |
| if (tokenName.type_ == tokenString) { | |
| if (!decodeString(tokenName, name)) | |
| return recoverFromError(tokenObjectEnd); | |
| } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { | |
| Value numberName; | |
| if (!decodeNumber(tokenName, numberName)) | |
| return recoverFromError(tokenObjectEnd); | |
| name = numberName.asString(); | |
| } else { | |
| break; | |
| } | |
| Token colon; | |
| if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { | |
| return addErrorAndRecover("Missing ':' after object member name", colon, | |
| tokenObjectEnd); | |
| } | |
| Value& value = currentValue()[name]; | |
| nodes_.push(&value); | |
| bool ok = readValue(); | |
| nodes_.pop(); | |
| if (!ok) // error already set | |
| return recoverFromError(tokenObjectEnd); | |
| Token comma; | |
| if (!readTokenSkippingComments(comma) || | |
| (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { | |
| return addErrorAndRecover("Missing ',' or '}' in object declaration", | |
| comma, tokenObjectEnd); | |
| } | |
| if (comma.type_ == tokenObjectEnd) | |
| return true; | |
| } | |
| return addErrorAndRecover("Missing '}' or object member name", tokenName, | |
| tokenObjectEnd); | |
| } | |
| bool Reader::readArray(Token& token) { | |
| Value init(arrayValue); | |
| currentValue().swapPayload(init); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| skipSpaces(); | |
| if (current_ != end_ && *current_ == ']') // empty array | |
| { | |
| Token endArray; | |
| readToken(endArray); | |
| return true; | |
| } | |
| int index = 0; | |
| for (;;) { | |
| Value& value = currentValue()[index++]; | |
| nodes_.push(&value); | |
| bool ok = readValue(); | |
| nodes_.pop(); | |
| if (!ok) // error already set | |
| return recoverFromError(tokenArrayEnd); | |
| Token currentToken; | |
| // Accept Comment after last item in the array. | |
| ok = readTokenSkippingComments(currentToken); | |
| bool badTokenType = (currentToken.type_ != tokenArraySeparator && | |
| currentToken.type_ != tokenArrayEnd); | |
| if (!ok || badTokenType) { | |
| return addErrorAndRecover("Missing ',' or ']' in array declaration", | |
| currentToken, tokenArrayEnd); | |
| } | |
| if (currentToken.type_ == tokenArrayEnd) | |
| break; | |
| } | |
| return true; | |
| } | |
| bool Reader::decodeNumber(Token& token) { | |
| Value decoded; | |
| if (!decodeNumber(token, decoded)) | |
| return false; | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool Reader::decodeNumber(Token& token, Value& decoded) { | |
| // Attempts to parse the number as an integer. If the number is | |
| // larger than the maximum supported value of an integer then | |
| // we decode the number as a double. | |
| Location current = token.start_; | |
| bool isNegative = *current == '-'; | |
| if (isNegative) | |
| ++current; | |
| // TODO: Help the compiler do the div and mod at compile time or get rid of | |
| // them. | |
| Value::LargestUInt maxIntegerValue = | |
| isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 | |
| : Value::maxLargestUInt; | |
| Value::LargestUInt threshold = maxIntegerValue / 10; | |
| Value::LargestUInt value = 0; | |
| while (current < token.end_) { | |
| Char c = *current++; | |
| if (c < '0' || c > '9') | |
| return decodeDouble(token, decoded); | |
| auto digit(static_cast<Value::UInt>(c - '0')); | |
| if (value >= threshold) { | |
| // We've hit or exceeded the max value divided by 10 (rounded down). If | |
| // a) we've only just touched the limit, b) this is the last digit, and | |
| // c) it's small enough to fit in that rounding delta, we're okay. | |
| // Otherwise treat this number as a double to avoid overflow. | |
| if (value > threshold || current != token.end_ || | |
| digit > maxIntegerValue % 10) { | |
| return decodeDouble(token, decoded); | |
| } | |
| } | |
| value = value * 10 + digit; | |
| } | |
| if (isNegative && value == maxIntegerValue) | |
| decoded = Value::minLargestInt; | |
| else if (isNegative) | |
| decoded = -Value::LargestInt(value); | |
| else if (value <= Value::LargestUInt(Value::maxInt)) | |
| decoded = Value::LargestInt(value); | |
| else | |
| decoded = value; | |
| return true; | |
| } | |
| bool Reader::decodeDouble(Token& token) { | |
| Value decoded; | |
| if (!decodeDouble(token, decoded)) | |
| return false; | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool Reader::decodeDouble(Token& token, Value& decoded) { | |
| double value = 0; | |
| IStringStream is(String(token.start_, token.end_)); | |
| is.imbue(std::locale::classic()); | |
| if (!(is >> value)) { | |
| if (value == std::numeric_limits<double>::max()) | |
| value = std::numeric_limits<double>::infinity(); | |
| else if (value == std::numeric_limits<double>::lowest()) | |
| value = -std::numeric_limits<double>::infinity(); | |
| // operator>> sets failbit for a subnormal result (underflow) even though | |
| // it produced the correctly-rounded value, which made such numbers fail to | |
| // parse back after jsoncpp serialized them. Keep a subnormal value instead | |
| // of rejecting it. See issue #1427. Other failures -- malformed numbers | |
| // like "0e" or "0e+", or non-numbers -- leave the value at zero/non-finite | |
| // and are still rejected. | |
| else if (!std::isinf(value) && std::fpclassify(value) != FP_SUBNORMAL) | |
| return addError( | |
| "'" + String(token.start_, token.end_) + "' is not a number.", token); | |
| } | |
| decoded = value; | |
| return true; | |
| } | |
| bool Reader::decodeString(Token& token) { | |
| String decoded_string; | |
| if (!decodeString(token, decoded_string)) | |
| return false; | |
| Value decoded(decoded_string); | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool Reader::decodeString(Token& token, String& decoded) { | |
| decoded.reserve(static_cast<size_t>(token.end_ - token.start_ - 2)); | |
| Location current = token.start_ + 1; // skip '"' | |
| Location end = token.end_ - 1; // do not include '"' | |
| while (current != end) { | |
| Char c = *current++; | |
| if (c == '"') | |
| break; | |
| if (c == '\\') { | |
| if (current == end) | |
| return addError("Empty escape sequence in string", token, current); | |
| Char escape = *current++; | |
| switch (escape) { | |
| case '"': | |
| decoded += '"'; | |
| break; | |
| case '/': | |
| decoded += '/'; | |
| break; | |
| case '\\': | |
| decoded += '\\'; | |
| break; | |
| case 'b': | |
| decoded += '\b'; | |
| break; | |
| case 'f': | |
| decoded += '\f'; | |
| break; | |
| case 'n': | |
| decoded += '\n'; | |
| break; | |
| case 'r': | |
| decoded += '\r'; | |
| break; | |
| case 't': | |
| decoded += '\t'; | |
| break; | |
| case 'u': { | |
| unsigned int unicode; | |
| if (!decodeUnicodeCodePoint(token, current, end, unicode)) | |
| return false; | |
| decoded += codePointToUTF8(unicode); | |
| } break; | |
| default: | |
| return addError("Bad escape sequence in string", token, current); | |
| } | |
| } else { | |
| if (static_cast<unsigned char>(c) < 0x20) | |
| return addError("Control character in string", token, current - 1); | |
| decoded += c; | |
| } | |
| } | |
| return true; | |
| } | |
| bool Reader::decodeUnicodeCodePoint(Token& token, Location& current, | |
| Location end, unsigned int& unicode) { | |
| if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) | |
| return false; | |
| if (unicode >= 0xD800 && unicode <= 0xDBFF) { | |
| // surrogate pairs | |
| if (end - current < 6) | |
| return addError( | |
| "additional six characters expected to parse unicode surrogate pair.", | |
| token, current); | |
| if (*(current++) == '\\' && *(current++) == 'u') { | |
| unsigned int surrogatePair; | |
| if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { | |
| unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); | |
| } else | |
| return false; | |
| } else | |
| return addError("expecting another \\u token to begin the second half of " | |
| "a unicode surrogate pair", | |
| token, current); | |
| } | |
| return true; | |
| } | |
| bool Reader::decodeUnicodeEscapeSequence(Token& token, Location& current, | |
| Location end, | |
| unsigned int& ret_unicode) { | |
| if (end - current < 4) | |
| return addError( | |
| "Bad unicode escape sequence in string: four digits expected.", token, | |
| current); | |
| int unicode = 0; | |
| for (int index = 0; index < 4; ++index) { | |
| Char c = *current++; | |
| unicode *= 16; | |
| if (c >= '0' && c <= '9') | |
| unicode += c - '0'; | |
| else if (c >= 'a' && c <= 'f') | |
| unicode += c - 'a' + 10; | |
| else if (c >= 'A' && c <= 'F') | |
| unicode += c - 'A' + 10; | |
| else | |
| return addError( | |
| "Bad unicode escape sequence in string: hexadecimal digit expected.", | |
| token, current); | |
| } | |
| ret_unicode = static_cast<unsigned int>(unicode); | |
| return true; | |
| } | |
| bool Reader::addError(const String& message, Token& token, Location extra) { | |
| ErrorInfo info; | |
| info.token_ = token; | |
| info.message_ = message; | |
| info.extra_ = extra; | |
| errors_.push_back(info); | |
| return false; | |
| } | |
| bool Reader::recoverFromError(TokenType skipUntilToken) { | |
| size_t const errorCount = errors_.size(); | |
| Token skip; | |
| for (;;) { | |
| if (!readToken(skip)) | |
| errors_.resize(errorCount); // discard errors caused by recovery | |
| if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) | |
| break; | |
| } | |
| errors_.resize(errorCount); | |
| return false; | |
| } | |
| bool Reader::addErrorAndRecover(const String& message, Token& token, | |
| TokenType skipUntilToken) { | |
| addError(message, token); | |
| return recoverFromError(skipUntilToken); | |
| } | |
| Value& Reader::currentValue() { return *(nodes_.top()); } | |
| Reader::Char Reader::getNextChar() { | |
| if (current_ == end_) | |
| return 0; | |
| return *current_++; | |
| } | |
| void Reader::getLocationLineAndColumn(Location location, int& line, | |
| int& column) const { | |
| Location current = begin_; | |
| Location lastLineStart = current; | |
| line = 0; | |
| while (current < location && current != end_) { | |
| Char c = *current++; | |
| if (c == '\r') { | |
| if (current != end_ && *current == '\n') | |
| ++current; | |
| lastLineStart = current; | |
| ++line; | |
| } else if (c == '\n') { | |
| lastLineStart = current; | |
| ++line; | |
| } | |
| } | |
| // column & line start at 1 | |
| column = int(location - lastLineStart) + 1; | |
| ++line; | |
| } | |
| String Reader::getLocationLineAndColumn(Location location) const { | |
| int line, column; | |
| getLocationLineAndColumn(location, line, column); | |
| char buffer[18 + 16 + 16 + 1]; | |
| jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); | |
| return buffer; | |
| } | |
| // Deprecated. Preserved for backward compatibility | |
| String Reader::getFormatedErrorMessages() const { | |
| return getFormattedErrorMessages(); | |
| } | |
| String Reader::getFormattedErrorMessages() const { | |
| String formattedMessage; | |
| for (const auto& error : errors_) { | |
| formattedMessage += | |
| "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; | |
| formattedMessage += " " + error.message_ + "\n"; | |
| if (error.extra_) | |
| formattedMessage += | |
| "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; | |
| } | |
| return formattedMessage; | |
| } | |
| std::vector<Reader::StructuredError> Reader::getStructuredErrors() const { | |
| std::vector<Reader::StructuredError> allErrors; | |
| for (const auto& error : errors_) { | |
| Reader::StructuredError structured; | |
| structured.offset_start = error.token_.start_ - begin_; | |
| structured.offset_limit = error.token_.end_ - begin_; | |
| structured.message = error.message_; | |
| allErrors.push_back(structured); | |
| } | |
| return allErrors; | |
| } | |
| bool Reader::pushError(const Value& value, const String& message) { | |
| ptrdiff_t const length = end_ - begin_; | |
| if (value.getOffsetStart() > length || value.getOffsetLimit() > length) | |
| return false; | |
| Token token; | |
| token.type_ = tokenError; | |
| token.start_ = begin_ + value.getOffsetStart(); | |
| token.end_ = begin_ + value.getOffsetLimit(); | |
| ErrorInfo info; | |
| info.token_ = token; | |
| info.message_ = message; | |
| info.extra_ = nullptr; | |
| errors_.push_back(info); | |
| return true; | |
| } | |
| bool Reader::pushError(const Value& value, const String& message, | |
| const Value& extra) { | |
| ptrdiff_t const length = end_ - begin_; | |
| if (value.getOffsetStart() > length || value.getOffsetLimit() > length || | |
| extra.getOffsetLimit() > length) | |
| return false; | |
| Token token; | |
| token.type_ = tokenError; | |
| token.start_ = begin_ + value.getOffsetStart(); | |
| token.end_ = begin_ + value.getOffsetLimit(); | |
| ErrorInfo info; | |
| info.token_ = token; | |
| info.message_ = message; | |
| info.extra_ = begin_ + extra.getOffsetStart(); | |
| errors_.push_back(info); | |
| return true; | |
| } | |
| bool Reader::good() const { return errors_.empty(); } | |
| // Originally copied from the Features class (now deprecated), used internally | |
| // for features implementation. | |
| class OurFeatures { | |
| public: | |
| static OurFeatures all(); | |
| bool allowComments_; | |
| bool allowTrailingCommas_; | |
| bool strictRoot_; | |
| bool allowDroppedNullPlaceholders_; | |
| bool allowNumericKeys_; | |
| bool allowSingleQuotes_; | |
| bool failIfExtra_; | |
| bool rejectDupKeys_; | |
| bool allowSpecialFloats_; | |
| bool skipBom_; | |
| size_t stackLimit_; | |
| }; // OurFeatures | |
| OurFeatures OurFeatures::all() { return {}; } | |
| // Implementation of class Reader | |
| // //////////////////////////////// | |
| // Originally copied from the Reader class (now deprecated), used internally | |
| // for implementing JSON reading. | |
| class OurReader { | |
| public: | |
| using Char = char; | |
| using Location = const Char*; | |
| explicit OurReader(OurFeatures const& features); | |
| bool parse(const char* beginDoc, const char* endDoc, Value& root, | |
| bool collectComments = true); | |
| String getFormattedErrorMessages() const; | |
| std::vector<CharReader::StructuredError> getStructuredErrors() const; | |
| private: | |
| OurReader(OurReader const&); // no impl | |
| void operator=(OurReader const&); // no impl | |
| enum TokenType { | |
| tokenEndOfStream = 0, | |
| tokenObjectBegin, | |
| tokenObjectEnd, | |
| tokenArrayBegin, | |
| tokenArrayEnd, | |
| tokenString, | |
| tokenNumber, | |
| tokenTrue, | |
| tokenFalse, | |
| tokenNull, | |
| tokenNaN, | |
| tokenPosInf, | |
| tokenNegInf, | |
| tokenArraySeparator, | |
| tokenMemberSeparator, | |
| tokenComment, | |
| tokenError | |
| }; | |
| class Token { | |
| public: | |
| TokenType type_; | |
| Location start_; | |
| Location end_; | |
| }; | |
| class ErrorInfo { | |
| public: | |
| Token token_; | |
| String message_; | |
| Location extra_; | |
| }; | |
| using Errors = std::deque<ErrorInfo>; | |
| bool readToken(Token& token); | |
| bool readTokenSkippingComments(Token& token); | |
| void skipSpaces(); | |
| void skipCommentTokens(); | |
| void skipBom(bool skipBom); | |
| bool match(const Char* pattern, int patternLength); | |
| bool readComment(); | |
| bool readCStyleComment(bool* containsNewLineResult); | |
| bool readCppStyleComment(); | |
| bool readString(); | |
| bool readStringSingleQuote(); | |
| bool readNumber(bool checkInf); | |
| bool readValue(); | |
| bool readObject(Token& token); | |
| bool readArray(Token& token); | |
| bool decodeNumber(Token& token); | |
| bool decodeNumber(Token& token, Value& decoded); | |
| bool decodeString(Token& token); | |
| bool decodeString(Token& token, String& decoded); | |
| bool decodeDouble(Token& token); | |
| bool decodeDouble(Token& token, Value& decoded); | |
| bool decodeUnicodeCodePoint(Token& token, Location& current, Location end, | |
| unsigned int& unicode); | |
| bool decodeUnicodeEscapeSequence(Token& token, Location& current, | |
| Location end, unsigned int& unicode); | |
| bool addError(const String& message, Token& token, Location extra = nullptr); | |
| bool recoverFromError(TokenType skipUntilToken); | |
| bool addErrorAndRecover(const String& message, Token& token, | |
| TokenType skipUntilToken); | |
| void skipUntilSpace(); | |
| Value& currentValue(); | |
| Char getNextChar(); | |
| void getLocationLineAndColumn(Location location, int& line, | |
| int& column) const; | |
| String getLocationLineAndColumn(Location location) const; | |
| void addComment(Location begin, Location end, CommentPlacement placement); | |
| static String normalizeEOL(Location begin, Location end); | |
| static bool containsNewLine(Location begin, Location end); | |
| using Nodes = std::stack<Value*>; | |
| Nodes nodes_{}; | |
| Errors errors_{}; | |
| String document_{}; | |
| Location begin_ = nullptr; | |
| Location end_ = nullptr; | |
| Location current_ = nullptr; | |
| Location lastValueEnd_ = nullptr; | |
| Value* lastValue_ = nullptr; | |
| bool lastValueHasAComment_ = false; | |
| String commentsBefore_{}; | |
| OurFeatures const features_; | |
| bool collectComments_ = false; | |
| }; // OurReader | |
| // complete copy of Read impl, for OurReader | |
| // Test-only instrumentation: total bytes examined by | |
| // OurReader::containsNewLine, so unit tests can assert that comment handling | |
| // stays linear in the input rather than quadratic in the comment count (see | |
| // CharReaderTest/parseCommentsAfterValueScansLinearly). thread_local so it | |
| // never races during concurrent parsing; the increment is negligible and only | |
| // runs while parsing comments. Not part of the supported public API. | |
| JSON_API size_t& newlineScanByteCountForTesting() { | |
| static thread_local size_t count = 0; | |
| return count; | |
| } | |
| bool OurReader::containsNewLine(OurReader::Location begin, | |
| OurReader::Location end) { | |
| newlineScanByteCountForTesting() += static_cast<size_t>(end - begin); | |
| return std::any_of(begin, end, [](char b) { return b == '\n' || b == '\r'; }); | |
| } | |
| OurReader::OurReader(OurFeatures const& features) : features_(features) {} | |
| bool OurReader::parse(const char* beginDoc, const char* endDoc, Value& root, | |
| bool collectComments) { | |
| if (!features_.allowComments_) { | |
| collectComments = false; | |
| } | |
| begin_ = beginDoc; | |
| end_ = endDoc; | |
| collectComments_ = collectComments; | |
| current_ = begin_; | |
| lastValueEnd_ = nullptr; | |
| lastValue_ = nullptr; | |
| commentsBefore_.clear(); | |
| errors_.clear(); | |
| while (!nodes_.empty()) | |
| nodes_.pop(); | |
| nodes_.push(&root); | |
| // skip byte order mark if it exists at the beginning of the UTF-8 text. | |
| skipBom(features_.skipBom_); | |
| bool successful = readValue(); | |
| nodes_.pop(); | |
| Token token; | |
| readTokenSkippingComments(token); | |
| if (features_.failIfExtra_ && (token.type_ != tokenEndOfStream)) { | |
| addError("Extra non-whitespace after JSON value.", token); | |
| return false; | |
| } | |
| if (collectComments_ && !commentsBefore_.empty()) | |
| root.setComment(commentsBefore_, commentAfter); | |
| if (features_.strictRoot_) { | |
| if (!root.isArray() && !root.isObject()) { | |
| // Set error location to start of doc, ideally should be first token found | |
| // in doc | |
| token.type_ = tokenError; | |
| token.start_ = beginDoc; | |
| token.end_ = endDoc; | |
| addError( | |
| "A valid JSON document must be either an array or an object value.", | |
| token); | |
| return false; | |
| } | |
| } | |
| return successful; | |
| } | |
| bool OurReader::readValue() { | |
| Token token; | |
| if (nodes_.size() > features_.stackLimit_) { | |
| #if JSON_USE_EXCEPTION | |
| throwRuntimeError("Exceeded stackLimit in readValue()."); | |
| #else | |
| // throwRuntimeError aborts. Don't abort here. | |
| token.start_ = current_; | |
| token.end_ = current_; | |
| token.type_ = tokenError; | |
| return addError( | |
| "Exceeded stackLimit for nested object and/or array values.", token); | |
| #endif | |
| } | |
| readTokenSkippingComments(token); | |
| bool successful = true; | |
| if (collectComments_ && !commentsBefore_.empty()) { | |
| currentValue().setComment(commentsBefore_, commentBefore); | |
| commentsBefore_.clear(); | |
| } | |
| switch (token.type_) { | |
| case tokenObjectBegin: | |
| successful = readObject(token); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| case tokenArrayBegin: | |
| successful = readArray(token); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| case tokenNumber: | |
| successful = decodeNumber(token); | |
| break; | |
| case tokenString: | |
| successful = decodeString(token); | |
| break; | |
| case tokenTrue: { | |
| Value v(true); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenFalse: { | |
| Value v(false); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenNull: { | |
| Value v; | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenNaN: { | |
| Value v(std::numeric_limits<double>::quiet_NaN()); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenPosInf: { | |
| Value v(std::numeric_limits<double>::infinity()); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenNegInf: { | |
| Value v(-std::numeric_limits<double>::infinity()); | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| } break; | |
| case tokenArraySeparator: | |
| case tokenObjectEnd: | |
| case tokenArrayEnd: | |
| if (features_.allowDroppedNullPlaceholders_) { | |
| // "Un-read" the current token and mark the current value as a null | |
| // token. | |
| current_--; | |
| Value v; | |
| currentValue().swapPayload(v); | |
| currentValue().setOffsetStart(current_ - begin_ - 1); | |
| currentValue().setOffsetLimit(current_ - begin_); | |
| break; | |
| } // else, fall through ... | |
| default: | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return addError("Syntax error: value, object or array expected.", token); | |
| } | |
| if (collectComments_) { | |
| lastValueEnd_ = current_; | |
| lastValueHasAComment_ = false; | |
| lastValue_ = ¤tValue(); | |
| } | |
| return successful; | |
| } | |
| bool OurReader::readTokenSkippingComments(Token& token) { | |
| bool success = readToken(token); | |
| if (features_.allowComments_) { | |
| while (success && token.type_ == tokenComment) { | |
| success = readToken(token); | |
| } | |
| } | |
| return success; | |
| } | |
| bool OurReader::readToken(Token& token) { | |
| skipSpaces(); | |
| token.start_ = current_; | |
| Char c = getNextChar(); | |
| bool ok = true; | |
| switch (c) { | |
| case '{': | |
| token.type_ = tokenObjectBegin; | |
| break; | |
| case '}': | |
| token.type_ = tokenObjectEnd; | |
| break; | |
| case '[': | |
| token.type_ = tokenArrayBegin; | |
| break; | |
| case ']': | |
| token.type_ = tokenArrayEnd; | |
| break; | |
| case '"': | |
| token.type_ = tokenString; | |
| ok = readString(); | |
| break; | |
| case '\'': | |
| if (features_.allowSingleQuotes_) { | |
| token.type_ = tokenString; | |
| ok = readStringSingleQuote(); | |
| } else { | |
| // If we don't allow single quotes, this is a failure case. | |
| ok = false; | |
| } | |
| break; | |
| case '/': | |
| token.type_ = tokenComment; | |
| ok = readComment(); | |
| break; | |
| case '0': | |
| case '1': | |
| case '2': | |
| case '3': | |
| case '4': | |
| case '5': | |
| case '6': | |
| case '7': | |
| case '8': | |
| case '9': | |
| token.type_ = tokenNumber; | |
| readNumber(false); | |
| break; | |
| case '-': | |
| if (readNumber(true)) { | |
| token.type_ = tokenNumber; | |
| } else { | |
| token.type_ = tokenNegInf; | |
| ok = features_.allowSpecialFloats_ && match("nfinity", 7); | |
| } | |
| break; | |
| case '+': | |
| if (readNumber(true)) { | |
| token.type_ = tokenNumber; | |
| } else { | |
| token.type_ = tokenPosInf; | |
| ok = features_.allowSpecialFloats_ && match("nfinity", 7); | |
| } | |
| break; | |
| case 't': | |
| token.type_ = tokenTrue; | |
| ok = match("rue", 3); | |
| break; | |
| case 'f': | |
| token.type_ = tokenFalse; | |
| ok = match("alse", 4); | |
| break; | |
| case 'n': | |
| token.type_ = tokenNull; | |
| ok = match("ull", 3); | |
| break; | |
| case 'N': | |
| if (features_.allowSpecialFloats_) { | |
| token.type_ = tokenNaN; | |
| ok = match("aN", 2); | |
| } else { | |
| ok = false; | |
| } | |
| break; | |
| case 'I': | |
| if (features_.allowSpecialFloats_) { | |
| token.type_ = tokenPosInf; | |
| ok = match("nfinity", 7); | |
| } else { | |
| ok = false; | |
| } | |
| break; | |
| case ',': | |
| token.type_ = tokenArraySeparator; | |
| break; | |
| case ':': | |
| token.type_ = tokenMemberSeparator; | |
| break; | |
| case 0: | |
| token.type_ = tokenEndOfStream; | |
| break; | |
| default: | |
| ok = false; | |
| break; | |
| } | |
| if (!ok) | |
| token.type_ = tokenError; | |
| token.end_ = current_; | |
| return ok; | |
| } | |
| void OurReader::skipSpaces() { | |
| while (current_ != end_) { | |
| Char c = *current_; | |
| if (c == ' ' || c == '\t' || c == '\r' || c == '\n') | |
| ++current_; | |
| else | |
| break; | |
| } | |
| } | |
| // Skip whitespace and any comments, leaving current_ at the next significant | |
| // character. Consumed comments are recorded (commentsBefore_) so the next value | |
| // still receives them; if none follows they are simply not attached. This lets | |
| // callers peek for a delimiter that is preceded by comments (e.g. a ']' after a | |
| // trailing comma -- see readArray and issue #1500). | |
| void OurReader::skipCommentTokens() { | |
| skipSpaces(); | |
| if (!features_.allowComments_) | |
| return; | |
| while (current_ != end_ && *current_ == '/' && (current_ + 1) != end_ && | |
| (current_[1] == '/' || current_[1] == '*')) { | |
| Token comment; | |
| if (!readToken(comment)) | |
| return; | |
| skipSpaces(); | |
| } | |
| } | |
| void OurReader::skipBom(bool skipBom) { | |
| // The default behavior is to skip BOM. | |
| if (skipBom) { | |
| if ((end_ - begin_) >= 3 && strncmp(begin_, "\xEF\xBB\xBF", 3) == 0) { | |
| begin_ += 3; | |
| current_ = begin_; | |
| } | |
| } | |
| } | |
| bool OurReader::match(const Char* pattern, int patternLength) { | |
| if (end_ - current_ < patternLength) | |
| return false; | |
| int index = patternLength; | |
| while (index--) | |
| if (current_[index] != pattern[index]) | |
| return false; | |
| current_ += patternLength; | |
| return true; | |
| } | |
| bool OurReader::readComment() { | |
| const Location commentBegin = current_ - 1; | |
| const Char c = getNextChar(); | |
| bool successful = false; | |
| bool cStyleWithEmbeddedNewline = false; | |
| const bool isCStyleComment = (c == '*'); | |
| const bool isCppStyleComment = (c == '/'); | |
| if (isCStyleComment) { | |
| successful = readCStyleComment(&cStyleWithEmbeddedNewline); | |
| } else if (isCppStyleComment) { | |
| successful = readCppStyleComment(); | |
| } | |
| if (!successful) | |
| return false; | |
| if (collectComments_) { | |
| CommentPlacement placement = commentBefore; | |
| if (!lastValueHasAComment_) { | |
| if (lastValueEnd_ && !containsNewLine(lastValueEnd_, commentBegin)) { | |
| if (isCppStyleComment || !cStyleWithEmbeddedNewline) { | |
| placement = commentAfterOnSameLine; | |
| } | |
| } | |
| // The gap between the last value and this comment only grows as more | |
| // comments are consumed, so a later comment can never be on the same | |
| // line as that value. Mark it handled to avoid re-scanning the same | |
| // growing prefix for every following comment (quadratic behavior). | |
| lastValueHasAComment_ = true; | |
| } | |
| addComment(commentBegin, current_, placement); | |
| } | |
| return true; | |
| } | |
| String OurReader::normalizeEOL(OurReader::Location begin, | |
| OurReader::Location end) { | |
| String normalized; | |
| normalized.reserve(static_cast<size_t>(end - begin)); | |
| OurReader::Location current = begin; | |
| while (current != end) { | |
| char c = *current++; | |
| if (c == '\r') { | |
| if (current != end && *current == '\n') | |
| // convert dos EOL | |
| ++current; | |
| // convert Mac EOL | |
| normalized += '\n'; | |
| } else { | |
| normalized += c; | |
| } | |
| } | |
| return normalized; | |
| } | |
| void OurReader::addComment(Location begin, Location end, | |
| CommentPlacement placement) { | |
| assert(collectComments_); | |
| const String& normalized = normalizeEOL(begin, end); | |
| if (placement == commentAfterOnSameLine) { | |
| assert(lastValue_ != nullptr); | |
| lastValue_->setComment(normalized, placement); | |
| } else { | |
| commentsBefore_ += normalized; | |
| } | |
| } | |
| bool OurReader::readCStyleComment(bool* containsNewLineResult) { | |
| *containsNewLineResult = false; | |
| while ((current_ + 1) < end_) { | |
| Char c = getNextChar(); | |
| if (c == '*' && *current_ == '/') | |
| break; | |
| if (c == '\n') | |
| *containsNewLineResult = true; | |
| } | |
| return getNextChar() == '/'; | |
| } | |
| bool OurReader::readCppStyleComment() { | |
| while (current_ != end_) { | |
| Char c = getNextChar(); | |
| if (c == '\n') | |
| break; | |
| if (c == '\r') { | |
| // Consume DOS EOL. It will be normalized in addComment. | |
| if (current_ != end_ && *current_ == '\n') | |
| getNextChar(); | |
| // Break on Moc OS 9 EOL. | |
| break; | |
| } | |
| } | |
| return true; | |
| } | |
| bool OurReader::readNumber(bool checkInf) { | |
| Location p = current_; | |
| if (checkInf && p != end_ && *p == 'I') { | |
| current_ = ++p; | |
| return false; | |
| } | |
| char c = '0'; // stopgap for already consumed character | |
| // integral part | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| // fractional part | |
| if (c == '.') { | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| } | |
| // exponential part | |
| if (c == 'e' || c == 'E') { | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| if (c == '+' || c == '-') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| while (c >= '0' && c <= '9') | |
| c = (current_ = p) < end_ ? *p++ : '\0'; | |
| } | |
| return true; | |
| } | |
| bool OurReader::readString() { | |
| Char c = 0; | |
| while (current_ != end_) { | |
| c = getNextChar(); | |
| if (c == '\\') | |
| getNextChar(); | |
| else if (c == '"') | |
| break; | |
| } | |
| return c == '"'; | |
| } | |
| bool OurReader::readStringSingleQuote() { | |
| Char c = 0; | |
| while (current_ != end_) { | |
| c = getNextChar(); | |
| if (c == '\\') | |
| getNextChar(); | |
| else if (c == '\'') | |
| break; | |
| } | |
| return c == '\''; | |
| } | |
| bool OurReader::readObject(Token& token) { | |
| Token tokenName; | |
| String name; | |
| Value init(objectValue); | |
| currentValue().swapPayload(init); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| while (readTokenSkippingComments(tokenName)) { | |
| if (tokenName.type_ == tokenObjectEnd && | |
| (name.empty() || | |
| features_.allowTrailingCommas_)) // empty object or trailing comma | |
| return true; | |
| name.clear(); | |
| if (tokenName.type_ == tokenString) { | |
| if (!decodeString(tokenName, name)) | |
| return recoverFromError(tokenObjectEnd); | |
| } else if (tokenName.type_ == tokenNumber && features_.allowNumericKeys_) { | |
| Value numberName; | |
| if (!decodeNumber(tokenName, numberName)) | |
| return recoverFromError(tokenObjectEnd); | |
| name = numberName.asString(); | |
| } else { | |
| break; | |
| } | |
| if (name.length() >= (1U << 30)) | |
| throwRuntimeError("keylength >= 2^30"); | |
| if (features_.rejectDupKeys_ && currentValue().isMember(name)) { | |
| String msg = "Duplicate key: '" + name + "'"; | |
| return addErrorAndRecover(msg, tokenName, tokenObjectEnd); | |
| } | |
| Token colon; | |
| if (!readToken(colon) || colon.type_ != tokenMemberSeparator) { | |
| return addErrorAndRecover("Missing ':' after object member name", colon, | |
| tokenObjectEnd); | |
| } | |
| Value& value = currentValue()[name]; | |
| nodes_.push(&value); | |
| bool ok = readValue(); | |
| nodes_.pop(); | |
| if (!ok) // error already set | |
| return recoverFromError(tokenObjectEnd); | |
| Token comma; | |
| if (!readTokenSkippingComments(comma) || | |
| (comma.type_ != tokenObjectEnd && comma.type_ != tokenArraySeparator)) { | |
| return addErrorAndRecover("Missing ',' or '}' in object declaration", | |
| comma, tokenObjectEnd); | |
| } | |
| if (comma.type_ == tokenObjectEnd) | |
| return true; | |
| } | |
| return addErrorAndRecover("Missing '}' or object member name", tokenName, | |
| tokenObjectEnd); | |
| } | |
| bool OurReader::readArray(Token& token) { | |
| Value init(arrayValue); | |
| currentValue().swapPayload(init); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| int index = 0; | |
| for (;;) { | |
| // Skip comments too, so a ']' that follows a trailing comma (or comments in | |
| // an otherwise empty array) is recognized rather than mistaken for the | |
| // start of another value. See issue #1500. | |
| skipCommentTokens(); | |
| if (current_ != end_ && *current_ == ']' && | |
| (index == 0 || | |
| (features_.allowTrailingCommas_ && | |
| !features_.allowDroppedNullPlaceholders_))) // empty array or trailing | |
| // comma | |
| { | |
| Token endArray; | |
| readToken(endArray); | |
| return true; | |
| } | |
| Value& value = currentValue()[index++]; | |
| nodes_.push(&value); | |
| bool ok = readValue(); | |
| nodes_.pop(); | |
| if (!ok) // error already set | |
| return recoverFromError(tokenArrayEnd); | |
| Token currentToken; | |
| // Accept Comment after last item in the array. | |
| ok = readTokenSkippingComments(currentToken); | |
| bool badTokenType = (currentToken.type_ != tokenArraySeparator && | |
| currentToken.type_ != tokenArrayEnd); | |
| if (!ok || badTokenType) { | |
| return addErrorAndRecover("Missing ',' or ']' in array declaration", | |
| currentToken, tokenArrayEnd); | |
| } | |
| if (currentToken.type_ == tokenArrayEnd) | |
| break; | |
| } | |
| return true; | |
| } | |
| bool OurReader::decodeNumber(Token& token) { | |
| Value decoded; | |
| if (!decodeNumber(token, decoded)) | |
| return false; | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool OurReader::decodeNumber(Token& token, Value& decoded) { | |
| // Attempts to parse the number as an integer. If the number is | |
| // larger than the maximum supported value of an integer then | |
| // we decode the number as a double. | |
| Location current = token.start_; | |
| const bool isNegative = *current == '-'; | |
| if (isNegative) { | |
| ++current; | |
| } | |
| // We assume we can represent the largest and smallest integer types as | |
| // unsigned integers with separate sign. This is only true if they can fit | |
| // into an unsigned integer. | |
| static_assert(Value::maxLargestInt <= Value::maxLargestUInt, | |
| "Int must be smaller than UInt"); | |
| // We need to convert minLargestInt into a positive number. The easiest way | |
| // to do this conversion is to assume our "threshold" value of minLargestInt | |
| // divided by 10 can fit in maxLargestInt when absolute valued. This should | |
| // be a safe assumption. | |
| static_assert(Value::minLargestInt <= -Value::maxLargestInt, | |
| "The absolute value of minLargestInt must be greater than or " | |
| "equal to maxLargestInt"); | |
| static_assert(Value::minLargestInt / 10 >= -Value::maxLargestInt, | |
| "The absolute value of minLargestInt must be only 1 magnitude " | |
| "larger than maxLargest Int"); | |
| static constexpr Value::LargestUInt positive_threshold = | |
| Value::maxLargestUInt / 10; | |
| static constexpr Value::UInt positive_last_digit = Value::maxLargestUInt % 10; | |
| // For the negative values, we have to be more careful. Since typically | |
| // -Value::minLargestInt will cause an overflow, we first divide by 10 and | |
| // then take the inverse. This assumes that minLargestInt is only a single | |
| // power of 10 different in magnitude, which we check above. For the last | |
| // digit, we take the modulus before negating for the same reason. | |
| static constexpr auto negative_threshold = | |
| Value::LargestUInt(-(Value::minLargestInt / 10)); | |
| static constexpr auto negative_last_digit = | |
| Value::UInt(-(Value::minLargestInt % 10)); | |
| const Value::LargestUInt threshold = | |
| isNegative ? negative_threshold : positive_threshold; | |
| const Value::UInt max_last_digit = | |
| isNegative ? negative_last_digit : positive_last_digit; | |
| Value::LargestUInt value = 0; | |
| while (current < token.end_) { | |
| Char c = *current++; | |
| if (c < '0' || c > '9') | |
| return decodeDouble(token, decoded); | |
| const auto digit(static_cast<Value::UInt>(c - '0')); | |
| if (value >= threshold) { | |
| // We've hit or exceeded the max value divided by 10 (rounded down). If | |
| // a) we've only just touched the limit, meaning value == threshold, | |
| // b) this is the last digit, or | |
| // c) it's small enough to fit in that rounding delta, we're okay. | |
| // Otherwise treat this number as a double to avoid overflow. | |
| if (value > threshold || current != token.end_ || | |
| digit > max_last_digit) { | |
| return decodeDouble(token, decoded); | |
| } | |
| } | |
| value = value * 10 + digit; | |
| } | |
| if (isNegative) { | |
| // We use the same magnitude assumption here, just in case. | |
| const auto last_digit = static_cast<Value::UInt>(value % 10); | |
| decoded = -Value::LargestInt(value / 10) * 10 - last_digit; | |
| } else if (value <= Value::LargestUInt(Value::maxLargestInt)) { | |
| decoded = Value::LargestInt(value); | |
| } else { | |
| decoded = value; | |
| } | |
| return true; | |
| } | |
| bool OurReader::decodeDouble(Token& token) { | |
| Value decoded; | |
| if (!decodeDouble(token, decoded)) | |
| return false; | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool OurReader::decodeDouble(Token& token, Value& decoded) { | |
| double value = 0; | |
| IStringStream is(String(token.start_, token.end_)); | |
| is.imbue(std::locale::classic()); | |
| if (!(is >> value)) { | |
| if (value == std::numeric_limits<double>::max()) | |
| value = std::numeric_limits<double>::infinity(); | |
| else if (value == std::numeric_limits<double>::lowest()) | |
| value = -std::numeric_limits<double>::infinity(); | |
| // operator>> sets failbit for a subnormal result (underflow) even though | |
| // it produced the correctly-rounded value, which made such numbers fail to | |
| // parse back after jsoncpp serialized them. Keep a subnormal value instead | |
| // of rejecting it. See issue #1427. Other failures -- malformed numbers | |
| // like "0e" or "0e+", or non-numbers -- leave the value at zero/non-finite | |
| // and are still rejected. | |
| else if (!std::isinf(value) && std::fpclassify(value) != FP_SUBNORMAL) | |
| return addError( | |
| "'" + String(token.start_, token.end_) + "' is not a number.", token); | |
| } | |
| decoded = value; | |
| return true; | |
| } | |
| bool OurReader::decodeString(Token& token) { | |
| String decoded_string; | |
| if (!decodeString(token, decoded_string)) | |
| return false; | |
| Value decoded(decoded_string); | |
| currentValue().swapPayload(decoded); | |
| currentValue().setOffsetStart(token.start_ - begin_); | |
| currentValue().setOffsetLimit(token.end_ - begin_); | |
| return true; | |
| } | |
| bool OurReader::decodeString(Token& token, String& decoded) { | |
| decoded.reserve(static_cast<size_t>(token.end_ - token.start_ - 2)); | |
| Location current = token.start_ + 1; // skip '"' | |
| Location end = token.end_ - 1; // do not include '"' | |
| while (current != end) { | |
| Char c = *current++; | |
| if (c == '"') | |
| break; | |
| if (c == '\\') { | |
| if (current == end) | |
| return addError("Empty escape sequence in string", token, current); | |
| Char escape = *current++; | |
| switch (escape) { | |
| case '"': | |
| decoded += '"'; | |
| break; | |
| case '/': | |
| decoded += '/'; | |
| break; | |
| case '\\': | |
| decoded += '\\'; | |
| break; | |
| case 'b': | |
| decoded += '\b'; | |
| break; | |
| case 'f': | |
| decoded += '\f'; | |
| break; | |
| case 'n': | |
| decoded += '\n'; | |
| break; | |
| case 'r': | |
| decoded += '\r'; | |
| break; | |
| case 't': | |
| decoded += '\t'; | |
| break; | |
| case 'u': { | |
| unsigned int unicode; | |
| if (!decodeUnicodeCodePoint(token, current, end, unicode)) | |
| return false; | |
| decoded += codePointToUTF8(unicode); | |
| } break; | |
| default: | |
| return addError("Bad escape sequence in string", token, current); | |
| } | |
| } else { | |
| if (static_cast<unsigned char>(c) < 0x20) | |
| return addError("Control character in string", token, current - 1); | |
| decoded += c; | |
| } | |
| } | |
| return true; | |
| } | |
| bool OurReader::decodeUnicodeCodePoint(Token& token, Location& current, | |
| Location end, unsigned int& unicode) { | |
| if (!decodeUnicodeEscapeSequence(token, current, end, unicode)) | |
| return false; | |
| if (unicode >= 0xD800 && unicode <= 0xDBFF) { | |
| // surrogate pairs | |
| if (end - current < 6) | |
| return addError( | |
| "additional six characters expected to parse unicode surrogate pair.", | |
| token, current); | |
| if (*(current++) == '\\' && *(current++) == 'u') { | |
| unsigned int surrogatePair; | |
| if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair)) { | |
| unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); | |
| } else | |
| return false; | |
| } else | |
| return addError("expecting another \\u token to begin the second half of " | |
| "a unicode surrogate pair", | |
| token, current); | |
| } | |
| return true; | |
| } | |
| bool OurReader::decodeUnicodeEscapeSequence(Token& token, Location& current, | |
| Location end, | |
| unsigned int& ret_unicode) { | |
| if (end - current < 4) | |
| return addError( | |
| "Bad unicode escape sequence in string: four digits expected.", token, | |
| current); | |
| int unicode = 0; | |
| for (int index = 0; index < 4; ++index) { | |
| Char c = *current++; | |
| unicode *= 16; | |
| if (c >= '0' && c <= '9') | |
| unicode += c - '0'; | |
| else if (c >= 'a' && c <= 'f') | |
| unicode += c - 'a' + 10; | |
| else if (c >= 'A' && c <= 'F') | |
| unicode += c - 'A' + 10; | |
| else | |
| return addError( | |
| "Bad unicode escape sequence in string: hexadecimal digit expected.", | |
| token, current); | |
| } | |
| ret_unicode = static_cast<unsigned int>(unicode); | |
| return true; | |
| } | |
| bool OurReader::addError(const String& message, Token& token, Location extra) { | |
| ErrorInfo info; | |
| info.token_ = token; | |
| info.message_ = message; | |
| info.extra_ = extra; | |
| errors_.push_back(info); | |
| return false; | |
| } | |
| bool OurReader::recoverFromError(TokenType skipUntilToken) { | |
| size_t errorCount = errors_.size(); | |
| Token skip; | |
| for (;;) { | |
| if (!readToken(skip)) | |
| errors_.resize(errorCount); // discard errors caused by recovery | |
| if (skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream) | |
| break; | |
| } | |
| errors_.resize(errorCount); | |
| return false; | |
| } | |
| bool OurReader::addErrorAndRecover(const String& message, Token& token, | |
| TokenType skipUntilToken) { | |
| addError(message, token); | |
| return recoverFromError(skipUntilToken); | |
| } | |
| Value& OurReader::currentValue() { return *(nodes_.top()); } | |
| OurReader::Char OurReader::getNextChar() { | |
| if (current_ == end_) | |
| return 0; | |
| return *current_++; | |
| } | |
| void OurReader::getLocationLineAndColumn(Location location, int& line, | |
| int& column) const { | |
| Location current = begin_; | |
| Location lastLineStart = current; | |
| line = 0; | |
| while (current < location && current != end_) { | |
| Char c = *current++; | |
| if (c == '\r') { | |
| if (current != end_ && *current == '\n') | |
| ++current; | |
| lastLineStart = current; | |
| ++line; | |
| } else if (c == '\n') { | |
| lastLineStart = current; | |
| ++line; | |
| } | |
| } | |
| // column & line start at 1 | |
| column = int(location - lastLineStart) + 1; | |
| ++line; | |
| } | |
| String OurReader::getLocationLineAndColumn(Location location) const { | |
| int line, column; | |
| getLocationLineAndColumn(location, line, column); | |
| char buffer[18 + 16 + 16 + 1]; | |
| jsoncpp_snprintf(buffer, sizeof(buffer), "Line %d, Column %d", line, column); | |
| return buffer; | |
| } | |
| String OurReader::getFormattedErrorMessages() const { | |
| String formattedMessage; | |
| for (const auto& error : errors_) { | |
| formattedMessage += | |
| "* " + getLocationLineAndColumn(error.token_.start_) + "\n"; | |
| formattedMessage += " " + error.message_ + "\n"; | |
| if (error.extra_) | |
| formattedMessage += | |
| "See " + getLocationLineAndColumn(error.extra_) + " for detail.\n"; | |
| } | |
| return formattedMessage; | |
| } | |
| std::vector<CharReader::StructuredError> | |
| OurReader::getStructuredErrors() const { | |
| std::vector<CharReader::StructuredError> allErrors; | |
| for (const auto& error : errors_) { | |
| CharReader::StructuredError structured; | |
| structured.offset_start = error.token_.start_ - begin_; | |
| structured.offset_limit = error.token_.end_ - begin_; | |
| structured.message = error.message_; | |
| allErrors.push_back(structured); | |
| } | |
| return allErrors; | |
| } | |
| class OurCharReader : public CharReader { | |
| public: | |
| OurCharReader(bool collectComments, OurFeatures const& features) | |
| : CharReader( | |
| std::unique_ptr<OurImpl>(new OurImpl(collectComments, features))) {} | |
| protected: | |
| class OurImpl : public Impl { | |
| public: | |
| OurImpl(bool collectComments, OurFeatures const& features) | |
| : collectComments_(collectComments), reader_(features) {} | |
| bool parse(char const* beginDoc, char const* endDoc, Value* root, | |
| String* errs) override { | |
| bool ok = reader_.parse(beginDoc, endDoc, *root, collectComments_); | |
| if (errs) { | |
| *errs = reader_.getFormattedErrorMessages(); | |
| } | |
| return ok; | |
| } | |
| std::vector<CharReader::StructuredError> | |
| getStructuredErrors() const override { | |
| return reader_.getStructuredErrors(); | |
| } | |
| private: | |
| bool const collectComments_; | |
| OurReader reader_; | |
| }; | |
| }; | |
| CharReaderBuilder::CharReaderBuilder() { setDefaults(&settings_); } | |
| CharReaderBuilder::~CharReaderBuilder() = default; | |
| CharReader* CharReaderBuilder::newCharReader() const { | |
| bool collectComments = settings_["collectComments"].asBool(); | |
| OurFeatures features = OurFeatures::all(); | |
| features.allowComments_ = settings_["allowComments"].asBool(); | |
| features.allowTrailingCommas_ = settings_["allowTrailingCommas"].asBool(); | |
| features.strictRoot_ = settings_["strictRoot"].asBool(); | |
| features.allowDroppedNullPlaceholders_ = | |
| settings_["allowDroppedNullPlaceholders"].asBool(); | |
| features.allowNumericKeys_ = settings_["allowNumericKeys"].asBool(); | |
| features.allowSingleQuotes_ = settings_["allowSingleQuotes"].asBool(); | |
| // Stack limit is always a size_t, so we get this as an unsigned int | |
| // regardless of it we have 64-bit integer support enabled. | |
| features.stackLimit_ = static_cast<size_t>(settings_["stackLimit"].asUInt()); | |
| features.failIfExtra_ = settings_["failIfExtra"].asBool(); | |
| features.rejectDupKeys_ = settings_["rejectDupKeys"].asBool(); | |
| features.allowSpecialFloats_ = settings_["allowSpecialFloats"].asBool(); | |
| features.skipBom_ = settings_["skipBom"].asBool(); | |
| return new OurCharReader(collectComments, features); | |
| } | |
| bool CharReaderBuilder::validate(Json::Value* invalid) const { | |
| static const auto& valid_keys = *new std::set<String>{ | |
| "collectComments", | |
| "allowComments", | |
| "allowTrailingCommas", | |
| "strictRoot", | |
| "allowDroppedNullPlaceholders", | |
| "allowNumericKeys", | |
| "allowSingleQuotes", | |
| "stackLimit", | |
| "failIfExtra", | |
| "rejectDupKeys", | |
| "allowSpecialFloats", | |
| "skipBom", | |
| }; | |
| for (auto si = settings_.begin(); si != settings_.end(); ++si) { | |
| auto key = si.name(); | |
| if (valid_keys.count(key)) | |
| continue; | |
| if (invalid) | |
| (*invalid)[key] = *si; | |
| else | |
| return false; | |
| } | |
| return invalid ? invalid->empty() : true; | |
| } | |
| Value& CharReaderBuilder::operator[](const String& key) { | |
| return settings_[key]; | |
| } | |
| // static | |
| void CharReaderBuilder::strictMode(Json::Value* settings) { | |
| //! [CharReaderBuilderStrictMode] | |
| (*settings)["allowComments"] = false; | |
| (*settings)["allowTrailingCommas"] = false; | |
| (*settings)["strictRoot"] = true; | |
| (*settings)["allowDroppedNullPlaceholders"] = false; | |
| (*settings)["allowNumericKeys"] = false; | |
| (*settings)["allowSingleQuotes"] = false; | |
| (*settings)["stackLimit"] = 256; | |
| (*settings)["failIfExtra"] = true; | |
| (*settings)["rejectDupKeys"] = true; | |
| (*settings)["allowSpecialFloats"] = false; | |
| (*settings)["skipBom"] = true; | |
| //! [CharReaderBuilderStrictMode] | |
| } | |
| // static | |
| void CharReaderBuilder::setDefaults(Json::Value* settings) { | |
| //! [CharReaderBuilderDefaults] | |
| (*settings)["collectComments"] = true; | |
| (*settings)["allowComments"] = true; | |
| (*settings)["allowTrailingCommas"] = true; | |
| (*settings)["strictRoot"] = false; | |
| (*settings)["allowDroppedNullPlaceholders"] = false; | |
| (*settings)["allowNumericKeys"] = false; | |
| (*settings)["allowSingleQuotes"] = false; | |
| (*settings)["stackLimit"] = 256; | |
| (*settings)["failIfExtra"] = false; | |
| (*settings)["rejectDupKeys"] = false; | |
| (*settings)["allowSpecialFloats"] = false; | |
| (*settings)["skipBom"] = true; | |
| //! [CharReaderBuilderDefaults] | |
| } | |
| // static | |
| void CharReaderBuilder::ecma404Mode(Json::Value* settings) { | |
| //! [CharReaderBuilderECMA404Mode] | |
| (*settings)["allowComments"] = false; | |
| (*settings)["allowTrailingCommas"] = false; | |
| (*settings)["strictRoot"] = false; | |
| (*settings)["allowDroppedNullPlaceholders"] = false; | |
| (*settings)["allowNumericKeys"] = false; | |
| (*settings)["allowSingleQuotes"] = false; | |
| (*settings)["stackLimit"] = 256; | |
| (*settings)["failIfExtra"] = true; | |
| (*settings)["rejectDupKeys"] = false; | |
| (*settings)["allowSpecialFloats"] = false; | |
| (*settings)["skipBom"] = false; | |
| //! [CharReaderBuilderECMA404Mode] | |
| } | |
| std::vector<CharReader::StructuredError> | |
| CharReader::getStructuredErrors() const { | |
| return _impl->getStructuredErrors(); | |
| } | |
| bool CharReader::parse(char const* beginDoc, char const* endDoc, Value* root, | |
| String* errs) { | |
| return _impl->parse(beginDoc, endDoc, root, errs); | |
| } | |
| ////////////////////////////////// | |
| // global functions | |
| bool parseFromStream(CharReader::Factory const& fact, IStream& sin, Value* root, | |
| String* errs) { | |
| OStringStream ssin; | |
| ssin << sin.rdbuf(); | |
| String doc = std::move(ssin).str(); | |
| char const* begin = doc.data(); | |
| char const* end = begin + doc.size(); | |
| // Note that we do not actually need a null-terminator. | |
| CharReaderPtr const reader(fact.newCharReader()); | |
| return reader->parse(begin, end, root, errs); | |
| } | |
| IStream& operator>>(IStream& sin, Value& root) { | |
| CharReaderBuilder b; | |
| String errs; | |
| bool ok = parseFromStream(b, sin, &root, &errs); | |
| if (!ok) { | |
| throwRuntimeError(errs); | |
| } | |
| return sin; | |
| } | |
| } // namespace Json |