Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++
++ #include "qv4stringobject_p.h"
++ #include "qv4regexp_p.h"
++ #include "qv4regexpobject_p.h"
++ #include <private/qv4mm_p.h>
++ #include "qv4scopedvalue_p.h"
++ #include "qv4symbol_p.h"
++ #include <private/qv4alloca_p.h>
++ #include "qv4jscall_p.h"
++ #include "qv4stringiterator_p.h"
++ #include <QtCore/QDateTime>
++ #include <QtCore/QDebug>
++ #include <QtCore/QStringList>
++ #include <QtQml/private/qv4runtime_p.h>
++
++ #include <cassert>
++
++ #ifndef Q_OS_WIN
++ # include <time.h>
++ # ifndef Q_OS_VXWORKS
++ # include <sys/time.h>
++ # else
++ # include "qplatformdefs.h"
++ # endif
++ #else
++ # include <qt_windows.h>
++ #endif
++
++ using namespace QV4;
++ using namespace Qt::Literals::StringLiterals;
++
++ DEFINE_OBJECT_VTABLE(StringObject);
++
++ void Heap::StringObject::init()
++ {
++ Object::init();
++ Q_ASSERT(vtable() == QV4::StringObject::staticVTable());
++ string.set(internalClass->engine, internalClass->engine->id_empty()->d());
++ setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(0));
++ }
++
++ void Heap::StringObject::init(const QV4::String *str)
++ {
++ Object::init();
++ string.set(internalClass->engine, str->d());
++ setProperty(internalClass->engine, LengthPropertyIndex, Value::fromInt32(length()));
++ }
++
++ Heap::String *Heap::StringObject::getIndex(uint index) const
++ {
++ QString str = string->toQString();
++ if (index >= (uint)str.size())
++ return nullptr;
++ return internalClass->engine->newString(str.mid(index, 1));
++ }
++
++ uint Heap::StringObject::length() const
++ {
++ return string->length();
++ }
++
++ bool StringObject::virtualDeleteProperty(Managed *m, PropertyKey id)
++ {
++ Q_ASSERT(m->as<StringObject>());
++ if (id.isArrayIndex()) {
++ StringObject *o = static_cast<StringObject *>(m);
++ uint index = id.asArrayIndex();
++ if (index < static_cast<uint>(o->d()->string->toQString().size()))
++ return false;
++ }
++ return Object::virtualDeleteProperty(m, id);
++ }
++
++ namespace {
++ struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
++ {
++ ~StringObjectOwnPropertyKeyIterator() override = default;
++ PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
++ };
++ } // namespace
++
++ PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs)
++ {
++ const StringObject *s = static_cast<const StringObject *>(o);
++ uint slen = s->d()->string->toQString().size();
++ if (arrayIndex < slen) {
++ uint index = arrayIndex;
++ ++arrayIndex;
++ if (attrs)
++ *attrs = Attr_NotConfigurable|Attr_NotWritable;
++ if (pd)
++ pd->value = s->getIndex(index);
++ return PropertyKey::fromArrayIndex(index);
++ } else if (arrayIndex == slen) {
++ if (s->arrayData()) {
++ SparseArrayNode *arrayNode = s->sparseBegin();
++ // iterate until we're past the end of the string
++ while (arrayNode && arrayNode->key() < slen)
++ arrayNode = arrayNode->nextNode();
++ }
++ }
++
++ return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
++ }
++
++ OwnPropertyKeyIterator *StringObject::virtualOwnPropertyKeys(const Object *m, Value *target)
++ {
++ *target = *m;
++ return new StringObjectOwnPropertyKeyIterator;
++ }
++
++ PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
++ {
++ PropertyAttributes attributes = Object::virtualGetOwnProperty(m, id, p);
++ if (attributes != Attr_Invalid)
++ return attributes;
++
++ if (id.isArrayIndex()) {
++ const uint index = id.asArrayIndex();
++ const auto s = static_cast<const StringObject *>(m);
++ if (index < uint(s->d()->string->toQString().size())) {
++ if (p)
++ p->value = s->getIndex(index);
++ return Attr_NotConfigurable|Attr_NotWritable;
++ }
++ }
++ return Object::virtualGetOwnProperty(m, id, p);
++ }
++
++ DEFINE_OBJECT_VTABLE(StringCtor);
++
++ void Heap::StringCtor::init(QV4::ExecutionEngine *engine)
++ {
++ Heap::FunctionObject::init(engine, QStringLiteral("String"));
++ }
++
++ ReturnedValue StringCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
++ {
++ ExecutionEngine *v4 = static_cast<const Object *>(f)->engine();
++ Scope scope(v4);
++ ScopedString value(scope);
++ if (argc)
++ value = argv[0].toString(v4);
++ else
++ value = v4->newString();
++ CHECK_EXCEPTION();
++ ReturnedValue o = Encode(v4->newStringObject(value));
++
++ if (!newTarget)
++ return o;
++ ScopedObject obj(scope, o);
++ obj->setProtoFromNewTarget(newTarget);
++ return obj->asReturnedValue();
++ }
++
++ ReturnedValue StringCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = m->engine();
++ if (!argc)
++ return v4->newString()->asReturnedValue();
++ if (argv[0].isSymbol())
++ return v4->newString(argv[0].symbolValue()->descriptiveString())->asReturnedValue();
++ return argv[0].toString(v4)->asReturnedValue();
++ }
++
++ ReturnedValue StringCtor::method_fromCharCode(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ QString str(argc, Qt::Uninitialized);
++ QChar *ch = str.data();
++ for (int i = 0, ei = argc; i < ei; ++i) {
++ *ch = QChar(argv[i].toUInt16());
++ ++ch;
++ }
++ return Encode(b->engine()->newString(str));
++ }
++
++
++
++ ReturnedValue StringCtor::method_fromCodePoint(const FunctionObject *f, const Value *, const Value *argv, int argc)
++ {
++ ExecutionEngine *e = f->engine();
++ QString result(argc*2, Qt::Uninitialized); // assume worst case
++ QChar *ch = result.data();
++ for (int i = 0; i < argc; ++i) {
++ double num = argv[i].toNumber();
++ if (e->hasException)
++ return Encode::undefined();
++ int cp = static_cast<int>(num);
++ if (cp != num || cp < 0 || cp > 0x10ffff)
++ return e->throwRangeError(QStringLiteral("String.fromCodePoint: argument out of range."));
++ if (cp > 0xffff) {
++ *ch = QChar::highSurrogate(cp);
++ ++ch;
++ *ch = QChar::lowSurrogate(cp);
++ } else {
++ *ch = QChar(cp);
++ }
++ ++ch;
++ }
++ result.truncate(ch - result.constData());
++ return e->newString(result)->asReturnedValue();
++ }
++
++ ReturnedValue StringCtor::method_raw(const FunctionObject *f, const Value *, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ if (!argc)
++ return scope.engine->throwTypeError();
++
++ ScopedObject cooked(scope, argv[0].toObject(scope.engine));
++ if (!cooked)
++ return scope.engine->throwTypeError();
++ ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral("raw")));
++ ScopedValue rawValue(scope, cooked->get(rawString));
++ ScopedObject raw(scope, rawValue->toObject(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ ++argv;
++ --argc;
++
++ QString result;
++ uint literalSegments = raw->getLength();
++ if (!literalSegments)
++ return scope.engine->id_empty()->asReturnedValue();
++
++ uint nextIndex = 0;
++ ScopedValue val(scope);
++ while (1) {
++ val = raw->get(nextIndex);
++ result += val->toQString();
++ if (scope.hasException())
++ return Encode::undefined();
++ if (nextIndex + 1 == literalSegments)
++ return scope.engine->newString(result)->asReturnedValue();
++
++ if (nextIndex < static_cast<uint>(argc))
++ result += argv[nextIndex].toQString();
++ if (scope.hasException())
++ return Encode::undefined();
++ ++nextIndex;
++ }
++ }
++
++ void StringPrototype::init(ExecutionEngine *engine, Object *ctor)
++ {
++ Scope scope(engine);
++ ScopedObject o(scope);
++
++ // need to set this once again, as these were not fully defined when creating the string proto
++ Heap::InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(scope.engine->objectPrototype()->d());
++ d()->internalClass.set(scope.engine, ic);
++ d()->string.set(scope.engine, scope.engine->id_empty()->d());
++ setProperty(scope.engine, Heap::StringObject::LengthPropertyIndex, Value::fromInt32(0));
++
++ ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
++ ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(1));
++ ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), StringCtor::method_fromCharCode, 1);
++ ctor->defineDefaultProperty(QStringLiteral("fromCodePoint"), StringCtor::method_fromCodePoint, 1);
++ ctor->defineDefaultProperty(QStringLiteral("raw"), StringCtor::method_raw, 1);
++
++ defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
++ defineDefaultProperty(engine->id_toString(), method_toString);
++ defineDefaultProperty(engine->id_valueOf(), method_toString); // valueOf and toString are identical
++ defineDefaultProperty(QStringLiteral("charAt"), method_charAt, 1);
++ defineDefaultProperty(QStringLiteral("charCodeAt"), method_charCodeAt, 1);
++ defineDefaultProperty(QStringLiteral("codePointAt"), method_codePointAt, 1);
++ defineDefaultProperty(QStringLiteral("concat"), method_concat, 1);
++ defineDefaultProperty(QStringLiteral("endsWith"), method_endsWith, 1);
++ defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1);
++ defineDefaultProperty(QStringLiteral("includes"), method_includes, 1);
++ defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1);
++ defineDefaultProperty(QStringLiteral("localeCompare"), method_localeCompare, 1);
++ defineDefaultProperty(QStringLiteral("match"), method_match, 1);
++ defineDefaultProperty(QStringLiteral("normalize"), method_normalize, 0);
++ defineDefaultProperty(QStringLiteral("padEnd"), method_padEnd, 1);
++ defineDefaultProperty(QStringLiteral("padStart"), method_padStart, 1);
++ defineDefaultProperty(QStringLiteral("repeat"), method_repeat, 1);
++ defineDefaultProperty(QStringLiteral("replace"), method_replace, 2);
++ defineDefaultProperty(QStringLiteral("search"), method_search, 1);
++ defineDefaultProperty(QStringLiteral("slice"), method_slice, 2);
++ defineDefaultProperty(QStringLiteral("split"), method_split, 2);
++ defineDefaultProperty(QStringLiteral("startsWith"), method_startsWith, 1);
++ defineDefaultProperty(QStringLiteral("substr"), method_substr, 2);
++ defineDefaultProperty(QStringLiteral("substring"), method_substring, 2);
++ defineDefaultProperty(QStringLiteral("toLowerCase"), method_toLowerCase);
++ defineDefaultProperty(QStringLiteral("toLocaleLowerCase"), method_toLocaleLowerCase);
++ defineDefaultProperty(QStringLiteral("toUpperCase"), method_toUpperCase);
++ defineDefaultProperty(QStringLiteral("toLocaleUpperCase"), method_toLocaleUpperCase);
++ defineDefaultProperty(QStringLiteral("trim"), method_trim);
++ defineDefaultProperty(engine->symbol_iterator(), method_iterator);
++ }
++
++ static Heap::String *thisAsString(ExecutionEngine *v4, const QV4::Value *thisObject)
++ {
++ if (String *s = thisObject->stringValue())
++ return s->d();
++ if (const StringObject *thisString = thisObject->as<StringObject>())
++ return thisString->d()->string;
++ return thisObject->toString(v4);
++ }
++
++ static QString getThisString(ExecutionEngine *v4, const QV4::Value *thisObject)
++ {
++ if (String *s = thisObject->stringValue())
++ return s->toQString();
++ if (const StringObject *thisString = thisObject->as<StringObject>())
++ return thisString->d()->string->toQString();
++ if (thisObject->isUndefined() || thisObject->isNull()) {
++ v4->throwTypeError();
++ return QString();
++ }
++ return thisObject->toQString();
++ }
++
++ ReturnedValue StringPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ if (thisObject->isString())
++ return thisObject->asReturnedValue();
++
++ ExecutionEngine *v4 = b->engine();
++ const StringObject *o = thisObject->as<StringObject>();
++ if (!o)
++ return v4->throwTypeError();
++ return o->d()->string->asReturnedValue();
++ }
++
++ ReturnedValue StringPrototype::method_charAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString str = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ double pos = 0;
++ if (argc > 0)
++ pos = argv[0].toInteger();
++
++ QString result;
++ if (pos >= 0 && pos < str.size())
++ result += str.at(pos);
++
++ return Encode(v4->newString(result));
++ }
++
++ ReturnedValue StringPrototype::method_charCodeAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString str = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ double pos = 0;
++ if (argc > 0)
++ pos = argv[0].toInteger();
++
++
++ if (pos >= 0 && pos < str.size())
++ RETURN_RESULT(Encode(str.at(pos).unicode()));
++
++ return Encode(qt_qnan());
++ }
++
++ ReturnedValue StringPrototype::method_codePointAt(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = f->engine();
++ QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ double index = argc ? argv[0].toInteger() : 0.0;
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ if (index < 0 || index >= value.size())
++ return Encode::undefined();
++
++ uint first = value.at(index).unicode();
++ if (QChar::isHighSurrogate(first) && index + 1 < value.size()) {
++ uint second = value.at(index + 1).unicode();
++ if (QChar::isLowSurrogate(second))
++ return Encode(QChar::surrogateToUcs4(first, second));
++ }
++ return Encode(first);
++ }
++
++ ReturnedValue StringPrototype::method_concat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ Scope scope(v4);
++ ScopedString s(scope);
++ for (int i = 0; i < argc; ++i) {
++ s = argv[i].toString(scope.engine);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ Q_ASSERT(s->isString());
++ value += s->toQString();
++ }
++
++ return Encode(v4->newString(value));
++ }
++
++ ReturnedValue StringPrototype::method_endsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ if (argc && argv[0].as<RegExpObject>())
++ return v4->throwTypeError();
++ QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ double pos = value.size();
++ if (argc > 1)
++ pos = argv[1].toInteger();
++
++ if (pos == value.size())
++ RETURN_RESULT(Encode(value.endsWith(searchString)));
++
++ QStringView stringToSearch = QStringView{value}.left(pos);
++ return Encode(stringToSearch.endsWith(searchString));
++ }
++
++ ReturnedValue StringPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ double pos = 0;
++ if (argc > 1)
++ pos = argv[1].toInteger();
++
++ int index = -1;
++ if (!value.isEmpty())
++ index = value.indexOf(searchString, qMin(qMax(pos, 0.0), double(value.size())));
++
++ return Encode(index);
++ }
++
++ ReturnedValue StringPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ if (argc && argv[0].as<RegExpObject>())
++ return v4->throwTypeError();
++ QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ double pos = 0;
++ if (argc > 1) {
++ const Value &posArg = argv[1];
++ pos = posArg.toInteger();
++ if (!posArg.isInteger() && posArg.isNumber() && qIsInf(posArg.toNumber()))
++ pos = value.size();
++ }
++
++ if (pos == 0)
++ RETURN_RESULT(Encode(value.contains(searchString)));
++
++ QStringView stringToSearch = QStringView{value}.mid(pos);
++ return Encode(stringToSearch.contains(searchString));
++ }
++
++ ReturnedValue StringPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ double position = argc > 1 ? RuntimeHelpers::toNumber(argv[1]) : +qInf();
++ if (std::isnan(position))
++ position = +qInf();
++ else
++ position = std::trunc(position);
++
++ int pos = std::trunc(qMin(qMax(position, 0.0), double(value.size())));
++ if (!searchString.isEmpty() && pos == value.size())
++ --pos;
++ if (searchString.isNull() && pos == 0)
++ RETURN_RESULT(Encode(-1));
++ int index = value.lastIndexOf(searchString, pos);
++ return Encode(index);
++ }
++
++ ReturnedValue StringPrototype::method_localeCompare(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ const QString that = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ return Encode(QString::localeAwareCompare(value, that));
++ }
++
++ ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ if (thisObject->isNullOrUndefined())
++ return v4->throwTypeError();
++
++ Scope scope(v4);
++ if (argc && !argv[0].isNullOrUndefined()) {
++ ScopedObject r(scope, argv[0].toObject(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++ ScopedValue f(scope, r->get(scope.engine->symbol_match()));
++ if (!f->isNullOrUndefined()) {
++ ScopedFunctionObject fo(scope, f);
++ if (!fo)
++ return scope.engine->throwTypeError();
++ return checkedResult(scope.engine, fo->call(r, thisObject, 1));
++ }
++ }
++
++ ScopedString s(scope, thisObject->toString(v4));
++ if (v4->hasException)
++ return Encode::undefined();
++
++ Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue());
++ if (!that) {
++ // convert args[0] to a regexp
++ that = RegExpCtor::virtualCallAsConstructor(b, argv, argc, b);
++ if (v4->hasException)
++ return Encode::undefined();
++ }
++ Q_ASSERT(!!that);
++
++ ScopedFunctionObject match(scope, that->get(scope.engine->symbol_match()));
++ if (!match)
++ return scope.engine->throwTypeError();
++ return checkedResult(scope.engine, match->call(that, s, 1));
++ }
++
++ ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = f->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return Encode::undefined();
++
++ QString::NormalizationForm form = QString::NormalizationForm_C;
++ if (argc >= 1 && !argv[0].isUndefined()) {
++ QString f = argv[0].toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++ if (f == QLatin1String("NFC"))
++ form = QString::NormalizationForm_C;
++ else if (f == QLatin1String("NFD"))
++ form = QString::NormalizationForm_D;
++ else if (f == QLatin1String("NFKC"))
++ form = QString::NormalizationForm_KC;
++ else if (f == QLatin1String("NFKD"))
++ form = QString::NormalizationForm_KD;
++ else
++ return v4->throwRangeError(QLatin1String("String.prototype.normalize: Invalid normalization form."));
++ }
++ QString normalized = value.normalized(form);
++ return v4->newString(normalized)->asReturnedValue();
++ }
++
++ ReturnedValue StringPrototype::method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = f->engine();
++ if (thisObject->isNullOrUndefined())
++ return v4->throwTypeError();
++
++ Scope scope(v4);
++ ScopedString s(scope, thisAsString(v4, thisObject));
++ if (v4->hasException)
++ return Encode::undefined();
++ if (!argc)
++ return s->asReturnedValue();
++
++ double maxLen = argv[0].toInteger();
++ if (maxLen <= s->d()->length())
++ return s->asReturnedValue();
++ QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
++ if (v4->hasException)
++ return Encode::undefined();
++
++ if (fillString.isEmpty())
++ return s->asReturnedValue();
++
++ QString padded = s->toQString();
++ int oldLength = padded.size();
++ int toFill = maxLen - oldLength;
++ padded.resize(maxLen);
++ QChar *ch = padded.data() + oldLength;
++ while (toFill) {
++ int copy = qMin(fillString.size(), toFill);
++ memcpy(ch, fillString.constData(), copy*sizeof(QChar));
++ toFill -= copy;
++ ch += copy;
++ }
++ *ch = QChar::Null;
++
++ return v4->newString(padded)->asReturnedValue();
++ }
++
++ ReturnedValue StringPrototype::method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = f->engine();
++ if (thisObject->isNullOrUndefined())
++ return v4->throwTypeError();
++
++ Scope scope(v4);
++ ScopedString s(scope, thisAsString(v4, thisObject));
++ if (v4->hasException)
++ return Encode::undefined();
++ if (!argc)
++ return s->asReturnedValue();
++
++ double maxLen = argv[0].toInteger();
++ if (maxLen <= s->d()->length())
++ return s->asReturnedValue();
++ QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : u" "_s;
++ if (v4->hasException)
++ return Encode::undefined();
++
++ if (fillString.isEmpty())
++ return s->asReturnedValue();
++
++ QString original = s->toQString();
++ int oldLength = original.size();
++ int toFill = maxLen - oldLength;
++ QString padded;
++ padded.resize(maxLen);
++ QChar *ch = padded.data();
++ while (toFill) {
++ int copy = qMin(fillString.size(), toFill);
++ memcpy(ch, fillString.constData(), copy*sizeof(QChar));
++ toFill -= copy;
++ ch += copy;
++ }
++ memcpy(ch, original.constData(), oldLength*sizeof(QChar));
++ ch += oldLength;
++ *ch = QChar::Null;
++
++ return v4->newString(padded)->asReturnedValue();
++ }
++
++
++ ReturnedValue StringPrototype::method_repeat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ double repeats = (argc ? argv[0] : Value::undefinedValue()).toInteger();
++
++ if (repeats < 0 || qIsInf(repeats))
++ return v4->throwRangeError(QLatin1String("Invalid count value"));
++
++ return Encode(v4->newString(value.repeated(int(repeats))));
++ }
++
++ static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount)
++ {
++ result->reserve(result->size() + replaceValue.size());
++ for (int i = 0; i < replaceValue.size(); ++i) {
++ if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.size() - 1) {
++ ushort ch = replaceValue.at(i + 1).unicode();
++ uint substStart = JSC::Yarr::offsetNoMatch;
++ uint substEnd = JSC::Yarr::offsetNoMatch;
++ int skip = 0;
++ if (ch == '$') {
++ *result += QChar(ch);
++ ++i;
++ continue;
++ } else if (ch == '&') {
++ substStart = matchOffsets[0];
++ substEnd = matchOffsets[1];
++ skip = 1;
++ } else if (ch == '`') {
++ substStart = 0;
++ substEnd = matchOffsets[0];
++ skip = 1;
++ } else if (ch == '\'') {
++ substStart = matchOffsets[1];
++ substEnd = input.size();
++ skip = 1;
++ } else if (ch >= '0' && ch <= '9') {
++ uint capture = ch - '0';
++ skip = 1;
++ if (i < replaceValue.size() - 2) {
++ ch = replaceValue.at(i + 2).unicode();
++ if (ch >= '0' && ch <= '9') {
++ uint c = capture*10 + ch - '0';
++ if (c < static_cast<uint>(captureCount)) {
++ capture = c;
++ skip = 2;
++ }
++ }
++ }
++ if (capture > 0 && capture < static_cast<uint>(captureCount)) {
++ substStart = matchOffsets[capture * 2];
++ substEnd = matchOffsets[capture * 2 + 1];
++ } else {
++ skip = 0;
++ }
++ }
++ i += skip;
++ if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
++ *result += QStringView{input}.mid(substStart, substEnd - substStart);
++ else if (skip == 0) // invalid capture reference. Taken as literal value
++ *result += replaceValue.at(i);
++ } else {
++ *result += replaceValue.at(i);
++ }
++ }
++ }
++
++ ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ QString string;
++ if (const StringObject *thisString = thisObject->as<StringObject>())
++ string = thisString->d()->string->toQString();
++ else
++ string = thisObject->toQString();
++
++ int numCaptures = 0;
++ int numStringMatches = 0;
++
++ uint allocatedMatchOffsets = 64;
++ uint _matchOffsets[64];
++ uint *matchOffsets = _matchOffsets;
++
++ Scope scope(b);
++ ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
++ Scoped<RegExpObject> regExp(scope, searchValue);
++ if (regExp) {
++ uint offset = 0;
++ uint nMatchOffsets = 0;
++
++ // We extract the pointer here to work around a compiler bug on Android.
++ Scoped<RegExp> re(scope, regExp->value());
++ while (true) {
++ int oldSize = nMatchOffsets;
++ if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) {
++ allocatedMatchOffsets = qMax(allocatedMatchOffsets * 2, nMatchOffsets + re->captureCount() * 2);
++ uint *newOffsets = (uint *)malloc(allocatedMatchOffsets*sizeof(uint));
++ memcpy(newOffsets, matchOffsets, nMatchOffsets*sizeof(uint));
++ if (matchOffsets != _matchOffsets)
++ free(matchOffsets);
++ matchOffsets = newOffsets;
++ }
++ if (re->match(string, offset, matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) {
++ nMatchOffsets = oldSize;
++ break;
++ }
++ nMatchOffsets += re->captureCount() * 2;
++ if (!regExp->global())
++ break;
++
++ const uint matchBegin = matchOffsets[oldSize];
++ const uint matchEnd = matchOffsets[oldSize + 1];
++
++ // If we have a zero-sized match, don't match at the same place again.
++ const uint matchOffset = (matchBegin == matchEnd) ? matchEnd + 1 : matchEnd;
++
++ offset = std::max(offset + 1, matchOffset);
++ }
++ if (regExp->global()) {
++ regExp->setLastIndex(0);
++ if (scope.hasException())
++ return Encode::undefined();
++ }
++ numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2);
++ numCaptures = regExp->value()->captureCount();
++ } else {
++ numCaptures = 1;
++ QString searchString = searchValue->toQString();
++ int idx = string.indexOf(searchString);
++ if (idx != -1) {
++ numStringMatches = 1;
++ matchOffsets[0] = idx;
++ matchOffsets[1] = idx + searchString.size();
++ }
++ }
++
++ QString result;
++ ScopedValue replacement(scope);
++ ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++ ScopedFunctionObject searchCallback(scope, replaceValue);
++ if (!!searchCallback) {
++ result.reserve(string.size() + 10*numStringMatches);
++ ScopedValue entry(scope);
++ Value *arguments = scope.constructUndefined(numCaptures + 2);
++ int lastEnd = 0;
++ for (int i = 0; i < numStringMatches; ++i) {
++ for (int k = 0; k < numCaptures; ++k) {
++ int idx = (i * numCaptures + k) * 2;
++ uint start = matchOffsets[idx];
++ uint end = matchOffsets[idx + 1];
++ entry = Value::undefinedValue();
++ if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
++ entry = scope.engine->newString(string.mid(start, end - start));
++ arguments[k] = entry;
++ }
++ uint matchStart = matchOffsets[i * numCaptures * 2];
++ Q_ASSERT(matchStart >= static_cast<uint>(lastEnd));
++ uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
++ arguments[numCaptures] = Value::fromUInt32(matchStart);
++ arguments[numCaptures + 1] = scope.engine->newString(string);
++
++ Value that = Value::undefinedValue();
++ replacement = searchCallback->call(&that, arguments, numCaptures + 2);
++ CHECK_EXCEPTION();
++ result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
++ result += replacement->toQString();
++ lastEnd = matchEnd;
++ }
++ result += QStringView{string}.mid(lastEnd);
++ } else {
++ QString newString = replaceValue->toQString();
++ result.reserve(string.size() + numStringMatches*newString.size());
++
++ int lastEnd = 0;
++ for (int i = 0; i < numStringMatches; ++i) {
++ int baseIndex = i * numCaptures * 2;
++ uint matchStart = matchOffsets[baseIndex];
++ uint matchEnd = matchOffsets[baseIndex + 1];
++ if (matchStart == JSC::Yarr::offsetNoMatch)
++ continue;
++
++ result += QStringView{string}.mid(lastEnd, matchStart - lastEnd);
++ appendReplacementString(&result, string, newString, matchOffsets + baseIndex, numCaptures);
++ lastEnd = matchEnd;
++ }
++ result += QStringView{string}.mid(lastEnd);
++ }
++
++ if (matchOffsets != _matchOffsets)
++ free(matchOffsets);
++
++ return Encode(scope.engine->newString(result));
++ }
++
++ ReturnedValue StringPrototype::method_search(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Q_ALLOCA_INIT();
++ Scope scope(b);
++ QString string = getThisString(scope.engine, thisObject);
++ if (scope.hasException())
++ return QV4::Encode::undefined();
++
++ Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
++ if (!regExp) {
++ regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1);
++ if (scope.hasException())
++ return QV4::Encode::undefined();
++
++ Q_ASSERT(regExp);
++ }
++ Scoped<RegExp> re(scope, regExp->value());
++ Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint));
++ uint result = re->match(string, /*offset*/0, matchOffsets);
++ if (result == JSC::Yarr::offsetNoMatch)
++ return Encode(-1);
++ else
++ return Encode(result);
++ }
++
++ ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ Scope scope(v4);
++ ScopedString s(scope, thisAsString(v4, thisObject));
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++ Q_ASSERT(s);
++
++ const double length = s->d()->length();
++
++ double start = argc ? argv[0].toInteger() : 0;
++ double end = (argc < 2 || argv[1].isUndefined())
++ ? length : argv[1].toInteger();
++
++ if (start < 0)
++ start = qMax(length + start, 0.);
++ else
++ start = qMin(start, length);
++
++ if (end < 0)
++ end = qMax(length + end, 0.);
++ else
++ end = qMin(end, length);
++
++ const int intStart = int(start);
++ const int intEnd = int(end);
++
++ int count = qMax(0, intEnd - intStart);
++ return Encode(v4->memoryManager->alloc<ComplexString>(s->d(), intStart, count));
++ }
++
++ ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Q_ALLOCA_INIT();
++ ExecutionEngine *v4 = b->engine();
++ QString text = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ Scope scope(v4);
++ ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
++ ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++
++ ScopedArrayObject array(scope, scope.engine->newArrayObject());
++
++ if (separatorValue->isUndefined()) {
++ if (limitValue->isUndefined()) {
++ ScopedString s(scope, scope.engine->newString(text));
++ array->push_back(s);
++ return array.asReturnedValue();
++ }
++ RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
++ }
++
++ uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
++
++ if (limit == 0)
++ return array.asReturnedValue();
++
++ Scoped<RegExpObject> re(scope, separatorValue);
++ if (re) {
++ if (re->value()->pattern->isEmpty()) {
++ re = (RegExpObject *)nullptr;
++ separatorValue = scope.engine->newString();
++ }
++ }
++
++ ScopedString s(scope);
++ if (re) {
++ uint offset = 0;
++ Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 * sizeof(uint));
++ while (true) {
++ Scoped<RegExp> regexp(scope, re->value());
++ uint result = regexp->match(text, offset, matchOffsets);
++ if (result == JSC::Yarr::offsetNoMatch)
++ break;
++
++ array->push_back((s = scope.engine->newString(text.mid(offset, matchOffsets[0] - offset))));
++ offset = qMax(offset + 1, matchOffsets[1]);
++
++ if (array->getLength() >= limit)
++ break;
++
++ for (int i = 1; i < re->value()->captureCount(); ++i) {
++ uint start = matchOffsets[i * 2];
++ uint end = matchOffsets[i * 2 + 1];
++ array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
++ if (array->getLength() >= limit)
++ break;
++ }
++ }
++ if (array->getLength() < limit)
++ array->push_back((s = scope.engine->newString(text.mid(offset))));
++ } else {
++ QString separator = separatorValue->toQString();
++ if (separator.isEmpty()) {
++ for (uint i = 0; i < qMin(limit, uint(text.size())); ++i)
++ array->push_back((s = scope.engine->newString(text.mid(i, 1))));
++ return array.asReturnedValue();
++ }
++
++ int start = 0;
++ int end;
++ while ((end = text.indexOf(separator, start)) != -1) {
++ array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
++ start = end + separator.size();
++ if (array->getLength() >= limit)
++ break;
++ }
++ if (array->getLength() < limit && start != -1)
++ array->push_back((s = scope.engine->newString(text.mid(start))));
++ }
++ return array.asReturnedValue();
++ }
++
++ ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ if (argc && argv[0].as<RegExpObject>())
++ return v4->throwTypeError();
++ QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ double pos = 0;
++ if (argc > 1)
++ pos = argv[1].toInteger();
++
++ pos = std::clamp(
++ pos,
++ 0.0,
++ double(value.size()));
++
++ if (pos == 0)
++ return Encode(value.startsWith(searchString));
++
++ QStringView stringToSearch = QStringView{value}.mid(pos);
++ RETURN_RESULT(Encode(stringToSearch.startsWith(searchString)));
++ }
++
++ ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ double start = 0;
++ if (argc > 0)
++ start = argv[0].toInteger();
++
++ double length = +qInf();
++ if (argc > 1)
++ length = argv[1].toInteger();
++
++ double count = value.size();
++ if (start < 0)
++ start = qMax(count + start, 0.0);
++
++ length = qMin(qMax(length, 0.0), count - start);
++
++ qint32 x = Value::toInt32(start);
++ qint32 y = Value::toInt32(length);
++ return Encode(v4->newString(value.mid(x, y)));
++ }
++
++ ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ int length = value.size();
++
++ double start = 0;
++ double end = length;
++
++ if (argc > 0)
++ start = argv[0].toInteger();
++
++ if (argc > 1 && !argv[1].isUndefined())
++ end = argv[1].toInteger();
++
++ if (std::isnan(start) || start < 0)
++ start = 0;
++
++ if (std::isnan(end) || end < 0)
++ end = 0;
++
++ if (start > length)
++ start = length;
++
++ if (end > length)
++ end = length;
++
++ if (start > end) {
++ double was = start;
++ start = end;
++ end = was;
++ }
++
++ qint32 x = (int)start;
++ qint32 y = (int)(end - start);
++ return Encode(v4->newString(value.mid(x, y)));
++ }
++
++ ReturnedValue StringPrototype::method_toLowerCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ return Encode(v4->newString(value.toLower()));
++ }
++
++ static const QLocale *getLocaleDataResource(const QV4::Value &val)
++ {
++ if (const QV4::QQmlValueTypeWrapper *wrapper = val.as<QQmlValueTypeWrapper>())
++ return wrapper->cast<const QLocale>();
++
++ return nullptr;
++ }
++
++ static QLocale getLocaleFromArgs(const FunctionObject *b, const QV4::Value *argv, int argc)
++ {
++ if (argc == 0)
++ return QLocale();
++
++ ExecutionEngine *v4 = b->engine();
++ Scope scope(b);
++ QString stringifiedLocale;
++ // First argument is a string, we check if it is a valid locale
++ if (const QV4::String *that = argv[0].as<QV4::String>()) {
++ stringifiedLocale = that->toQString();
++ } else if (const QLocale *locale = getLocaleDataResource(argv[0])) {
++ return *locale;
++ } else if (argv[0].isObject()) {
++ // First argument is an array, we check if the first element is
++ // a string and a valid locale.
++ ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
++ ScopedValue kValue(scope);
++ if (arrayLike->getLength() > 0) {
++ kValue = arrayLike->get(uint(0));
++ if (kValue->isString())
++ stringifiedLocale = kValue->toQString();
++ else if (const QLocale *locale = getLocaleDataResource(kValue))
++ return *locale;
++ }
++ } else {
++ v4->throwTypeError();
++ }
++
++ return QLocale(stringifiedLocale);
++ }
++
++ ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ QLocale locale = getLocaleFromArgs(b, argv, argc);
++ return Encode(v4->newString(locale.toLower(value)));
++ }
++
++ ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ return Encode(v4->newString(value.toUpper()));
++ }
++
++ ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ const QString value = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ QLocale locale = getLocaleFromArgs(b, argv, argc);
++ return Encode(v4->newString(locale.toUpper(value)));
++ }
++
++ ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ ExecutionEngine *v4 = b->engine();
++ QString s = getThisString(v4, thisObject);
++ if (v4->hasException)
++ return QV4::Encode::undefined();
++
++ const QChar *chars = s.constData();
++ int start, end;
++ for (start = 0; start < s.size(); ++start) {
++ if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff)
++ break;
++ }
++ for (end = s.size() - 1; end >= start; --end) {
++ if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff)
++ break;
++ }
++
++ return Encode(v4->newString(QString(chars + start, end - start + 1)));
++ }
++
++
++
++ ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedString s(scope, thisObject->toString(scope.engine));
++ if (!s || thisObject->isNullOrUndefined())
++ return scope.engine->throwTypeError();
++
++ Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(s->d(), scope.engine));
++ return si->asReturnedValue();
++ }