| /**************************************************************************** |
| ** |
| ** 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 "qv4globalobject_p.h" |
| #include <private/qv4mm_p.h> |
| #include "qv4value_p.h" |
| #include "qv4context_p.h" |
| #include "qv4function_p.h" |
| #include "qv4debugging_p.h" |
| #include "qv4profiling_p.h" |
| #include "qv4script_p.h" |
| #include "qv4scopedvalue_p.h" |
| #include "qv4string_p.h" |
| #include "qv4jscall_p.h" |
| |
| #include <private/qv4codegen_p.h> |
| #include <private/qv4alloca_p.h> |
| #include "private/qlocale_tools_p.h" |
| #include "private/qtools_p.h" |
| |
| #include <QtCore/QDebug> |
| #include <QtCore/QString> |
| #include <iostream> |
| |
| #include <wtf/MathExtras.h> |
| |
| using namespace QV4; |
| using QtMiscUtils::toHexUpper; |
| using QtMiscUtils::fromHex; |
| |
| static QString escape(const QString &input) |
| { |
| QString output; |
| output.reserve(input.size() * 3); |
| const int length = input.length(); |
| for (int i = 0; i < length; ++i) { |
| ushort uc = input.at(i).unicode(); |
| if (uc < 0x100) { |
| if ( (uc > 0x60 && uc < 0x7B) |
| || (uc > 0x3F && uc < 0x5B) |
| || (uc > 0x2C && uc < 0x3A) |
| || (uc == 0x2A) |
| || (uc == 0x2B) |
| || (uc == 0x5F)) { |
| output.append(QChar(uc)); |
| } else { |
| output.append('%'); |
| output.append(QLatin1Char(toHexUpper(uc >> 4))); |
| output.append(QLatin1Char(toHexUpper(uc))); |
| } |
| } else { |
| output.append('%'); |
| output.append('u'); |
| output.append(QLatin1Char(toHexUpper(uc >> 12))); |
| output.append(QLatin1Char(toHexUpper(uc >> 8))); |
| output.append(QLatin1Char(toHexUpper(uc >> 4))); |
| output.append(QLatin1Char(toHexUpper(uc))); |
| } |
| } |
| return output; |
| } |
| |
| static QString unescape(const QString &input) |
| { |
| QString result; |
| result.reserve(input.length()); |
| int i = 0; |
| const int length = input.length(); |
| while (i < length) { |
| QChar c = input.at(i++); |
| if ((c == '%') && (i + 1 < length)) { |
| QChar a = input.at(i); |
| if ((a == 'u') && (i + 4 < length)) { |
| int d3 = fromHex(input.at(i+1).unicode()); |
| int d2 = fromHex(input.at(i+2).unicode()); |
| int d1 = fromHex(input.at(i+3).unicode()); |
| int d0 = fromHex(input.at(i+4).unicode()); |
| if ((d3 != -1) && (d2 != -1) && (d1 != -1) && (d0 != -1)) { |
| ushort uc = ushort((d3 << 12) | (d2 << 8) | (d1 << 4) | d0); |
| result.append(QChar(uc)); |
| i += 5; |
| } else { |
| result.append(c); |
| } |
| } else { |
| int d1 = fromHex(a.unicode()); |
| int d0 = fromHex(input.at(i+1).unicode()); |
| if ((d1 != -1) && (d0 != -1)) { |
| c = (d1 << 4) | d0; |
| i += 2; |
| } |
| result.append(c); |
| } |
| } else { |
| result.append(c); |
| } |
| } |
| return result; |
| } |
| |
| static const char uriReserved[] = ";/?:@&=+$,#"; |
| static const char uriUnescaped[] = "-_.!~*'()"; |
| static const char uriUnescapedReserved[] = "-_.!~*'();/?:@&=+$,#"; |
| |
| static void addEscapeSequence(QString &output, uchar ch) |
| { |
| output.append(QLatin1Char('%')); |
| output.append(QLatin1Char(toHexUpper(ch >> 4))); |
| output.append(QLatin1Char(toHexUpper(ch & 0xf))); |
| } |
| |
| static QString encode(const QString &input, const char *unescapedSet, bool *ok) |
| { |
| *ok = true; |
| QString output; |
| const int length = input.length(); |
| int i = 0; |
| while (i < length) { |
| const QChar c = input.at(i); |
| bool escape = true; |
| if ((c.unicode() >= 'a' && c.unicode() <= 'z') || |
| (c.unicode() >= 'A' && c.unicode() <= 'Z') || |
| (c.unicode() >= '0' && c.unicode() <= '9')) { |
| escape = false; |
| } else { |
| const char *r = unescapedSet; |
| while (*r) { |
| if (*r == c.unicode()) { |
| escape = false; |
| break; |
| } |
| ++r; |
| } |
| } |
| if (escape) { |
| uint uc = c.unicode(); |
| if ((uc >= 0xDC00) && (uc <= 0xDFFF)) { |
| *ok = false; |
| break; |
| } |
| if (!((uc < 0xD800) || (uc > 0xDBFF))) { |
| ++i; |
| if (i == length) { |
| *ok = false; |
| break; |
| } |
| const uint uc2 = input.at(i).unicode(); |
| if ((uc2 < 0xDC00) || (uc2 > 0xDFFF)) { |
| *ok = false; |
| break; |
| } |
| uc = ((uc - 0xD800) * 0x400) + (uc2 - 0xDC00) + 0x10000; |
| } |
| if (uc < 0x80) { |
| addEscapeSequence(output, (uchar)uc); |
| } else { |
| if (uc < 0x0800) { |
| addEscapeSequence(output, 0xc0 | ((uchar) (uc >> 6))); |
| } else { |
| |
| if (QChar::requiresSurrogates(uc)) { |
| addEscapeSequence(output, 0xf0 | ((uchar) (uc >> 18))); |
| addEscapeSequence(output, 0x80 | (((uchar) (uc >> 12)) & 0x3f)); |
| } else { |
| addEscapeSequence(output, 0xe0 | (((uchar) (uc >> 12)) & 0x3f)); |
| } |
| addEscapeSequence(output, 0x80 | (((uchar) (uc >> 6)) & 0x3f)); |
| } |
| addEscapeSequence(output, 0x80 | ((uchar) (uc&0x3f))); |
| } |
| } else { |
| output.append(c); |
| } |
| ++i; |
| } |
| if (i != length) |
| *ok = false; |
| return output; |
| } |
| |
| enum DecodeMode { |
| DecodeAll, |
| DecodeNonReserved |
| }; |
| |
| static QString decode(const QString &input, DecodeMode decodeMode, bool *ok) |
| { |
| *ok = true; |
| QString output; |
| output.reserve(input.length()); |
| const int length = input.length(); |
| int i = 0; |
| const QChar percent = QLatin1Char('%'); |
| while (i < length) { |
| const QChar ch = input.at(i); |
| if (ch == percent) { |
| int start = i; |
| if (i + 2 >= length) |
| goto error; |
| |
| int d1 = fromHex(input.at(i+1).unicode()); |
| int d0 = fromHex(input.at(i+2).unicode()); |
| if ((d1 == -1) || (d0 == -1)) |
| goto error; |
| |
| int b = (d1 << 4) | d0; |
| i += 2; |
| if (b & 0x80) { |
| int uc; |
| int min_uc; |
| int need; |
| if ((b & 0xe0) == 0xc0) { |
| uc = b & 0x1f; |
| need = 1; |
| min_uc = 0x80; |
| } else if ((b & 0xf0) == 0xe0) { |
| uc = b & 0x0f; |
| need = 2; |
| min_uc = 0x800; |
| } else if ((b & 0xf8) == 0xf0) { |
| uc = b & 0x07; |
| need = 3; |
| min_uc = 0x10000; |
| } else { |
| goto error; |
| } |
| |
| if (i + (3 * need) >= length) |
| goto error; |
| |
| for (int j = 0; j < need; ++j) { |
| ++i; |
| if (input.at(i) != percent) |
| goto error; |
| |
| d1 = fromHex(input.at(i+1).unicode()); |
| d0 = fromHex(input.at(i+2).unicode()); |
| if ((d1 == -1) || (d0 == -1)) |
| goto error; |
| |
| b = (d1 << 4) | d0; |
| if ((b & 0xC0) != 0x80) |
| goto error; |
| |
| i += 2; |
| uc = (uc << 6) + (b & 0x3f); |
| } |
| if (uc < min_uc) |
| goto error; |
| |
| if (uc < 0x10000) { |
| output.append(QChar(uc)); |
| } else { |
| if (uc > 0x10FFFF) |
| goto error; |
| |
| ushort l = ushort(((uc - 0x10000) & 0x3FF) + 0xDC00); |
| ushort h = ushort((((uc - 0x10000) >> 10) & 0x3FF) + 0xD800); |
| output.append(QChar(h)); |
| output.append(QChar(l)); |
| } |
| } else { |
| if (decodeMode == DecodeNonReserved && b <= 0x40) { |
| const char *r = uriReserved; |
| while (*r) { |
| if (*r == b) |
| break; |
| ++r; |
| } |
| if (*r) |
| output.append(input.midRef(start, i - start + 1)); |
| else |
| output.append(QChar(b)); |
| } else { |
| output.append(QChar(b)); |
| } |
| } |
| } else { |
| output.append(ch); |
| } |
| ++i; |
| } |
| if (i != length) |
| *ok = false; |
| return output; |
| error: |
| *ok = false; |
| return QString(); |
| } |
| |
| DEFINE_OBJECT_VTABLE(EvalFunction); |
| |
| void Heap::EvalFunction::init(QV4::ExecutionContext *scope) |
| { |
| Scope s(scope); |
| Heap::FunctionObject::init(scope, s.engine->id_eval()); |
| ScopedFunctionObject f(s, this); |
| f->defineReadonlyConfigurableProperty(s.engine->id_length(), Value::fromInt32(1)); |
| } |
| |
| ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, bool directCall) const |
| { |
| if (argc < 1) |
| return Encode::undefined(); |
| |
| ExecutionEngine *v4 = engine(); |
| bool isStrict = v4->currentStackFrame->v4Function->isStrict(); |
| |
| Scope scope(v4); |
| ScopedContext ctx(scope, v4->currentContext()); |
| |
| if (!directCall) { |
| // the context for eval should be the global scope |
| ctx = v4->scriptContext(); |
| } |
| |
| String *scode = argv[0].stringValue(); |
| if (!scode) |
| return argv[0].asReturnedValue(); |
| |
| const QString code = scode->toQString(); |
| bool inheritContext = !isStrict; |
| |
| Script script(ctx, QV4::Compiler::ContextType::Eval, code, QStringLiteral("eval code")); |
| script.strictMode = (directCall && isStrict); |
| script.inheritContext = inheritContext; |
| script.parse(); |
| if (v4->hasException) |
| return Encode::undefined(); |
| |
| Function *function = script.function(); |
| if (!function) |
| return Encode::undefined(); |
| function->isEval = true; |
| |
| if (function->isStrict() || isStrict) { |
| ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(ctx, function)); |
| ScopedValue thisObject(scope, directCall ? scope.engine->currentStackFrame->thisObject() : scope.engine->globalObject->asReturnedValue()); |
| return e->call(thisObject, nullptr, 0); |
| } |
| |
| ScopedValue thisObject(scope, scope.engine->currentStackFrame->thisObject()); |
| |
| return function->call(thisObject, nullptr, 0, ctx); |
| } |
| |
| |
| ReturnedValue EvalFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc) |
| { |
| // indirect call |
| return static_cast<const EvalFunction *>(f)->evalCall(thisObject, argv, argc, false); |
| } |
| |
| |
| static inline int toInt(const QChar &qc, int R) |
| { |
| ushort c = qc.unicode(); |
| int v = -1; |
| if (c >= '0' && c <= '9') |
| v = c - '0'; |
| else if (c >= 'A' && c <= 'Z') |
| v = c - 'A' + 10; |
| else if (c >= 'a' && c <= 'z') |
| v = c - 'a' + 10; |
| if (v >= 0 && v < R) |
| return v; |
| else |
| return -1; |
| } |
| |
| // parseInt [15.1.2.2] |
| ReturnedValue GlobalFunctions::method_parseInt(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| Scope scope(b); |
| ScopedValue inputString(scope, argc ? argv[0] : Value::undefinedValue()); |
| ScopedValue radix(scope, argc > 1 ? argv[1] : Value::undefinedValue()); |
| int R = radix->isUndefined() ? 0 : radix->toInt32(); |
| |
| // [15.1.2.2] step by step: |
| QString trimmed = inputString->toQString().trimmed(); // 1 + 2 |
| CHECK_EXCEPTION(); |
| |
| const QChar *pos = trimmed.constData(); |
| const QChar *end = pos + trimmed.length(); |
| |
| int sign = 1; // 3 |
| if (pos != end) { |
| if (*pos == QLatin1Char('-')) |
| sign = -1; // 4 |
| if (*pos == QLatin1Char('-') || *pos == QLatin1Char('+')) |
| ++pos; // 5 |
| } |
| bool stripPrefix = true; // 7 |
| if (R) { // 8 |
| if (R < 2 || R > 36) |
| RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 8a |
| if (R != 16) |
| stripPrefix = false; // 8b |
| } else { // 9 |
| R = 10; // 9a |
| } |
| if (stripPrefix) { // 10 |
| if ((end - pos >= 2) |
| && (pos[0] == QLatin1Char('0')) |
| && (pos[1] == QLatin1Char('x') || pos[1] == QLatin1Char('X'))) { // 10a |
| pos += 2; |
| R = 16; |
| } |
| } |
| // 11: Z is progressively built below |
| // 13: this is handled by the toInt function |
| if (pos == end) // 12 |
| RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); |
| bool overflow = false; |
| qint64 v_overflow = 0; |
| unsigned overflow_digit_count = 0; |
| int d = toInt(*pos++, R); |
| if (d == -1) |
| RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); |
| qint64 v = d; |
| while (pos != end) { |
| d = toInt(*pos++, R); |
| if (d == -1) |
| break; |
| if (overflow) { |
| if (overflow_digit_count == 0) { |
| v_overflow = v; |
| v = 0; |
| } |
| ++overflow_digit_count; |
| v = v * R + d; |
| } else { |
| qint64 vNew = v * R + d; |
| if (vNew < v) { |
| overflow = true; |
| --pos; |
| } else { |
| v = vNew; |
| } |
| } |
| } |
| |
| if (overflow) { |
| double result = (double) v_overflow * pow(static_cast<double>(R), static_cast<double>(overflow_digit_count)); |
| result += v; |
| RETURN_RESULT(Encode(sign * result)); |
| } else { |
| RETURN_RESULT(Encode(sign * (double) v)); // 15 |
| } |
| } |
| |
| // parseFloat [15.1.2.3] |
| ReturnedValue GlobalFunctions::method_parseFloat(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| Scope scope(b); |
| // [15.1.2.3] step by step: |
| ScopedString inputString(scope, argc ? argv[0] : Value::undefinedValue(), ScopedString::Convert); |
| CHECK_EXCEPTION(); |
| |
| QString trimmed = inputString->toQString().trimmed(); // 2 |
| |
| // 4: |
| if (trimmed.startsWith(QLatin1String("Infinity")) |
| || trimmed.startsWith(QLatin1String("+Infinity"))) |
| RETURN_RESULT(Encode(Q_INFINITY)); |
| if (trimmed.startsWith(QLatin1String("-Infinity"))) |
| RETURN_RESULT(Encode(-Q_INFINITY)); |
| QByteArray ba = trimmed.toLatin1(); |
| bool ok; |
| const char *begin = ba.constData(); |
| const char *end = nullptr; |
| double d = qstrtod(begin, &end, &ok); |
| if (end - begin == 0) |
| RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 3 |
| else |
| RETURN_RESULT(Encode(d)); |
| } |
| |
| /// isNaN [15.1.2.4] |
| ReturnedValue GlobalFunctions::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc) |
| { |
| if (!argc) |
| // undefined gets converted to NaN |
| RETURN_RESULT(Encode(true)); |
| |
| if (argv[0].integerCompatible()) |
| RETURN_RESULT(Encode(false)); |
| |
| double d = argv[0].toNumber(); |
| RETURN_RESULT(Encode((bool)std::isnan(d))); |
| } |
| |
| /// isFinite [15.1.2.5] |
| ReturnedValue GlobalFunctions::method_isFinite(const FunctionObject *, const Value *, const Value *argv, int argc) |
| { |
| if (!argc) |
| // undefined gets converted to NaN |
| RETURN_RESULT(Encode(false)); |
| |
| if (argv[0].integerCompatible()) |
| RETURN_RESULT(Encode(true)); |
| |
| double d = argv[0].toNumber(); |
| RETURN_RESULT(Encode((bool)std::isfinite(d))); |
| } |
| |
| /// decodeURI [15.1.3.1] |
| ReturnedValue GlobalFunctions::method_decodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| if (argc == 0) |
| RETURN_UNDEFINED(); |
| |
| ExecutionEngine *v4 = b->engine(); |
| QString uriString = argv[0].toQString(); |
| bool ok; |
| QString out = decode(uriString, DecodeNonReserved, &ok); |
| if (!ok) { |
| Scope scope(v4); |
| ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); |
| RETURN_RESULT(scope.engine->throwURIError(s)); |
| } |
| |
| RETURN_RESULT(v4->newString(out)); |
| } |
| |
| /// decodeURIComponent [15.1.3.2] |
| ReturnedValue GlobalFunctions::method_decodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| if (argc == 0) |
| RETURN_UNDEFINED(); |
| |
| ExecutionEngine *v4 = b->engine(); |
| QString uriString = argv[0].toQString(); |
| bool ok; |
| QString out = decode(uriString, DecodeAll, &ok); |
| if (!ok) { |
| Scope scope(v4); |
| ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); |
| RETURN_RESULT(scope.engine->throwURIError(s)); |
| } |
| |
| RETURN_RESULT(v4->newString(out)); |
| } |
| |
| /// encodeURI [15.1.3.3] |
| ReturnedValue GlobalFunctions::method_encodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| if (argc == 0) |
| RETURN_UNDEFINED(); |
| |
| ExecutionEngine *v4 = b->engine(); |
| QString uriString = argv[0].toQString(); |
| bool ok; |
| QString out = encode(uriString, uriUnescapedReserved, &ok); |
| if (!ok) { |
| Scope scope(v4); |
| ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); |
| RETURN_RESULT(scope.engine->throwURIError(s)); |
| } |
| |
| RETURN_RESULT(v4->newString(out)); |
| } |
| |
| /// encodeURIComponent [15.1.3.4] |
| ReturnedValue GlobalFunctions::method_encodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| if (argc == 0) |
| RETURN_UNDEFINED(); |
| |
| ExecutionEngine *v4 = b->engine(); |
| QString uriString = argv[0].toQString(); |
| bool ok; |
| QString out = encode(uriString, uriUnescaped, &ok); |
| if (!ok) { |
| Scope scope(v4); |
| ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence"))); |
| RETURN_RESULT(scope.engine->throwURIError(s)); |
| } |
| |
| RETURN_RESULT(v4->newString(out)); |
| } |
| |
| ReturnedValue GlobalFunctions::method_escape(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| ExecutionEngine *v4 = b->engine(); |
| if (!argc) |
| RETURN_RESULT(v4->newString(QStringLiteral("undefined"))); |
| |
| QString str = argv[0].toQString(); |
| RETURN_RESULT(v4->newString(escape(str))); |
| } |
| |
| ReturnedValue GlobalFunctions::method_unescape(const FunctionObject *b, const Value *, const Value *argv, int argc) |
| { |
| ExecutionEngine *v4 = b->engine(); |
| if (!argc) |
| RETURN_RESULT(v4->newString(QStringLiteral("undefined"))); |
| |
| QString str = argv[0].toQString(); |
| RETURN_RESULT(v4->newString(unescape(str))); |
| } |