/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include "qv4string_p.h" #include "qv4jscall_p.h" #include #include #include #include using namespace QV4; //#define PARSER_DEBUG #ifdef PARSER_DEBUG static int indent = 0; #define BEGIN qDebug() << QByteArray(4*indent++, ' ').constData() #define END --indent #define DEBUG qDebug() << QByteArray(4*indent, ' ').constData() #else #define BEGIN if (1) ; else qDebug() #define END do {} while (0) #define DEBUG if (1) ; else qDebug() #endif DEFINE_OBJECT_VTABLE(JsonObject); static const int nestingLimit = 1024; JsonParser::JsonParser(ExecutionEngine *engine, const QChar *json, int length) : engine(engine), head(json), json(json), nestingLevel(0), lastError(QJsonParseError::NoError) { end = json + length; } /* begin-array = ws %x5B ws ; [ left square bracket begin-object = ws %x7B ws ; { left curly bracket end-array = ws %x5D ws ; ] right square bracket end-object = ws %x7D ws ; } right curly bracket name-separator = ws %x3A ws ; : colon value-separator = ws %x2C ws ; , comma Insignificant whitespace is allowed before or after any of the six structural characters. ws = *( %x20 / ; Space %x09 / ; Horizontal tab %x0A / ; Line feed or New line %x0D ; Carriage return ) */ enum { Space = 0x20, Tab = 0x09, LineFeed = 0x0a, Return = 0x0d, BeginArray = 0x5b, BeginObject = 0x7b, EndArray = 0x5d, EndObject = 0x7d, NameSeparator = 0x3a, ValueSeparator = 0x2c, Quote = 0x22 }; bool JsonParser::eatSpace() { while (json < end) { if (*json > Space) break; if (*json != Space && *json != Tab && *json != LineFeed && *json != Return) break; ++json; } return (json < end); } QChar JsonParser::nextToken() { if (!eatSpace()) return 0; QChar token = *json++; switch (token.unicode()) { case BeginArray: case BeginObject: case NameSeparator: case ValueSeparator: case EndArray: case EndObject: eatSpace(); case Quote: break; default: token = 0; break; } return token; } /* JSON-text = object / array */ ReturnedValue JsonParser::parse(QJsonParseError *error) { #ifdef PARSER_DEBUG indent = 0; qDebug() << ">>>>> parser begin"; #endif eatSpace(); Scope scope(engine); ScopedValue v(scope); if (!parseValue(v)) { #ifdef PARSER_DEBUG qDebug() << ">>>>> parser error"; #endif if (lastError == QJsonParseError::NoError) lastError = QJsonParseError::IllegalValue; error->offset = json - head; error->error = lastError; return Encode::undefined(); } // some input left... if (eatSpace()) { lastError = QJsonParseError::IllegalValue; error->offset = json - head; error->error = lastError; return Encode::undefined(); } END; error->offset = 0; error->error = QJsonParseError::NoError; return v->asReturnedValue(); } /* object = begin-object [ member *( value-separator member ) ] end-object */ ReturnedValue JsonParser::parseObject() { if (++nestingLevel > nestingLimit) { lastError = QJsonParseError::DeepNesting; return Encode::undefined(); } BEGIN << "parseObject pos=" << json; Scope scope(engine); ScopedObject o(scope, engine->newObject()); QChar token = nextToken(); while (token == Quote) { if (!parseMember(o)) return Encode::undefined(); token = nextToken(); if (token != ValueSeparator) break; token = nextToken(); if (token == EndObject) { lastError = QJsonParseError::MissingObject; return Encode::undefined(); } } DEBUG << "end token=" << token; if (token != EndObject) { lastError = QJsonParseError::UnterminatedObject; return Encode::undefined(); } END; --nestingLevel; return o.asReturnedValue(); } /* member = string name-separator value */ bool JsonParser::parseMember(Object *o) { BEGIN << "parseMember"; Scope scope(engine); QString key; if (!parseString(&key)) return false; QChar token = nextToken(); if (token != NameSeparator) { lastError = QJsonParseError::MissingNameSeparator; return false; } ScopedValue val(scope); if (!parseValue(val)) return false; ScopedString s(scope, engine->newString(key)); PropertyKey skey = s->toPropertyKey(); if (skey.isArrayIndex()) { o->put(skey.asArrayIndex(), val); } else { // avoid trouble with properties named __proto__ o->insertMember(s, val); } END; return true; } /* array = begin-array [ value *( value-separator value ) ] end-array */ ReturnedValue JsonParser::parseArray() { Scope scope(engine); BEGIN << "parseArray"; ScopedArrayObject array(scope, engine->newArrayObject()); if (++nestingLevel > nestingLimit) { lastError = QJsonParseError::DeepNesting; return Encode::undefined(); } if (!eatSpace()) { lastError = QJsonParseError::UnterminatedArray; return Encode::undefined(); } if (*json == EndArray) { nextToken(); } else { uint index = 0; while (1) { ScopedValue val(scope); if (!parseValue(val)) return Encode::undefined(); array->arraySet(index, val); QChar token = nextToken(); if (token == EndArray) break; else if (token != ValueSeparator) { if (!eatSpace()) lastError = QJsonParseError::UnterminatedArray; else lastError = QJsonParseError::MissingValueSeparator; return Encode::undefined(); } ++index; } } DEBUG << "size =" << array->getLength(); END; --nestingLevel; return array.asReturnedValue(); } /* value = false / null / true / object / array / number / string */ bool JsonParser::parseValue(Value *val) { BEGIN << "parse Value" << *json; switch ((json++)->unicode()) { case 'n': if (end - json < 3) { lastError = QJsonParseError::IllegalValue; return false; } if (*json++ == 'u' && *json++ == 'l' && *json++ == 'l') { *val = Value::nullValue(); DEBUG << "value: null"; END; return true; } lastError = QJsonParseError::IllegalValue; return false; case 't': if (end - json < 3) { lastError = QJsonParseError::IllegalValue; return false; } if (*json++ == 'r' && *json++ == 'u' && *json++ == 'e') { *val = Value::fromBoolean(true); DEBUG << "value: true"; END; return true; } lastError = QJsonParseError::IllegalValue; return false; case 'f': if (end - json < 4) { lastError = QJsonParseError::IllegalValue; return false; } if (*json++ == 'a' && *json++ == 'l' && *json++ == 's' && *json++ == 'e') { *val = Value::fromBoolean(false); DEBUG << "value: false"; END; return true; } lastError = QJsonParseError::IllegalValue; return false; case Quote: { QString value; if (!parseString(&value)) return false; DEBUG << "value: string"; END; *val = Value::fromHeapObject(engine->newString(value)); return true; } case BeginArray: { *val = parseArray(); if (val->isUndefined()) return false; DEBUG << "value: array"; END; return true; } case BeginObject: { *val = parseObject(); if (val->isUndefined()) return false; DEBUG << "value: object"; END; return true; } case EndArray: lastError = QJsonParseError::MissingObject; return false; default: --json; if (!parseNumber(val)) return false; DEBUG << "value: number"; END; } return true; } /* number = [ minus ] int [ frac ] [ exp ] decimal-point = %x2E ; . digit1-9 = %x31-39 ; 1-9 e = %x65 / %x45 ; e E exp = e [ minus / plus ] 1*DIGIT frac = decimal-point 1*DIGIT int = zero / ( digit1-9 *DIGIT ) minus = %x2D ; - plus = %x2B ; + zero = %x30 ; 0 */ bool JsonParser::parseNumber(Value *val) { BEGIN << "parseNumber" << *json; const QChar *start = json; bool isInt = true; // minus if (json < end && *json == '-') ++json; // int = zero / ( digit1-9 *DIGIT ) if (json < end && *json == '0') { ++json; } else { while (json < end && *json >= '0' && *json <= '9') ++json; } // frac = decimal-point 1*DIGIT if (json < end && *json == '.') { isInt = false; ++json; while (json < end && *json >= '0' && *json <= '9') ++json; } // exp = e [ minus / plus ] 1*DIGIT if (json < end && (*json == 'e' || *json == 'E')) { isInt = false; ++json; if (json < end && (*json == '-' || *json == '+')) ++json; while (json < end && *json >= '0' && *json <= '9') ++json; } QString number(start, json - start); DEBUG << "numberstring" << number; if (isInt) { bool ok; int n = number.toInt(&ok); if (ok && n < (1<<25) && n > -(1<<25)) { *val = Value::fromInt32(n); END; return true; } } bool ok; double d; d = number.toDouble(&ok); if (!ok) { lastError = QJsonParseError::IllegalNumber; return false; } * val = Value::fromDouble(d); END; return true; } /* string = quotation-mark *char quotation-mark char = unescaped / escape ( %x22 / ; " quotation mark U+0022 %x5C / ; \ reverse solidus U+005C %x2F / ; / solidus U+002F %x62 / ; b backspace U+0008 %x66 / ; f form feed U+000C %x6E / ; n line feed U+000A %x72 / ; r carriage return U+000D %x74 / ; t tab U+0009 %x75 4HEXDIG ) ; uXXXX U+XXXX escape = %x5C ; \ quotation-mark = %x22 ; " unescaped = %x20-21 / %x23-5B / %x5D-10FFFF */ static inline bool addHexDigit(QChar digit, uint *result) { ushort d = digit.unicode(); *result <<= 4; if (d >= '0' && d <= '9') *result |= (d - '0'); else if (d >= 'a' && d <= 'f') *result |= (d - 'a') + 10; else if (d >= 'A' && d <= 'F') *result |= (d - 'A') + 10; else return false; return true; } static inline bool scanEscapeSequence(const QChar *&json, const QChar *end, uint *ch) { ++json; if (json >= end) return false; DEBUG << "scan escape"; uint escaped = (json++)->unicode(); switch (escaped) { case '"': *ch = '"'; break; case '\\': *ch = '\\'; break; case '/': *ch = '/'; break; case 'b': *ch = 0x8; break; case 'f': *ch = 0xc; break; case 'n': *ch = 0xa; break; case 'r': *ch = 0xd; break; case 't': *ch = 0x9; break; case 'u': { *ch = 0; if (json > end - 4) return false; for (int i = 0; i < 4; ++i) { if (!addHexDigit(*json, ch)) return false; ++json; } return true; } default: return false; } return true; } bool JsonParser::parseString(QString *string) { BEGIN << "parse string stringPos=" << json; while (json < end) { if (*json == '"') break; else if (*json == '\\') { uint ch = 0; if (!scanEscapeSequence(json, end, &ch)) { lastError = QJsonParseError::IllegalEscapeSequence; return false; } if (QChar::requiresSurrogates(ch)) { *string += QChar(QChar::highSurrogate(ch)) + QChar(QChar::lowSurrogate(ch)); } else { *string += QChar(ch); } } else { if (json->unicode() <= 0x1f) { lastError = QJsonParseError::IllegalEscapeSequence; return false; } *string += *json; ++json; } } ++json; if (json > end) { lastError = QJsonParseError::UnterminatedString; return false; } END; return true; } struct Stringify { ExecutionEngine *v4; FunctionObject *replacerFunction; QV4::String *propertyList; int propertyListSize; QString gap; QString indent; QStack stack; bool stackContains(Object *o) { for (int i = 0; i < stack.size(); ++i) if (stack.at(i)->d() == o->d()) return true; return false; } Stringify(ExecutionEngine *e) : v4(e), replacerFunction(nullptr), propertyList(nullptr), propertyListSize(0) {} QString Str(const QString &key, const Value &v); QString JA(Object *a); QString JO(Object *o); QString makeMember(const QString &key, const Value &v); }; static QString quote(const QString &str) { QString product; const int length = str.length(); product.reserve(length + 2); product += QLatin1Char('"'); for (int i = 0; i < length; ++i) { QChar c = str.at(i); switch (c.unicode()) { case '"': product += QLatin1String("\\\""); break; case '\\': product += QLatin1String("\\\\"); break; case '\b': product += QLatin1String("\\b"); break; case '\f': product += QLatin1String("\\f"); break; case '\n': product += QLatin1String("\\n"); break; case '\r': product += QLatin1String("\\r"); break; case '\t': product += QLatin1String("\\t"); break; default: if (c.unicode() <= 0x1f) { product += QLatin1String("\\u00"); product += (c.unicode() > 0xf ? QLatin1Char('1') : QLatin1Char('0')) + QLatin1Char("0123456789abcdef"[c.unicode() & 0xf]); } else { product += c; } } } product += QLatin1Char('"'); return product; } QString Stringify::Str(const QString &key, const Value &v) { Scope scope(v4); ScopedValue value(scope, v); ScopedObject o(scope, value); if (o) { ScopedString s(scope, v4->newString(QStringLiteral("toJSON"))); ScopedFunctionObject toJSON(scope, o->get(s)); if (!!toJSON) { JSCallData jsCallData(scope, 1); *jsCallData->thisObject = value; jsCallData->args[0] = v4->newString(key); value = toJSON->call(jsCallData); } } if (replacerFunction) { ScopedObject holder(scope, v4->newObject()); holder->put(scope.engine->id_empty(), value); JSCallData jsCallData(scope, 2); jsCallData->args[0] = v4->newString(key); jsCallData->args[1] = value; *jsCallData->thisObject = holder; value = replacerFunction->call(jsCallData); } o = value->asReturnedValue(); if (o) { if (NumberObject *n = o->as()) value = Encode(n->value()); else if (StringObject *so = o->as()) value = so->d()->string; else if (BooleanObject *b = o->as()) value = Encode(b->value()); } if (value->isNull()) return QStringLiteral("null"); if (value->isBoolean()) return value->booleanValue() ? QStringLiteral("true") : QStringLiteral("false"); if (value->isString()) return quote(value->stringValue()->toQString()); if (value->isNumber()) { double d = value->toNumber(); return std::isfinite(d) ? value->toQString() : QStringLiteral("null"); } if (const QV4::VariantObject *v = value->as()) { return quote(v->d()->data().toString()); } o = value->asReturnedValue(); if (o) { if (!o->as()) { if (o->isArrayLike()) { return JA(o.getPointer()); } else { return JO(o); } } } return QString(); } QString Stringify::makeMember(const QString &key, const Value &v) { QString strP = Str(key, v); if (!strP.isEmpty()) { QString member = quote(key) + QLatin1Char(':'); if (!gap.isEmpty()) member += QLatin1Char(' '); member += strP; return member; } return QString(); } QString Stringify::JO(Object *o) { if (stackContains(o)) { v4->throwTypeError(); return QString(); } Scope scope(v4); QString result; stack.push(o); QString stepback = indent; indent += gap; QStringList partial; if (!propertyListSize) { ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); ScopedValue name(scope); ScopedValue val(scope); while (1) { name = it.nextPropertyNameAsString(val); if (name->isNull()) break; QString key = name->toQString(); QString member = makeMember(key, val); if (!member.isEmpty()) partial += member; } } else { ScopedValue v(scope); for (int i = 0; i < propertyListSize; ++i) { bool exists; String *s = propertyList + i; if (!s) continue; v = o->get(s, &exists); if (!exists) continue; QString member = makeMember(s->toQString(), v); if (!member.isEmpty()) partial += member; } } if (partial.isEmpty()) { result = QStringLiteral("{}"); } else if (gap.isEmpty()) { result = QLatin1Char('{') + partial.join(QLatin1Char(',')) + QLatin1Char('}'); } else { QString separator = QLatin1String(",\n") + indent; result = QLatin1String("{\n") + indent + partial.join(separator) + QLatin1Char('\n') + stepback + QLatin1Char('}'); } indent = stepback; stack.pop(); return result; } QString Stringify::JA(Object *a) { if (stackContains(a)) { v4->throwTypeError(); return QString(); } Scope scope(a->engine()); QString result; stack.push(a); QString stepback = indent; indent += gap; QStringList partial; uint len = a->getLength(); ScopedValue v(scope); for (uint i = 0; i < len; ++i) { bool exists; v = a->get(i, &exists); if (!exists) { partial += QStringLiteral("null"); continue; } QString strP = Str(QString::number(i), v); if (!strP.isEmpty()) partial += strP; else partial += QStringLiteral("null"); } if (partial.isEmpty()) { result = QStringLiteral("[]"); } else if (gap.isEmpty()) { result = QLatin1Char('[') + partial.join(QLatin1Char(',')) + QLatin1Char(']'); } else { QString separator = QLatin1String(",\n") + indent; result = QLatin1String("[\n") + indent + partial.join(separator) + QLatin1Char('\n') + stepback + QLatin1Char(']'); } indent = stepback; stack.pop(); return result; } void Heap::JsonObject::init() { Object::init(); Scope scope(internalClass->engine); ScopedObject o(scope, this); o->defineDefaultProperty(QStringLiteral("parse"), QV4::JsonObject::method_parse, 2); o->defineDefaultProperty(QStringLiteral("stringify"), QV4::JsonObject::method_stringify, 3); ScopedString json(scope, scope.engine->newString(QStringLiteral("JSON"))); o->defineReadonlyConfigurableProperty(scope.engine->symbol_toStringTag(), json); } ReturnedValue JsonObject::method_parse(const FunctionObject *b, const Value *, const Value *argv, int argc) { ExecutionEngine *v4 = b->engine(); QString jtext; if (argc > 0) jtext = argv[0].toQString(); DEBUG << "parsing source = " << jtext; JsonParser parser(v4, jtext.constData(), jtext.length()); QJsonParseError error; ReturnedValue result = parser.parse(&error); if (error.error != QJsonParseError::NoError) { DEBUG << "parse error" << error.errorString(); RETURN_RESULT(v4->throwSyntaxError(QStringLiteral("JSON.parse: Parse error"))); } return result; } ReturnedValue JsonObject::method_stringify(const FunctionObject *b, const Value *, const Value *argv, int argc) { Scope scope(b); Stringify stringify(scope.engine); ScopedObject o(scope, argc > 1 ? argv[1] : Value::undefinedValue()); if (o) { stringify.replacerFunction = o->as(); if (o->isArrayObject()) { uint arrayLen = o->getLength(); stringify.propertyList = static_cast(scope.alloc(arrayLen)); for (uint i = 0; i < arrayLen; ++i) { Value *v = stringify.propertyList + i; *v = o->get(i); if (v->as() || v->as() || v->isNumber()) *v = v->toString(scope.engine); if (!v->isString()) { v->setM(nullptr); } else { for (uint j = 0; j m()) { v->setM(nullptr); break; } } } } } } ScopedValue s(scope, argc > 2 ? argv[2] : Value::undefinedValue()); if (NumberObject *n = s->as()) s = Encode(n->value()); else if (StringObject *so = s->as()) s = so->d()->string; if (s->isNumber()) { stringify.gap = QString(qMin(10, (int)s->toInteger()), ' '); } else if (String *str = s->stringValue()) { stringify.gap = str->toQString().left(10); } ScopedValue arg0(scope, argc ? argv[0] : Value::undefinedValue()); QString result = stringify.Str(QString(), arg0); if (result.isEmpty() || scope.engine->hasException) RETURN_UNDEFINED(); return Encode(scope.engine->newString(result)); } ReturnedValue JsonObject::fromJsonValue(ExecutionEngine *engine, const QJsonValue &value) { if (value.isString()) return engine->newString(value.toString())->asReturnedValue(); else if (value.isDouble()) return Encode(value.toDouble()); else if (value.isBool()) return Encode(value.toBool()); else if (value.isArray()) return fromJsonArray(engine, value.toArray()); else if (value.isObject()) return fromJsonObject(engine, value.toObject()); else if (value.isNull()) return Encode::null(); else return Encode::undefined(); } QJsonValue JsonObject::toJsonValue(const Value &value, V4ObjectSet &visitedObjects) { if (value.isNumber()) return QJsonValue(value.toNumber()); else if (value.isBoolean()) return QJsonValue((bool)value.booleanValue()); else if (value.isNull()) return QJsonValue(QJsonValue::Null); else if (value.isUndefined()) return QJsonValue(QJsonValue::Undefined); else if (String *s = value.stringValue()) return QJsonValue(s->toQString()); Q_ASSERT(value.isObject()); Scope scope(value.as()->engine()); ScopedArrayObject a(scope, value); if (a) return toJsonArray(a, visitedObjects); ScopedObject o(scope, value); if (o) return toJsonObject(o, visitedObjects); return QJsonValue(value.toQString()); } QV4::ReturnedValue JsonObject::fromJsonObject(ExecutionEngine *engine, const QJsonObject &object) { Scope scope(engine); ScopedObject o(scope, engine->newObject()); ScopedString s(scope); ScopedValue v(scope); for (QJsonObject::const_iterator it = object.begin(), cend = object.end(); it != cend; ++it) { v = fromJsonValue(engine, it.value()); o->put((s = engine->newString(it.key())), v); } return o.asReturnedValue(); } QJsonObject JsonObject::toJsonObject(const Object *o, V4ObjectSet &visitedObjects) { QJsonObject result; if (!o || o->as()) return result; Scope scope(o->engine()); if (visitedObjects.contains(ObjectItem(o))) { // Avoid recursion. // For compatibility with QVariant{List,Map} conversion, we return an // empty object (and no error is thrown). return result; } visitedObjects.insert(ObjectItem(o)); ObjectIterator it(scope, o, ObjectIterator::EnumerableOnly); ScopedValue name(scope); QV4::ScopedValue val(scope); while (1) { name = it.nextPropertyNameAsString(val); if (name->isNull()) break; QString key = name->toQStringNoThrow(); if (!val->as()) result.insert(key, toJsonValue(val, visitedObjects)); } visitedObjects.remove(ObjectItem(o)); return result; } QV4::ReturnedValue JsonObject::fromJsonArray(ExecutionEngine *engine, const QJsonArray &array) { Scope scope(engine); int size = array.size(); ScopedArrayObject a(scope, engine->newArrayObject()); a->arrayReserve(size); ScopedValue v(scope); for (int i = 0; i < size; i++) a->arrayPut(i, (v = fromJsonValue(engine, array.at(i)))); a->setArrayLengthUnchecked(size); return a.asReturnedValue(); } QJsonArray JsonObject::toJsonArray(const ArrayObject *a, V4ObjectSet &visitedObjects) { QJsonArray result; if (!a) return result; Scope scope(a->engine()); if (visitedObjects.contains(ObjectItem(a))) { // Avoid recursion. // For compatibility with QVariant{List,Map} conversion, we return an // empty array (and no error is thrown). return result; } visitedObjects.insert(ObjectItem(a)); ScopedValue v(scope); quint32 length = a->getLength(); for (quint32 i = 0; i < length; ++i) { v = a->get(i); if (v->as()) v = Encode::null(); result.append(toJsonValue(v, visitedObjects)); } visitedObjects.remove(ObjectItem(a)); return result; }