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 "qv4regexpobject_p.h"
++ #include "qv4regexp_p.h"
++ #include <private/qv4mm_p.h>
++ #include "qv4scopedvalue_p.h"
++ #include "qv4jscall_p.h"
++ #include "qv4symbol_p.h"
++
++ #include "private/qlocale_tools_p.h"
++
++ #include <QtCore/QDebug>
++ #if QT_CONFIG(regularexpression)
++ #include <QtCore/qregularexpression.h>
++ #endif
++ #include <cassert>
++ #include <typeinfo>
++ #include <iostream>
++ #include <private/qv4alloca_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace QV4;
++
++ DEFINE_OBJECT_VTABLE(RegExpObject);
++
++ void Heap::RegExpObject::init()
++ {
++ Object::init();
++ Scope scope(internalClass->engine);
++ Scoped<QV4::RegExpObject> o(scope, this);
++ value.set(scope.engine, QV4::RegExp::create(scope.engine, QString(), CompiledData::RegExp::RegExp_NoFlags));
++ o->initProperties();
++ }
++
++ void Heap::RegExpObject::init(QV4::RegExp *value)
++ {
++ Object::init();
++ Scope scope(internalClass->engine);
++ this->value.set(scope.engine, value->d());
++ Scoped<QV4::RegExpObject> o(scope, this);
++ o->initProperties();
++ }
++
++ static QString minimalPattern(const QString &pattern)
++ {
++ QString ecmaPattern;
++ int len = pattern.size();
++ ecmaPattern.reserve(len);
++ int i = 0;
++ const QChar *wc = pattern.unicode();
++ bool inBracket = false;
++ while (i < len) {
++ QChar c = wc[i++];
++ ecmaPattern += c;
++ switch (c.unicode()) {
++ case '?':
++ case '+':
++ case '*':
++ case '}':
++ if (!inBracket)
++ ecmaPattern += QLatin1Char('?');
++ break;
++ case '\\':
++ if (i < len)
++ ecmaPattern += wc[i++];
++ break;
++ case '[':
++ inBracket = true;
++ break;
++ case ']':
++ inBracket = false;
++ break;
++ default:
++ break;
++ }
++ }
++ return ecmaPattern;
++ }
++
++ #if QT_CONFIG(regularexpression)
++ // Converts a QRegularExpression to a JS RegExp.
++ // The conversion is not 100% exact since ECMA regexp and QRegularExpression
++ // have different semantics/flags, but we try to do our best.
++ void Heap::RegExpObject::init(const QRegularExpression &re)
++ {
++ Object::init();
++
++ Scope scope(internalClass->engine);
++ Scoped<QV4::RegExpObject> o(scope, this);
++
++ QRegularExpression::PatternOptions options = re.patternOptions();
++ CompiledData::RegExp::Flags flags = (options & QRegularExpression::CaseInsensitiveOption)
++ ? CompiledData::RegExp::RegExp_IgnoreCase
++ : CompiledData::RegExp::RegExp_NoFlags;
++ if (options & QRegularExpression::MultilineOption)
++ flags |= CompiledData::RegExp::RegExp_Multiline;
++ QString pattern = re.pattern();
++ if (options & QRegularExpression::InvertedGreedinessOption)
++ pattern = minimalPattern(pattern);
++ o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, pattern, flags));
++ o->initProperties();
++ }
++ #endif
++
++ void RegExpObject::initProperties()
++ {
++ setProperty(Index_LastIndex, Value::fromInt32(0));
++
++ Q_ASSERT(value());
++ }
++
++ #if QT_CONFIG(regularexpression)
++ // Converts a JS RegExp to a QRegularExpression.
++ // The conversion is not 100% exact since ECMA regexp and QRegularExpression
++ // have different semantics/flags, but we try to do our best.
++ QRegularExpression RegExpObject::toQRegularExpression() const
++ {
++ QRegularExpression::PatternOptions options = QRegularExpression::NoPatternOption;
++ if (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase)
++ options |= QRegularExpression::CaseInsensitiveOption;
++ if (value()->flags & CompiledData::RegExp::RegExp_Multiline)
++ options |= QRegularExpression::MultilineOption;
++ return QRegularExpression(*value()->pattern, options);
++ }
++ #endif
++
++ QString RegExpObject::toString() const
++ {
++ QString p = *value()->pattern;
++ if (p.isEmpty()) {
++ p = QStringLiteral("(?:)");
++ } else {
++ // escape certain parts, see ch. 15.10.4
++ p.replace(u'/', QLatin1String("\\/"));
++ }
++ return p;
++ }
++
++ ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str)
++ {
++ Q_ALLOCA_INIT();
++ QString s = str->toQString();
++
++ Scope scope(engine);
++ int offset = (global() || sticky()) ? lastIndex() : 0;
++ if (offset < 0 || offset > s.size()) {
++ setLastIndex(0);
++ RETURN_RESULT(Encode::null());
++ }
++
++ Q_ALLOCA_VAR(uint, matchOffsets, value()->captureCount() * 2 * sizeof(uint));
++ const uint result = Scoped<RegExp>(scope, value())->match(s, offset, matchOffsets);
++
++ RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
++ regExpCtor->d()->clearLastMatch();
++
++ if (result == JSC::Yarr::offsetNoMatch) {
++ if (global() || sticky())
++ setLastIndex(0);
++ RETURN_RESULT(Encode::null());
++ }
++
++ Q_ASSERT(result <= uint(std::numeric_limits<int>::max()));
++
++ // fill in result data
++ ScopedArrayObject array(scope, scope.engine->newArrayObject(scope.engine->internalClasses(EngineBase::Class_RegExpExecArray)));
++ int len = value()->captureCount();
++ array->arrayReserve(len);
++ ScopedValue v(scope);
++ int strlen = s.size();
++ for (int i = 0; i < len; ++i) {
++ int start = matchOffsets[i * 2];
++ int end = matchOffsets[i * 2 + 1];
++ if (end > strlen)
++ end = strlen;
++ v = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
++ array->arrayPut(i, v);
++ }
++ array->setArrayLengthUnchecked(len);
++ array->setProperty(Index_ArrayIndex, Value::fromInt32(int(result)));
++ array->setProperty(Index_ArrayInput, *str);
++
++ RegExpCtor::Data *dd = regExpCtor->d();
++ dd->lastMatch.set(scope.engine, array);
++ dd->lastInput.set(scope.engine, str->d());
++ dd->lastMatchStart = matchOffsets[0];
++ dd->lastMatchEnd = matchOffsets[1];
++
++ if (global() || sticky())
++ setLastIndex(matchOffsets[1]);
++
++ return array.asReturnedValue();
++ }
++
++ DEFINE_OBJECT_VTABLE(RegExpCtor);
++
++ void Heap::RegExpCtor::init(QV4::ExecutionEngine *engine)
++ {
++ Heap::FunctionObject::init(engine, QStringLiteral("RegExp"));
++ clearLastMatch();
++ }
++
++ void Heap::RegExpCtor::clearLastMatch()
++ {
++ lastMatch.set(internalClass->engine, Value::nullValue());
++ lastInput.set(internalClass->engine, internalClass->engine->id_empty()->d());
++ lastMatchStart = 0;
++ lastMatchEnd = 0;
++ }
++
++ static bool isRegExp(ExecutionEngine *e, const QV4::Value *arg)
++ {
++ const QV4::Object *o = arg->objectValue();
++ if (!o)
++ return false;
++
++ QV4::Value isRegExp = QV4::Value::fromReturnedValue(o->get(e->symbol_match()));
++ if (!isRegExp.isUndefined())
++ return isRegExp.toBoolean();
++ const RegExpObject *re = o->as<RegExpObject>();
++ return re ? true : false;
++ }
++
++ static CompiledData::RegExp::Flags parseFlags(Scope &scope, const QV4::Value *f)
++ {
++ CompiledData::RegExp::Flags flags = CompiledData::RegExp::RegExp_NoFlags;
++ if (!f->isUndefined()) {
++ ScopedString s(scope, f->toString(scope.engine));
++ if (scope.hasException())
++ return flags;
++ QString str = s->toQString();
++ for (int i = 0; i < str.size(); ++i) {
++ if (str.at(i) == QLatin1Char('g') && !(flags & CompiledData::RegExp::RegExp_Global)) {
++ flags |= CompiledData::RegExp::RegExp_Global;
++ } else if (str.at(i) == QLatin1Char('i') && !(flags & CompiledData::RegExp::RegExp_IgnoreCase)) {
++ flags |= CompiledData::RegExp::RegExp_IgnoreCase;
++ } else if (str.at(i) == QLatin1Char('m') && !(flags & CompiledData::RegExp::RegExp_Multiline)) {
++ flags |= CompiledData::RegExp::RegExp_Multiline;
++ } else if (str.at(i) == QLatin1Char('u') && !(flags & CompiledData::RegExp::RegExp_Unicode)) {
++ flags |= CompiledData::RegExp::RegExp_Unicode;
++ } else if (str.at(i) == QLatin1Char('y') && !(flags & CompiledData::RegExp::RegExp_Sticky)) {
++ flags |= CompiledData::RegExp::RegExp_Sticky;
++ } else {
++ scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor"));
++ return flags;
++ }
++ }
++ }
++ return flags;
++ }
++
++ ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget)
++ {
++ Scope scope(fo);
++
++ bool patternIsRegExp = argc ? ::isRegExp(scope.engine, argv) : false;
++
++ if (newTarget == fo) {
++ if (patternIsRegExp && (argc < 2 || argv[1].isUndefined())) {
++ const Object *pattern = static_cast<const Object *>(argv);
++ ScopedValue patternConstructor(scope, pattern->get(scope.engine->id_constructor()));
++ if (patternConstructor->sameValue(*newTarget))
++ return pattern->asReturnedValue();
++ }
++ }
++
++ ScopedValue p(scope, argc ? argv[0] : Value::undefinedValue());
++ ScopedValue f(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++ Scoped<RegExpObject> re(scope, p);
++ QString pattern;
++ CompiledData::RegExp::Flags flags = CompiledData::RegExp::RegExp_NoFlags;
++
++ if (re) {
++ if (f->isUndefined()) {
++ Scoped<RegExp> regexp(scope, re->value());
++ return Encode(scope.engine->newRegExpObject(regexp));
++ }
++ pattern = *re->value()->pattern;
++ flags = parseFlags(scope, f);
++ } else if (patternIsRegExp) {
++ const Object *po = static_cast<const Object *>(argv);
++ p = po->get(scope.engine->id_source());
++ if (!p->isUndefined())
++ pattern = p->toQString();
++ if (scope.hasException())
++ return Encode::undefined();
++ if (f->isUndefined())
++ f = po->get(scope.engine->id_flags());
++ flags = parseFlags(scope, f);
++ } else {
++ if (!p->isUndefined())
++ pattern = p->toQString();
++ if (scope.hasException())
++ return Encode::undefined();
++ flags = parseFlags(scope, f);
++ }
++ if (scope.hasException())
++ return Encode::undefined();
++
++ Scoped<RegExp> regexp(scope, RegExp::create(scope.engine, pattern, flags));
++ if (!regexp->isValid()) {
++ return scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression"));
++ }
++
++ ReturnedValue o = Encode(scope.engine->newRegExpObject(regexp));
++
++ if (!newTarget)
++ return o;
++ ScopedObject obj(scope, o);
++ obj->setProtoFromNewTarget(newTarget);
++ return obj->asReturnedValue();
++ }
++
++ ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc)
++ {
++ return virtualCallAsConstructor(f, argv, argc, f);
++ }
++
++ void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor)
++ {
++ Scope scope(engine);
++ ScopedObject o(scope);
++ ScopedObject ctor(scope, constructor);
++
++ ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
++ ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(2));
++ ctor->addSymbolSpecies();
++
++ // Properties deprecated in the spec but required by "the web" :(
++ ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, nullptr);
++ ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, nullptr);
++
++ defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
++ defineAccessorProperty(scope.engine->id_flags(), method_get_flags, nullptr);
++ defineAccessorProperty(scope.engine->id_global(), method_get_global, nullptr);
++ defineAccessorProperty(scope.engine->id_ignoreCase(), method_get_ignoreCase, nullptr);
++ defineDefaultProperty(QStringLiteral("exec"), method_exec, 1);
++ defineDefaultProperty(engine->symbol_match(), method_match, 1);
++ defineAccessorProperty(scope.engine->id_multiline(), method_get_multiline, nullptr);
++ defineDefaultProperty(engine->symbol_replace(), method_replace, 2);
++ defineDefaultProperty(engine->symbol_search(), method_search, 1);
++ defineAccessorProperty(scope.engine->id_source(), method_get_source, nullptr);
++ defineDefaultProperty(engine->symbol_split(), method_split, 2);
++ defineAccessorProperty(scope.engine->id_sticky(), method_get_sticky, nullptr);
++ defineDefaultProperty(QStringLiteral("test"), method_test, 1);
++ defineDefaultProperty(engine->id_toString(), method_toString, 0);
++ defineAccessorProperty(scope.engine->id_unicode(), method_get_unicode, nullptr);
++
++ // another web extension
++ defineDefaultProperty(QStringLiteral("compile"), method_compile, 2);
++ }
++
++ /* used by String.match */
++ ReturnedValue RegExpPrototype::execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Q_ALLOCA_INIT();
++ Scope scope(b);
++ Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
++ Q_ASSERT(r && r->global());
++
++ ScopedString str(scope, argc ? argv[0] : Value::undefinedValue());
++ Q_ASSERT(str);
++ QString s = str->toQString();
++
++ int offset = r->lastIndex();
++ if (offset < 0 || offset > s.size()) {
++ r->setLastIndex(0);
++ RETURN_RESULT(Encode::null());
++ }
++
++ Q_ALLOCA_VAR(uint, matchOffsets, r->value()->captureCount() * 2 * sizeof(uint));
++ const int result = Scoped<RegExp>(scope, r->value())->match(s, offset, matchOffsets);
++
++ RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
++ regExpCtor->d()->clearLastMatch();
++
++ if (result == -1) {
++ r->setLastIndex(0);
++ RETURN_RESULT(Encode::null());
++ }
++
++ ReturnedValue retVal = Encode::undefined();
++ // return first match
++ if (r->value()->captureCount()) {
++ int start = matchOffsets[0];
++ int end = matchOffsets[1];
++ retVal = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
++ }
++
++ RegExpCtor::Data *dd = regExpCtor->d();
++ dd->lastInput.set(scope.engine, str->d());
++ dd->lastMatchStart = matchOffsets[0];
++ dd->lastMatchEnd = matchOffsets[1];
++
++ r->setLastIndex(matchOffsets[1]);
++
++ return retVal;
++ }
++
++ ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, const String *s)
++ {
++ Scope scope(engine);
++ ScopedString key(scope, scope.engine->newString(QStringLiteral("exec")));
++ ScopedFunctionObject exec(scope, o->get(key));
++ if (exec) {
++ ScopedValue result(scope, exec->call(o, s, 1));
++ if (scope.hasException())
++ RETURN_UNDEFINED();
++ if (!result->isNull() && !result->isObject())
++ return scope.engine->throwTypeError();
++ return result->asReturnedValue();
++ }
++ Scoped<RegExpObject> re(scope, o);
++ if (!re)
++ return scope.engine->throwTypeError();
++ return re->builtinExec(engine, s);
++ }
++
++ ReturnedValue RegExpPrototype::method_exec(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
++ if (!r)
++ return scope.engine->throwTypeError();
++
++ ScopedValue arg(scope, argc ? argv[0] : Value::undefinedValue());
++ ScopedString str(scope, arg->toString(scope.engine));
++ if (scope.hasException())
++ RETURN_UNDEFINED();
++
++ return r->builtinExec(scope.engine, str);
++ }
++
++ ReturnedValue RegExpPrototype::method_get_flags(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ ScopedObject o(scope, thisObject);
++ if (!o)
++ return scope.engine->throwTypeError();
++
++ QString result;
++ ScopedValue v(scope);
++ v = o->get(scope.engine->id_global());
++ if (scope.hasException())
++ return Encode::undefined();
++ if (v->toBoolean())
++ result += QLatin1Char('g');
++ v = o->get(scope.engine->id_ignoreCase());
++ if (scope.hasException())
++ return Encode::undefined();
++ if (v->toBoolean())
++ result += QLatin1Char('i');
++ v = o->get(scope.engine->id_multiline());
++ if (scope.hasException())
++ return Encode::undefined();
++ if (v->toBoolean())
++ result += QLatin1Char('m');
++ v = o->get(scope.engine->id_unicode());
++ if (scope.hasException())
++ return Encode::undefined();
++ if (v->toBoolean())
++ result += QLatin1Char('u');
++ v = o->get(scope.engine->id_sticky());
++ if (scope.hasException())
++ return Encode::undefined();
++ if (v->toBoolean())
++ result += QLatin1Char('y');
++ return scope.engine->newString(result)->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_get_global(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return Encode::undefined();
++ return scope.engine->throwTypeError();
++ }
++
++ bool b = re->value()->flags & CompiledData::RegExp::RegExp_Global;
++ return Encode(b);
++ }
++
++ ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return Encode::undefined();
++ return scope.engine->throwTypeError();
++ }
++
++ bool b = re->value()->flags & CompiledData::RegExp::RegExp_IgnoreCase;
++ return Encode(b);
++ }
++
++ static int advanceStringIndex(int index, const QString &str, bool unicode)
++ {
++ if (unicode) {
++ if (index < str.size() - 1 &&
++ str.at(index).isHighSurrogate() &&
++ str.at(index + 1).isLowSurrogate())
++ ++index;
++ }
++ ++index;
++ return index;
++ }
++
++ static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, QV4::Object *rx, const String *matchString, const QString &str)
++ {
++ Scope scope(e);
++ if (matchString->d()->length() == 0) {
++ QV4::ScopedValue v(scope, rx->get(scope.engine->id_lastIndex()));
++ int lastIndex = advanceStringIndex(v->toLength(), str, unicode);
++ if (!rx->put(scope.engine->id_lastIndex(), QV4::Value::fromInt32(lastIndex)))
++ scope.engine->throwTypeError();
++ }
++ }
++
++ ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ ScopedObject rx(scope, thisObject);
++ if (!rx)
++ return scope.engine->throwTypeError();
++ ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++ bool global = ScopedValue(scope, rx->get(scope.engine->id_global()))->toBoolean();
++
++ if (!global)
++ return exec(scope.engine, rx, s);
++
++ bool unicode = ScopedValue(scope, rx->get(scope.engine->id_unicode()))->toBoolean();
++
++ rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0));
++ ScopedArrayObject a(scope, scope.engine->newArrayObject());
++ uint n = 0;
++
++ ScopedValue result(scope);
++ ScopedValue match(scope);
++ ScopedString matchString(scope);
++ ScopedValue v(scope);
++ while (1) {
++ result = exec(scope.engine, rx, s);
++ if (scope.hasException())
++ return Encode::undefined();
++ if (result->isNull()) {
++ if (!n)
++ return Encode::null();
++ return a->asReturnedValue();
++ }
++ Q_ASSERT(result->isObject());
++ match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
++ matchString = match->toString(scope.engine);
++ if (scope.hasException())
++ return Encode::undefined();
++ a->push_back(matchString);
++ advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
++ ++n;
++ }
++ }
++
++ ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return Encode::undefined();
++ return scope.engine->throwTypeError();
++ }
++
++ bool b = re->value()->flags & CompiledData::RegExp::RegExp_Multiline;
++ return Encode(b);
++ }
++
++ ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ ScopedObject rx(scope, thisObject);
++ if (!rx)
++ return scope.engine->throwTypeError();
++
++ ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ int lengthS = s->toQString().size();
++
++ ScopedString replaceValue(scope);
++ ScopedFunctionObject replaceFunction(scope, (argc > 1 ? argv[1] : Value::undefinedValue()));
++ bool functionalReplace = !!replaceFunction;
++ if (!functionalReplace)
++ replaceValue = (argc > 1 ? argv[1] : Value::undefinedValue()).toString(scope.engine);
++
++ ScopedValue v(scope);
++ bool global = (v = rx->get(scope.engine->id_global()))->toBoolean();
++ bool unicode = false;
++ if (global) {
++ unicode = (v = rx->get(scope.engine->id_unicode()))->toBoolean();
++ if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
++ return scope.engine->throwTypeError();
++ }
++
++ ScopedArrayObject results(scope, scope.engine->newArrayObject());
++ ScopedValue result(scope);
++ ScopedValue match(scope);
++ ScopedString matchString(scope);
++ while (1) {
++ result = exec(scope.engine, rx, s);
++ if (scope.hasException())
++ return Encode::undefined();
++ if (result->isNull())
++ break;
++ results->push_back(result);
++ if (!global)
++ break;
++ match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
++ matchString = match->toString(scope.engine);
++ if (scope.hasException())
++ return Encode::undefined();
++ advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
++ }
++ QString accumulatedResult;
++ int nextSourcePosition = 0;
++ int resultsLength = results->getLength();
++ ScopedObject resultObject(scope);
++ for (int i = 0; i < resultsLength; ++i) {
++ resultObject = results->get(PropertyKey::fromArrayIndex(i));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ int nCaptures = resultObject->getLength();
++ nCaptures = qMax(nCaptures - 1, 0);
++ match = resultObject->get(PropertyKey::fromArrayIndex(0));
++ matchString = match->toString(scope.engine);
++ if (scope.hasException())
++ return Encode::undefined();
++ QString m = matchString->toQString();
++ int matchLength = m.size();
++ v = resultObject->get(scope.engine->id_index());
++ int position = v->toInt32();
++ position = qMax(qMin(position, lengthS), 0);
++ if (scope.hasException())
++ return Encode::undefined();
++
++ int n = 1;
++ Scope innerScope(scope.engine);
++ JSCallArguments cData(scope, nCaptures + 3);
++ while (n <= nCaptures) {
++ v = resultObject->get(PropertyKey::fromArrayIndex(n));
++ if (!v->isUndefined())
++ cData.args[n] = v->toString(scope.engine);
++ ++n;
++ }
++ QString replacement;
++ if (functionalReplace) {
++ cData.args[0] = matchString;
++ cData.args[nCaptures + 1] = Encode(position);
++ cData.args[nCaptures + 2] = s;
++ ScopedValue replValue(scope, replaceFunction->call(cData));
++ if (scope.hasException())
++ return Encode::undefined();
++ replacement = replValue->toQString();
++ } else {
++ replacement = RegExp::getSubstitution(matchString->toQString(), s->toQString(), position, cData.args, nCaptures, replaceValue->toQString());
++ }
++ if (scope.hasException())
++ return Encode::undefined();
++ if (position >= nextSourcePosition) {
++ accumulatedResult += QStringView{s->toQString()}.mid(nextSourcePosition, position - nextSourcePosition) + replacement;
++ nextSourcePosition = position + matchLength;
++ }
++ }
++ if (nextSourcePosition < lengthS) {
++ accumulatedResult += QStringView{s->toQString()}.mid(nextSourcePosition);
++ }
++ return scope.engine->newString(accumulatedResult)->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ ScopedObject rx(scope, thisObject);
++ if (!rx)
++ return scope.engine->throwTypeError();
++
++ ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ ScopedValue previousLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
++ if (previousLastIndex->toNumber() != 0) {
++ if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
++ return scope.engine->throwTypeError();
++ }
++
++ ScopedValue result(scope, exec(scope.engine, rx, s));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ ScopedValue currentLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
++ if (!currentLastIndex->sameValue(previousLastIndex)) {
++ if (!rx->put(scope.engine->id_lastIndex(), previousLastIndex))
++ return scope.engine->throwTypeError();
++ }
++
++ if (result->isNull())
++ return Encode(-1);
++ ScopedObject o(scope, result);
++ Q_ASSERT(o);
++ return o->get(scope.engine->id_index());
++ }
++
++
++ ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return scope.engine->newString(QStringLiteral("(?:)"))->asReturnedValue();
++ return scope.engine->throwTypeError();
++ }
++
++ return scope.engine->newString(re->toString())->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_split(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(f);
++ ScopedObject rx(scope, thisObject);
++ if (!rx)
++ return scope.engine->throwTypeError();
++
++ ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ ScopedValue flagsValue(scope, rx->get(scope.engine->id_flags()));
++ ScopedString flags(scope, flagsValue->toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++ QString flagsString = flags->toQString();
++ if (!flagsString.contains(QLatin1Char('y')))
++ flags = scope.engine->newString(flagsString + QLatin1Char('y'));
++ bool unicodeMatching = flagsString.contains(QLatin1Char('u'));
++
++ const FunctionObject *C = rx->speciesConstructor(scope, scope.engine->regExpCtor());
++ if (!C)
++ return Encode::undefined();
++
++ Value *args = scope.constructUndefined(2);
++ args[0] = rx;
++ args[1] = flags;
++ ScopedObject splitter(scope, C->callAsConstructor(args, 2, f));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ ScopedArrayObject A(scope, scope.engine->newArrayObject());
++ uint lengthA = 0;
++ uint limit = argc < 2 ? UINT_MAX : argv[1].toUInt32();
++ if (limit == 0)
++ return A->asReturnedValue();
++
++ QString S = s->toQString();
++ int size = S.size();
++ if (size == 0) {
++ ScopedValue z(scope, exec(scope.engine, splitter, s));
++ if (z->isNull())
++ A->push_back(s);
++ return A->asReturnedValue();
++ }
++
++ int p = 0;
++ int q = 0;
++ ScopedValue v(scope);
++ ScopedValue z(scope);
++ ScopedObject zz(scope);
++ ScopedString t(scope);
++ while (q < size) {
++ Value qq = Value::fromInt32(q);
++ if (!splitter->put(scope.engine->id_lastIndex(), qq))
++ return scope.engine->throwTypeError();
++ z = exec(scope.engine, splitter, s);
++ if (scope.hasException())
++ return Encode::undefined();
++
++ if (z->isNull()) {
++ q = advanceStringIndex(q, S, unicodeMatching);
++ continue;
++ }
++
++ v = splitter->get(scope.engine->id_lastIndex());
++ int e = qMin(v->toInt32(), size);
++ if (e == p) {
++ q = advanceStringIndex(q, S, unicodeMatching);
++ continue;
++ }
++ QString T = S.mid(p, q - p);
++ t = scope.engine->newString(T);
++ A->push_back(t);
++ ++lengthA;
++ if (lengthA == limit)
++ return A->asReturnedValue();
++ p = e;
++ zz = *z;
++ uint numberOfCaptures = qMax(zz->getLength() - 1, 0ll);
++ for (uint i = 1; i <= numberOfCaptures; ++i) {
++ v = zz->get(PropertyKey::fromArrayIndex(i));
++ A->push_back(v);
++ ++lengthA;
++ if (lengthA == limit)
++ return A->asReturnedValue();
++ }
++ q = p;
++ }
++
++ QString T = S.mid(p);
++ t = scope.engine->newString(T);
++ A->push_back(t);
++ return A->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_get_sticky(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return Encode::undefined();
++ return scope.engine->throwTypeError();
++ }
++
++ bool b = re->value()->flags & CompiledData::RegExp::RegExp_Sticky;
++ return Encode(b);
++ }
++
++ ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Value res = Value::fromReturnedValue(method_exec(b, thisObject, argv, argc));
++ return Encode(!res.isNull());
++ }
++
++ ReturnedValue RegExpPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(b);
++ const Object *r = thisObject->as<Object>();
++ if (!r)
++ return scope.engine->throwTypeError();
++
++ ScopedValue v(scope);
++ v = r->get(scope.engine->id_source());
++ ScopedString source(scope, v->toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++ v = r->get(scope.engine->id_flags());
++ ScopedString flags(scope, v->toString(scope.engine));
++ if (scope.hasException())
++ return Encode::undefined();
++
++ QString result = QLatin1Char('/') + source->toQString() + QLatin1Char('/') + flags->toQString();
++ return Encode(scope.engine->newString(result));
++ }
++
++ ReturnedValue RegExpPrototype::method_get_unicode(const FunctionObject *f, const Value *thisObject, const Value *, int)
++ {
++ Scope scope(f);
++ Scoped<RegExpObject> re(scope, thisObject);
++ if (!re) {
++ if (thisObject->sameValue(*scope.engine->regExpPrototype()))
++ return Encode::undefined();
++ return scope.engine->throwTypeError();
++ }
++
++ bool b = re->value()->flags & CompiledData::RegExp::RegExp_Unicode;
++ return Encode(b);
++ }
++
++ ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
++ if (!r)
++ return scope.engine->throwTypeError();
++
++ Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc));
++ if (re) // Otherwise the regexp constructor should have thrown an exception
++ r->d()->value.set(scope.engine, re->value());
++ return Encode::undefined();
++ }
++
++ template <uint index>
++ ReturnedValue RegExpPrototype::method_get_lastMatch_n(const FunctionObject *b, const Value *, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
++ ScopedValue res(scope, lastMatch ? lastMatch->get(index) : Encode::undefined());
++ if (res->isUndefined())
++ res = scope.engine->newString();
++ return res->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_get_lastParen(const FunctionObject *b, const Value *, const Value *, int)
++ {
++ Scope scope(b);
++ ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
++ ScopedValue res(scope, lastMatch ? lastMatch->get(lastMatch->getLength() - 1) : Encode::undefined());
++ if (res->isUndefined())
++ res = scope.engine->newString();
++ return res->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_get_input(const FunctionObject *b, const Value *, const Value *, int)
++ {
++ return static_cast<RegExpCtor*>(b->engine()->regExpCtor())->lastInput()->asReturnedValue();
++ }
++
++ ReturnedValue RegExpPrototype::method_get_leftContext(const FunctionObject *b, const Value *, const Value *, int)
++ {
++ Scope scope(b);
++ Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
++ QString lastInput = regExpCtor->lastInput()->toQString();
++ return Encode(scope.engine->newString(lastInput.left(regExpCtor->lastMatchStart())));
++ }
++
++ ReturnedValue RegExpPrototype::method_get_rightContext(const FunctionObject *b, const Value *, const Value *, int)
++ {
++ Scope scope(b);
++ Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
++ QString lastInput = regExpCtor->lastInput()->toQString();
++ return Encode(scope.engine->newString(lastInput.mid(regExpCtor->lastMatchEnd())));
++ }
++
++ QT_END_NAMESPACE