1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQml module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qv4regexpobject_p.h"
41 #include "qv4objectproto_p.h"
42 #include "qv4regexp_p.h"
43 #include "qv4stringobject_p.h"
44 #include <private/qv4mm_p.h>
45 #include "qv4scopedvalue_p.h"
46 #include "qv4jscall_p.h"
47 #include "qv4symbol_p.h"
48 
49 #include "private/qlocale_tools_p.h"
50 
51 #include <QtCore/QDebug>
52 #include <QtCore/qregexp.h>
53 #if QT_CONFIG(regularexpression)
54 #include <QtCore/qregularexpression.h>
55 #endif
56 #include <cassert>
57 #include <typeinfo>
58 #include <iostream>
59 #include <private/qv4alloca_p.h>
60 
61 QT_BEGIN_NAMESPACE
62 
63 Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax);
64 
65 using namespace QV4;
66 
67 DEFINE_OBJECT_VTABLE(RegExpObject);
68 
init()69 void Heap::RegExpObject::init()
70 {
71     Object::init();
72     Scope scope(internalClass->engine);
73     Scoped<QV4::RegExpObject> o(scope, this);
74     value.set(scope.engine, QV4::RegExp::create(scope.engine, QString(), CompiledData::RegExp::RegExp_NoFlags));
75     o->initProperties();
76 }
77 
init(QV4::RegExp * value)78 void Heap::RegExpObject::init(QV4::RegExp *value)
79 {
80     Object::init();
81     Scope scope(internalClass->engine);
82     this->value.set(scope.engine, value->d());
83     Scoped<QV4::RegExpObject> o(scope, this);
84     o->initProperties();
85 }
86 
87 // Converts a QRegExp to a JS RegExp.
88 // The conversion is not 100% exact since ECMA regexp and QRegExp
89 // have different semantics/flags, but we try to do our best.
init(const QRegExp & re)90 void Heap::RegExpObject::init(const QRegExp &re)
91 {
92     Object::init();
93 
94     // Convert the pattern to a ECMAScript pattern.
95     QString pattern = QT_PREPEND_NAMESPACE(qt_regexp_toCanonical)(re.pattern(), re.patternSyntax());
96     if (re.isMinimal()) {
97         QString ecmaPattern;
98         int len = pattern.length();
99         ecmaPattern.reserve(len);
100         int i = 0;
101         const QChar *wc = pattern.unicode();
102         bool inBracket = false;
103         while (i < len) {
104             QChar c = wc[i++];
105             ecmaPattern += c;
106             switch (c.unicode()) {
107             case '?':
108             case '+':
109             case '*':
110             case '}':
111                 if (!inBracket)
112                     ecmaPattern += QLatin1Char('?');
113                 break;
114             case '\\':
115                 if (i < len)
116                     ecmaPattern += wc[i++];
117                 break;
118             case '[':
119                 inBracket = true;
120                 break;
121             case ']':
122                 inBracket = false;
123                 break;
124             default:
125                 break;
126             }
127         }
128         pattern = ecmaPattern;
129     }
130 
131     Scope scope(internalClass->engine);
132     Scoped<QV4::RegExpObject> o(scope, this);
133 
134     uint flags = (re.caseSensitivity() == Qt::CaseInsensitive ? CompiledData::RegExp::RegExp_IgnoreCase : CompiledData::RegExp::RegExp_NoFlags);
135     o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, pattern, flags));
136 
137     o->initProperties();
138 }
139 
140 #if QT_CONFIG(regularexpression)
141 // Converts a QRegularExpression to a JS RegExp.
142 // The conversion is not 100% exact since ECMA regexp and QRegularExpression
143 // have different semantics/flags, but we try to do our best.
init(const QRegularExpression & re)144 void Heap::RegExpObject::init(const QRegularExpression &re)
145 {
146     Object::init();
147 
148     Scope scope(internalClass->engine);
149     Scoped<QV4::RegExpObject> o(scope, this);
150 
151     const uint flags = (re.patternOptions() & QRegularExpression::CaseInsensitiveOption)
152             ? CompiledData::RegExp::RegExp_IgnoreCase
153             : CompiledData::RegExp::RegExp_NoFlags;
154     o->d()->value.set(scope.engine, QV4::RegExp::create(scope.engine, re.pattern(), flags));
155     o->initProperties();
156 }
157 #endif
158 
initProperties()159 void RegExpObject::initProperties()
160 {
161     setProperty(Index_LastIndex, Value::fromInt32(0));
162 
163     Q_ASSERT(value());
164 }
165 
166 // Converts a JS RegExp to a QRegExp.
167 // The conversion is not 100% exact since ECMA regexp and QRegExp
168 // have different semantics/flags, but we try to do our best.
toQRegExp() const169 QRegExp RegExpObject::toQRegExp() const
170 {
171     Qt::CaseSensitivity caseSensitivity = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase) ? Qt::CaseInsensitive : Qt::CaseSensitive;
172     return QRegExp(*value()->pattern, caseSensitivity, QRegExp::RegExp2);
173 }
174 
175 #if QT_CONFIG(regularexpression)
176 // Converts a JS RegExp to a QRegularExpression.
177 // The conversion is not 100% exact since ECMA regexp and QRegularExpression
178 // have different semantics/flags, but we try to do our best.
toQRegularExpression() const179 QRegularExpression RegExpObject::toQRegularExpression() const
180 {
181     QRegularExpression::PatternOptions caseSensitivity
182             = (value()->flags & CompiledData::RegExp::RegExp_IgnoreCase)
183             ? QRegularExpression::CaseInsensitiveOption
184             : QRegularExpression::NoPatternOption;
185     return QRegularExpression(*value()->pattern, caseSensitivity);
186 }
187 #endif
188 
toString() const189 QString RegExpObject::toString() const
190 {
191     QString p = *value()->pattern;
192     if (p.isEmpty()) {
193         p = QStringLiteral("(?:)");
194     } else {
195         // escape certain parts, see ch. 15.10.4
196         p.replace('/', QLatin1String("\\/"));
197     }
198     return p;
199 }
200 
builtinExec(ExecutionEngine * engine,const String * str)201 ReturnedValue RegExpObject::builtinExec(ExecutionEngine *engine, const String *str)
202 {
203     QString s = str->toQString();
204 
205     Scope scope(engine);
206     int offset = (global() || sticky()) ? lastIndex() : 0;
207     if (offset < 0 || offset > s.length()) {
208         setLastIndex(0);
209         RETURN_RESULT(Encode::null());
210     }
211 
212     Q_ALLOCA_VAR(uint, matchOffsets, value()->captureCount() * 2 * sizeof(uint));
213     const uint result = Scoped<RegExp>(scope, value())->match(s, offset, matchOffsets);
214 
215     RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
216     regExpCtor->d()->clearLastMatch();
217 
218     if (result == JSC::Yarr::offsetNoMatch) {
219         if (global() || sticky())
220             setLastIndex(0);
221         RETURN_RESULT(Encode::null());
222     }
223 
224     Q_ASSERT(result <= uint(std::numeric_limits<int>::max()));
225 
226     // fill in result data
227     ScopedArrayObject array(scope, scope.engine->newArrayObject(scope.engine->internalClasses(EngineBase::Class_RegExpExecArray)));
228     int len = value()->captureCount();
229     array->arrayReserve(len);
230     ScopedValue v(scope);
231     int strlen = s.length();
232     for (int i = 0; i < len; ++i) {
233         int start = matchOffsets[i * 2];
234         int end = matchOffsets[i * 2 + 1];
235         if (end > strlen)
236             end = strlen;
237         v = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
238         array->arrayPut(i, v);
239     }
240     array->setArrayLengthUnchecked(len);
241     array->setProperty(Index_ArrayIndex, Value::fromInt32(int(result)));
242     array->setProperty(Index_ArrayInput, *str);
243 
244     RegExpCtor::Data *dd = regExpCtor->d();
245     dd->lastMatch.set(scope.engine, array);
246     dd->lastInput.set(scope.engine, str->d());
247     dd->lastMatchStart = matchOffsets[0];
248     dd->lastMatchEnd = matchOffsets[1];
249 
250     if (global() || sticky())
251         setLastIndex(matchOffsets[1]);
252 
253     return array.asReturnedValue();
254 }
255 
256 DEFINE_OBJECT_VTABLE(RegExpCtor);
257 
init(QV4::ExecutionContext * scope)258 void Heap::RegExpCtor::init(QV4::ExecutionContext *scope)
259 {
260     Heap::FunctionObject::init(scope, QStringLiteral("RegExp"));
261     clearLastMatch();
262 }
263 
clearLastMatch()264 void Heap::RegExpCtor::clearLastMatch()
265 {
266     lastMatch.set(internalClass->engine, Value::nullValue());
267     lastInput.set(internalClass->engine, internalClass->engine->id_empty()->d());
268     lastMatchStart = 0;
269     lastMatchEnd = 0;
270 }
271 
isRegExp(ExecutionEngine * e,const QV4::Value * arg)272 static bool isRegExp(ExecutionEngine *e, const QV4::Value *arg)
273 {
274     const QV4::Object *o = arg->objectValue();
275     if (!o)
276         return false;
277 
278     QV4::Value isRegExp = QV4::Value::fromReturnedValue(o->get(e->symbol_match()));
279     if (!isRegExp.isUndefined())
280         return isRegExp.toBoolean();
281     const RegExpObject *re = o->as<RegExpObject>();
282     return re ? true : false;
283 }
284 
parseFlags(Scope & scope,const QV4::Value * f)285 uint parseFlags(Scope &scope, const QV4::Value *f)
286 {
287     uint flags = CompiledData::RegExp::RegExp_NoFlags;
288     if (!f->isUndefined()) {
289         ScopedString s(scope, f->toString(scope.engine));
290         if (scope.hasException())
291             return flags;
292         QString str = s->toQString();
293         for (int i = 0; i < str.length(); ++i) {
294             if (str.at(i) == QLatin1Char('g') && !(flags & CompiledData::RegExp::RegExp_Global)) {
295                 flags |= CompiledData::RegExp::RegExp_Global;
296             } else if (str.at(i) == QLatin1Char('i') && !(flags & CompiledData::RegExp::RegExp_IgnoreCase)) {
297                 flags |= CompiledData::RegExp::RegExp_IgnoreCase;
298             } else if (str.at(i) == QLatin1Char('m') && !(flags & CompiledData::RegExp::RegExp_Multiline)) {
299                 flags |= CompiledData::RegExp::RegExp_Multiline;
300             } else if (str.at(i) == QLatin1Char('u') && !(flags & CompiledData::RegExp::RegExp_Unicode)) {
301                 flags |= CompiledData::RegExp::RegExp_Unicode;
302             } else if (str.at(i) == QLatin1Char('y') && !(flags & CompiledData::RegExp::RegExp_Sticky)) {
303                 flags |= CompiledData::RegExp::RegExp_Sticky;
304             } else {
305                 scope.engine->throwSyntaxError(QStringLiteral("Invalid flags supplied to RegExp constructor"));
306                 return flags;
307             }
308         }
309     }
310     return flags;
311 }
312 
virtualCallAsConstructor(const FunctionObject * fo,const Value * argv,int argc,const Value * newTarget)313 ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget)
314 {
315     Scope scope(fo);
316 
317     bool patternIsRegExp = argc ? ::isRegExp(scope.engine, argv) : false;
318 
319     if (newTarget == fo) {
320         if (patternIsRegExp && (argc < 2 || argv[1].isUndefined())) {
321             const Object *pattern = static_cast<const Object *>(argv);
322             ScopedValue patternConstructor(scope, pattern->get(scope.engine->id_constructor()));
323             if (patternConstructor->sameValue(*newTarget))
324                 return pattern->asReturnedValue();
325         }
326     }
327 
328     ScopedValue p(scope, argc ? argv[0] : Value::undefinedValue());
329     ScopedValue f(scope, argc > 1 ? argv[1] : Value::undefinedValue());
330     Scoped<RegExpObject> re(scope, p);
331     QString pattern;
332     uint flags = CompiledData::RegExp::RegExp_NoFlags;
333 
334     if (re) {
335         if (f->isUndefined()) {
336             Scoped<RegExp> regexp(scope, re->value());
337             return Encode(scope.engine->newRegExpObject(regexp));
338         }
339         pattern = *re->value()->pattern;
340         flags = parseFlags(scope, f);
341     } else if (patternIsRegExp) {
342         const Object *po = static_cast<const Object *>(argv);
343         p = po->get(scope.engine->id_source());
344         if (!p->isUndefined())
345             pattern = p->toQString();
346         if (scope.hasException())
347             return Encode::undefined();
348         if (f->isUndefined())
349             f = po->get(scope.engine->id_flags());
350         flags = parseFlags(scope, f);
351     } else {
352         if (!p->isUndefined())
353             pattern = p->toQString();
354         if (scope.hasException())
355             return Encode::undefined();
356         flags = parseFlags(scope, f);
357     }
358     if (scope.hasException())
359         return Encode::undefined();
360 
361     Scoped<RegExp> regexp(scope, RegExp::create(scope.engine, pattern, flags));
362     if (!regexp->isValid()) {
363         return scope.engine->throwSyntaxError(QStringLiteral("Invalid regular expression"));
364     }
365 
366     ReturnedValue o = Encode(scope.engine->newRegExpObject(regexp));
367 
368     if (!newTarget)
369         return o;
370     ScopedObject obj(scope, o);
371     obj->setProtoFromNewTarget(newTarget);
372     return obj->asReturnedValue();
373 }
374 
virtualCall(const FunctionObject * f,const Value *,const Value * argv,int argc)375 ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc)
376 {
377     return virtualCallAsConstructor(f, argv, argc, f);
378 }
379 
init(ExecutionEngine * engine,Object * constructor)380 void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor)
381 {
382     Scope scope(engine);
383     ScopedObject o(scope);
384     ScopedObject ctor(scope, constructor);
385 
386     ctor->defineReadonlyProperty(engine->id_prototype(), (o = this));
387     ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(2));
388     ctor->addSymbolSpecies();
389 
390     // Properties deprecated in the spec but required by "the web" :(
391     ctor->defineAccessorProperty(QStringLiteral("lastMatch"), method_get_lastMatch_n<0>, nullptr);
392     ctor->defineAccessorProperty(QStringLiteral("$&"), method_get_lastMatch_n<0>, nullptr);
393     ctor->defineAccessorProperty(QStringLiteral("$1"), method_get_lastMatch_n<1>, nullptr);
394     ctor->defineAccessorProperty(QStringLiteral("$2"), method_get_lastMatch_n<2>, nullptr);
395     ctor->defineAccessorProperty(QStringLiteral("$3"), method_get_lastMatch_n<3>, nullptr);
396     ctor->defineAccessorProperty(QStringLiteral("$4"), method_get_lastMatch_n<4>, nullptr);
397     ctor->defineAccessorProperty(QStringLiteral("$5"), method_get_lastMatch_n<5>, nullptr);
398     ctor->defineAccessorProperty(QStringLiteral("$6"), method_get_lastMatch_n<6>, nullptr);
399     ctor->defineAccessorProperty(QStringLiteral("$7"), method_get_lastMatch_n<7>, nullptr);
400     ctor->defineAccessorProperty(QStringLiteral("$8"), method_get_lastMatch_n<8>, nullptr);
401     ctor->defineAccessorProperty(QStringLiteral("$9"), method_get_lastMatch_n<9>, nullptr);
402     ctor->defineAccessorProperty(QStringLiteral("lastParen"), method_get_lastParen, nullptr);
403     ctor->defineAccessorProperty(QStringLiteral("$+"), method_get_lastParen, nullptr);
404     ctor->defineAccessorProperty(QStringLiteral("input"), method_get_input, nullptr);
405     ctor->defineAccessorProperty(QStringLiteral("$_"), method_get_input, nullptr);
406     ctor->defineAccessorProperty(QStringLiteral("leftContext"), method_get_leftContext, nullptr);
407     ctor->defineAccessorProperty(QStringLiteral("$`"), method_get_leftContext, nullptr);
408     ctor->defineAccessorProperty(QStringLiteral("rightContext"), method_get_rightContext, nullptr);
409     ctor->defineAccessorProperty(QStringLiteral("$'"), method_get_rightContext, nullptr);
410 
411     defineDefaultProperty(QStringLiteral("constructor"), (o = ctor));
412     defineAccessorProperty(scope.engine->id_flags(), method_get_flags, nullptr);
413     defineAccessorProperty(scope.engine->id_global(), method_get_global, nullptr);
414     defineAccessorProperty(scope.engine->id_ignoreCase(), method_get_ignoreCase, nullptr);
415     defineDefaultProperty(QStringLiteral("exec"), method_exec, 1);
416     defineDefaultProperty(engine->symbol_match(), method_match, 1);
417     defineAccessorProperty(scope.engine->id_multiline(), method_get_multiline, nullptr);
418     defineDefaultProperty(engine->symbol_replace(), method_replace, 2);
419     defineDefaultProperty(engine->symbol_search(), method_search, 1);
420     defineAccessorProperty(scope.engine->id_source(), method_get_source, nullptr);
421     defineDefaultProperty(engine->symbol_split(), method_split, 2);
422     defineAccessorProperty(scope.engine->id_sticky(), method_get_sticky, nullptr);
423     defineDefaultProperty(QStringLiteral("test"), method_test, 1);
424     defineDefaultProperty(engine->id_toString(), method_toString, 0);
425     defineAccessorProperty(scope.engine->id_unicode(), method_get_unicode, nullptr);
426 
427     // another web extension
428     defineDefaultProperty(QStringLiteral("compile"), method_compile, 2);
429 }
430 
431 /* used by String.match */
execFirstMatch(const FunctionObject * b,const Value * thisObject,const Value * argv,int argc)432 ReturnedValue RegExpPrototype::execFirstMatch(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
433 {
434     Scope scope(b);
435     Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
436     Q_ASSERT(r && r->global());
437 
438     ScopedString str(scope, argc ? argv[0] : Value::undefinedValue());
439     Q_ASSERT(str);
440     QString s = str->toQString();
441 
442     int offset = r->lastIndex();
443     if (offset < 0 || offset > s.length()) {
444         r->setLastIndex(0);
445         RETURN_RESULT(Encode::null());
446     }
447 
448     Q_ALLOCA_VAR(uint, matchOffsets, r->value()->captureCount() * 2 * sizeof(uint));
449     const int result = Scoped<RegExp>(scope, r->value())->match(s, offset, matchOffsets);
450 
451     RegExpCtor *regExpCtor = static_cast<RegExpCtor *>(scope.engine->regExpCtor());
452     regExpCtor->d()->clearLastMatch();
453 
454     if (result == -1) {
455         r->setLastIndex(0);
456         RETURN_RESULT(Encode::null());
457     }
458 
459     ReturnedValue retVal = Encode::undefined();
460     // return first match
461     if (r->value()->captureCount()) {
462         int start = matchOffsets[0];
463         int end = matchOffsets[1];
464         retVal = (start != -1) ? scope.engine->memoryManager->alloc<ComplexString>(str->d(), start, end - start)->asReturnedValue() : Encode::undefined();
465     }
466 
467     RegExpCtor::Data *dd = regExpCtor->d();
468     dd->lastInput.set(scope.engine, str->d());
469     dd->lastMatchStart = matchOffsets[0];
470     dd->lastMatchEnd = matchOffsets[1];
471 
472     r->setLastIndex(matchOffsets[1]);
473 
474     return retVal;
475 }
476 
exec(ExecutionEngine * engine,const Object * o,const String * s)477 ReturnedValue RegExpPrototype::exec(ExecutionEngine *engine, const Object *o, const String *s)
478 {
479     Scope scope(engine);
480     ScopedString key(scope, scope.engine->newString(QStringLiteral("exec")));
481     ScopedFunctionObject exec(scope, o->get(key));
482     if (exec) {
483         ScopedValue result(scope, exec->call(o, s, 1));
484         if (scope.hasException())
485             RETURN_UNDEFINED();
486         if (!result->isNull() && !result->isObject())
487             return scope.engine->throwTypeError();
488         return result->asReturnedValue();
489     }
490     Scoped<RegExpObject> re(scope, o);
491     if (!re)
492         return scope.engine->throwTypeError();
493     return re->builtinExec(engine, s);
494 }
495 
method_exec(const FunctionObject * b,const Value * thisObject,const Value * argv,int argc)496 ReturnedValue RegExpPrototype::method_exec(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
497 {
498     Scope scope(b);
499     Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
500     if (!r)
501         return scope.engine->throwTypeError();
502 
503     ScopedValue arg(scope, argc ? argv[0]: Value::undefinedValue());
504     ScopedString str(scope, arg->toString(scope.engine));
505     if (scope.hasException())
506         RETURN_UNDEFINED();
507 
508     return  r->builtinExec(scope.engine, str);
509 }
510 
method_get_flags(const FunctionObject * f,const Value * thisObject,const Value *,int)511 ReturnedValue RegExpPrototype::method_get_flags(const FunctionObject *f, const Value *thisObject, const Value *, int)
512 {
513     Scope scope(f);
514     ScopedObject o(scope, thisObject);
515     if (!o)
516         return scope.engine->throwTypeError();
517 
518     QString result;
519     ScopedValue v(scope);
520     v = o->get(scope.engine->id_global());
521     if (scope.hasException())
522         return Encode::undefined();
523     if (v->toBoolean())
524         result += QLatin1Char('g');
525     v = o->get(scope.engine->id_ignoreCase());
526     if (scope.hasException())
527         return Encode::undefined();
528     if (v->toBoolean())
529         result += QLatin1Char('i');
530     v = o->get(scope.engine->id_multiline());
531     if (scope.hasException())
532         return Encode::undefined();
533     if (v->toBoolean())
534         result += QLatin1Char('m');
535     v = o->get(scope.engine->id_unicode());
536     if (scope.hasException())
537         return Encode::undefined();
538     if (v->toBoolean())
539         result += QLatin1Char('u');
540     v = o->get(scope.engine->id_sticky());
541     if (scope.hasException())
542         return Encode::undefined();
543     if (v->toBoolean())
544         result += QLatin1Char('y');
545     return scope.engine->newString(result)->asReturnedValue();
546 }
547 
method_get_global(const FunctionObject * f,const Value * thisObject,const Value *,int)548 ReturnedValue RegExpPrototype::method_get_global(const FunctionObject *f, const Value *thisObject, const Value *, int)
549 {
550     Scope scope(f);
551     Scoped<RegExpObject> re(scope, thisObject);
552     if (!re) {
553         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
554             return Encode::undefined();
555         return scope.engine->throwTypeError();
556     }
557 
558     bool b = re->value()->flags & CompiledData::RegExp::RegExp_Global;
559     return Encode(b);
560 }
561 
method_get_ignoreCase(const FunctionObject * f,const Value * thisObject,const Value *,int)562 ReturnedValue RegExpPrototype::method_get_ignoreCase(const FunctionObject *f, const Value *thisObject, const Value *, int)
563 {
564     Scope scope(f);
565     Scoped<RegExpObject> re(scope, thisObject);
566     if (!re) {
567         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
568             return Encode::undefined();
569         return scope.engine->throwTypeError();
570     }
571 
572     bool b = re->value()->flags & CompiledData::RegExp::RegExp_IgnoreCase;
573     return Encode(b);
574 }
575 
advanceStringIndex(int index,const QString & str,bool unicode)576 static int advanceStringIndex(int index, const QString &str, bool unicode)
577 {
578     if (unicode) {
579         if (index < str.length() - 1 &&
580             str.at(index).isHighSurrogate() &&
581             str.at(index + 1).isLowSurrogate())
582             ++index;
583     }
584     ++index;
585     return index;
586 }
587 
advanceLastIndexOnEmptyMatch(ExecutionEngine * e,bool unicode,QV4::Object * rx,const String * matchString,const QString & str)588 static void advanceLastIndexOnEmptyMatch(ExecutionEngine *e, bool unicode, QV4::Object *rx, const String *matchString, const QString &str)
589 {
590     Scope scope(e);
591     if (matchString->d()->length() == 0) {
592         QV4::ScopedValue v(scope, rx->get(scope.engine->id_lastIndex()));
593         int lastIndex = advanceStringIndex(v->toLength(), str, unicode);
594         if (!rx->put(scope.engine->id_lastIndex(), QV4::Value::fromInt32(lastIndex)))
595             scope.engine->throwTypeError();
596     }
597 }
598 
method_match(const FunctionObject * f,const Value * thisObject,const Value * argv,int argc)599 ReturnedValue RegExpPrototype::method_match(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
600 {
601     Scope scope(f);
602     ScopedObject rx(scope, thisObject);
603     if (!rx)
604         return scope.engine->throwTypeError();
605     ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
606     if (scope.hasException())
607         return Encode::undefined();
608     bool global = ScopedValue(scope, rx->get(scope.engine->id_global()))->toBoolean();
609 
610     if (!global)
611         return exec(scope.engine, rx, s);
612 
613     bool unicode = ScopedValue(scope, rx->get(scope.engine->id_unicode()))->toBoolean();
614 
615     rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0));
616     ScopedArrayObject a(scope, scope.engine->newArrayObject());
617     uint n = 0;
618 
619     ScopedValue result(scope);
620     ScopedValue match(scope);
621     ScopedString matchString(scope);
622     ScopedValue v(scope);
623     while (1) {
624         result = exec(scope.engine, rx, s);
625         if (scope.hasException())
626             return Encode::undefined();
627         if (result->isNull()) {
628             if (!n)
629                 return Encode::null();
630             return a->asReturnedValue();
631         }
632         Q_ASSERT(result->isObject());
633         match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
634         matchString = match->toString(scope.engine);
635         if (scope.hasException())
636             return Encode::undefined();
637         a->push_back(matchString);
638         advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
639         ++n;
640     }
641 }
642 
method_get_multiline(const FunctionObject * f,const Value * thisObject,const Value *,int)643 ReturnedValue RegExpPrototype::method_get_multiline(const FunctionObject *f, const Value *thisObject, const Value *, int)
644 {
645     Scope scope(f);
646     Scoped<RegExpObject> re(scope, thisObject);
647     if (!re) {
648         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
649             return Encode::undefined();
650         return scope.engine->throwTypeError();
651     }
652 
653     bool b = re->value()->flags & CompiledData::RegExp::RegExp_Multiline;
654     return Encode(b);
655 }
656 
method_replace(const FunctionObject * f,const Value * thisObject,const Value * argv,int argc)657 ReturnedValue RegExpPrototype::method_replace(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
658 {
659     Scope scope(f);
660     ScopedObject rx(scope, thisObject);
661     if (!rx)
662         return scope.engine->throwTypeError();
663 
664     ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
665     if (scope.hasException())
666         return Encode::undefined();
667 
668     int lengthS = s->toQString().length();
669 
670     ScopedString replaceValue(scope);
671     ScopedFunctionObject replaceFunction(scope, (argc > 1 ? argv[1] : Value::undefinedValue()));
672     bool functionalReplace = !!replaceFunction;
673     if (!functionalReplace)
674         replaceValue = (argc > 1 ? argv[1] : Value::undefinedValue()).toString(scope.engine);
675 
676     ScopedValue v(scope);
677     bool global = (v = rx->get(scope.engine->id_global()))->toBoolean();
678     bool unicode = false;
679     if (global) {
680         unicode = (v = rx->get(scope.engine->id_unicode()))->toBoolean();
681         if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
682             return scope.engine->throwTypeError();
683     }
684 
685     ScopedArrayObject results(scope, scope.engine->newArrayObject());
686     ScopedValue result(scope);
687     ScopedValue match(scope);
688     ScopedString matchString(scope);
689     while (1) {
690         result = exec(scope.engine, rx, s);
691         if (scope.hasException())
692             return Encode::undefined();
693         if (result->isNull())
694             break;
695         results->push_back(result);
696         if (!global)
697             break;
698         match = static_cast<Object &>(*result).get(PropertyKey::fromArrayIndex(0));
699         matchString = match->toString(scope.engine);
700         if (scope.hasException())
701             return Encode::undefined();
702         advanceLastIndexOnEmptyMatch(scope.engine, unicode, rx, matchString, s->toQString());
703     }
704     QString accumulatedResult;
705     int nextSourcePosition = 0;
706     int resultsLength = results->getLength();
707     ScopedObject resultObject(scope);
708     for (int i = 0; i < resultsLength; ++i) {
709         resultObject = results->get(PropertyKey::fromArrayIndex(i));
710         if (scope.hasException())
711             return Encode::undefined();
712 
713         int nCaptures = resultObject->getLength();
714         nCaptures = qMax(nCaptures - 1, 0);
715         match = resultObject->get(PropertyKey::fromArrayIndex(0));
716         matchString = match->toString(scope.engine);
717         if (scope.hasException())
718             return Encode::undefined();
719         QString m = matchString->toQString();
720         int matchLength = m.length();
721         v = resultObject->get(scope.engine->id_index());
722         int position = v->toInt32();
723         position = qMax(qMin(position, lengthS), 0);
724         if (scope.hasException())
725             return Encode::undefined();
726 
727         int n = 1;
728         Scope innerScope(scope.engine);
729         JSCallData cData(scope, nCaptures + 3);
730         while (n <= nCaptures) {
731             v = resultObject->get(PropertyKey::fromArrayIndex(n));
732             if (!v->isUndefined())
733                 cData->args[n] = v->toString(scope.engine);
734             ++n;
735         }
736         QString replacement;
737         if (functionalReplace) {
738             cData->args[0] = matchString;
739             cData->args[nCaptures + 1] = Encode(position);
740             cData->args[nCaptures + 2] = s;
741             ScopedValue replValue(scope, replaceFunction->call(cData));
742             if (scope.hasException())
743                 return Encode::undefined();
744             replacement = replValue->toQString();
745         } else {
746             replacement = RegExp::getSubstitution(matchString->toQString(), s->toQString(), position, cData.args, nCaptures, replaceValue->toQString());
747         }
748         if (scope.hasException())
749             return Encode::undefined();
750         if (position >= nextSourcePosition) {
751             accumulatedResult += s->toQString().midRef(nextSourcePosition, position - nextSourcePosition) + replacement;
752             nextSourcePosition = position + matchLength;
753         }
754     }
755     if (nextSourcePosition < lengthS) {
756         accumulatedResult += s->toQString().midRef(nextSourcePosition);
757     }
758     return scope.engine->newString(accumulatedResult)->asReturnedValue();
759 }
760 
method_search(const FunctionObject * f,const Value * thisObject,const Value * argv,int argc)761 ReturnedValue RegExpPrototype::method_search(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
762 {
763     Scope scope(f);
764     ScopedObject rx(scope, thisObject);
765     if (!rx)
766         return scope.engine->throwTypeError();
767 
768     ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
769     if (scope.hasException())
770         return Encode::undefined();
771 
772     ScopedValue previousLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
773     if (previousLastIndex->toNumber() != 0) {
774         if (!rx->put(scope.engine->id_lastIndex(), Value::fromInt32(0)))
775             return scope.engine->throwTypeError();
776     }
777 
778     ScopedValue result(scope, exec(scope.engine, rx, s));
779     if (scope.hasException())
780         return Encode::undefined();
781 
782     ScopedValue currentLastIndex(scope, rx->get(scope.engine->id_lastIndex()));
783     if (!currentLastIndex->sameValue(previousLastIndex)) {
784         if (!rx->put(scope.engine->id_lastIndex(), previousLastIndex))
785             return scope.engine->throwTypeError();
786     }
787 
788     if (result->isNull())
789         return Encode(-1);
790     ScopedObject o(scope, result);
791     Q_ASSERT(o);
792     return o->get(scope.engine->id_index());
793 }
794 
795 
method_get_source(const FunctionObject * f,const Value * thisObject,const Value *,int)796 ReturnedValue RegExpPrototype::method_get_source(const FunctionObject *f, const Value *thisObject, const Value *, int)
797 {
798     Scope scope(f);
799     Scoped<RegExpObject> re(scope, thisObject);
800     if (!re) {
801         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
802             return scope.engine->newString(QStringLiteral("(?:)"))->asReturnedValue();
803         return scope.engine->throwTypeError();
804     }
805 
806     return scope.engine->newString(re->toString())->asReturnedValue();
807 }
808 
method_split(const FunctionObject * f,const Value * thisObject,const Value * argv,int argc)809 ReturnedValue RegExpPrototype::method_split(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
810 {
811     Scope scope(f);
812     ScopedObject rx(scope, thisObject);
813     if (!rx)
814         return scope.engine->throwTypeError();
815 
816     ScopedString s(scope, (argc ? argv[0] : Value::undefinedValue()).toString(scope.engine));
817     if (scope.hasException())
818         return Encode::undefined();
819 
820     ScopedValue flagsValue(scope, rx->get(scope.engine->id_flags()));
821     ScopedString flags(scope, flagsValue->toString(scope.engine));
822     if (scope.hasException())
823         return Encode::undefined();
824     QString flagsString = flags->toQString();
825     if (!flagsString.contains(QLatin1Char('y')))
826         flags = scope.engine->newString(flagsString + QLatin1Char('y'));
827     bool unicodeMatching = flagsString.contains(QLatin1Char('u'));
828 
829     const FunctionObject *C = rx->speciesConstructor(scope, scope.engine->regExpCtor());
830     if (!C)
831         return Encode::undefined();
832 
833     Value *args = scope.alloc(2);
834     args[0] = rx;
835     args[1] = flags;
836     ScopedObject splitter(scope, C->callAsConstructor(args, 2, f));
837     if (scope.hasException())
838         return Encode::undefined();
839 
840     ScopedArrayObject A(scope, scope.engine->newArrayObject());
841     uint lengthA = 0;
842     uint limit = argc < 2 ? UINT_MAX : argv[1].toUInt32();
843     if (limit == 0)
844         return A->asReturnedValue();
845 
846     QString S = s->toQString();
847     int size = S.length();
848     if (size == 0) {
849         ScopedValue z(scope, exec(scope.engine, splitter, s));
850         if (z->isNull())
851             A->push_back(s);
852         return A->asReturnedValue();
853     }
854 
855     int p = 0;
856     int q = 0;
857     ScopedValue v(scope);
858     ScopedValue z(scope);
859     ScopedObject zz(scope);
860     ScopedString t(scope);
861     while (q < size) {
862         Value qq = Value::fromInt32(q);
863         if (!splitter->put(scope.engine->id_lastIndex(), qq))
864             return scope.engine->throwTypeError();
865         z = exec(scope.engine, splitter, s);
866         if (scope.hasException())
867             return Encode::undefined();
868 
869         if (z->isNull()) {
870             q = advanceStringIndex(q, S, unicodeMatching);
871             continue;
872         }
873 
874         v = splitter->get(scope.engine->id_lastIndex());
875         int e = qMin(v->toInt32(), size);
876         if (e == p) {
877             q = advanceStringIndex(q, S, unicodeMatching);
878             continue;
879         }
880         QString T = S.mid(p, q - p);
881         t = scope.engine->newString(T);
882         A->push_back(t);
883         ++lengthA;
884         if (lengthA == limit)
885             return A->asReturnedValue();
886         p = e;
887         zz = *z;
888         uint numberOfCaptures = qMax(zz->getLength() - 1, 0ll);
889         for (uint i = 1; i <= numberOfCaptures; ++i) {
890             v = zz->get(PropertyKey::fromArrayIndex(i));
891             A->push_back(v);
892             ++lengthA;
893             if (lengthA == limit)
894                 return A->asReturnedValue();
895         }
896         q = p;
897     }
898 
899     QString T = S.mid(p);
900     t = scope.engine->newString(T);
901     A->push_back(t);
902     return A->asReturnedValue();
903 }
904 
method_get_sticky(const FunctionObject * f,const Value * thisObject,const Value *,int)905 ReturnedValue RegExpPrototype::method_get_sticky(const FunctionObject *f, const Value *thisObject, const Value *, int)
906 {
907     Scope scope(f);
908     Scoped<RegExpObject> re(scope, thisObject);
909     if (!re) {
910         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
911             return Encode::undefined();
912         return scope.engine->throwTypeError();
913     }
914 
915     bool b = re->value()->flags & CompiledData::RegExp::RegExp_Sticky;
916     return Encode(b);
917 }
918 
method_test(const FunctionObject * b,const Value * thisObject,const Value * argv,int argc)919 ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
920 {
921     Value res = Value::fromReturnedValue(method_exec(b, thisObject, argv, argc));
922     return Encode(!res.isNull());
923 }
924 
method_toString(const FunctionObject * b,const Value * thisObject,const Value *,int)925 ReturnedValue RegExpPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
926 {
927     Scope scope(b);
928     const Object *r = thisObject->as<Object>();
929     if (!r)
930         return scope.engine->throwTypeError();
931 
932     ScopedValue v(scope);
933     v = r->get(scope.engine->id_source());
934     ScopedString source(scope, v->toString(scope.engine));
935     if (scope.hasException())
936         return Encode::undefined();
937     v = r->get(scope.engine->id_flags());
938     ScopedString flags(scope, v->toString(scope.engine));
939     if (scope.hasException())
940         return Encode::undefined();
941 
942     QString result = QLatin1Char('/') + source->toQString() + QLatin1Char('/') + flags->toQString();
943     return Encode(scope.engine->newString(result));
944 }
945 
method_get_unicode(const FunctionObject * f,const Value * thisObject,const Value *,int)946 ReturnedValue RegExpPrototype::method_get_unicode(const FunctionObject *f, const Value *thisObject, const Value *, int)
947 {
948     Scope scope(f);
949     Scoped<RegExpObject> re(scope, thisObject);
950     if (!re) {
951         if (thisObject->sameValue(*scope.engine->regExpPrototype()))
952             return Encode::undefined();
953         return scope.engine->throwTypeError();
954     }
955 
956     bool b = re->value()->flags & CompiledData::RegExp::RegExp_Unicode;
957     return Encode(b);
958 }
959 
method_compile(const FunctionObject * b,const Value * thisObject,const Value * argv,int argc)960 ReturnedValue RegExpPrototype::method_compile(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
961 {
962     Scope scope(b);
963     Scoped<RegExpObject> r(scope, thisObject->as<RegExpObject>());
964     if (!r)
965         return scope.engine->throwTypeError();
966 
967     Scoped<RegExpObject> re(scope, scope.engine->regExpCtor()->callAsConstructor(argv, argc));
968     if (re) // Otherwise the regexp constructor should have thrown an exception
969         r->d()->value.set(scope.engine, re->value());
970     return Encode::undefined();
971 }
972 
973 template <uint index>
method_get_lastMatch_n(const FunctionObject * b,const Value *,const Value *,int)974 ReturnedValue RegExpPrototype::method_get_lastMatch_n(const FunctionObject *b, const Value *, const Value *, int)
975 {
976     Scope scope(b);
977     ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
978     ScopedValue res(scope, lastMatch ? lastMatch->get(index) : Encode::undefined());
979     if (res->isUndefined())
980         res = scope.engine->newString();
981     return res->asReturnedValue();
982 }
983 
method_get_lastParen(const FunctionObject * b,const Value *,const Value *,int)984 ReturnedValue RegExpPrototype::method_get_lastParen(const FunctionObject *b, const Value *, const Value *, int)
985 {
986     Scope scope(b);
987     ScopedArrayObject lastMatch(scope, static_cast<RegExpCtor*>(scope.engine->regExpCtor())->lastMatch());
988     ScopedValue res(scope, lastMatch ? lastMatch->get(lastMatch->getLength() - 1) : Encode::undefined());
989     if (res->isUndefined())
990         res = scope.engine->newString();
991     return res->asReturnedValue();
992 }
993 
method_get_input(const FunctionObject * b,const Value *,const Value *,int)994 ReturnedValue RegExpPrototype::method_get_input(const FunctionObject *b, const Value *, const Value *, int)
995 {
996     return static_cast<RegExpCtor*>(b->engine()->regExpCtor())->lastInput()->asReturnedValue();
997 }
998 
method_get_leftContext(const FunctionObject * b,const Value *,const Value *,int)999 ReturnedValue  RegExpPrototype::method_get_leftContext(const FunctionObject *b, const Value *, const Value *, int)
1000 {
1001     Scope scope(b);
1002     Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
1003     QString lastInput = regExpCtor->lastInput()->toQString();
1004     return Encode(scope.engine->newString(lastInput.left(regExpCtor->lastMatchStart())));
1005 }
1006 
method_get_rightContext(const FunctionObject * b,const Value *,const Value *,int)1007 ReturnedValue  RegExpPrototype::method_get_rightContext(const FunctionObject *b, const Value *, const Value *, int)
1008 {
1009     Scope scope(b);
1010     Scoped<RegExpCtor> regExpCtor(scope, scope.engine->regExpCtor());
1011     QString lastInput = regExpCtor->lastInput()->toQString();
1012     return Encode(scope.engine->newString(lastInput.mid(regExpCtor->lastMatchEnd())));
1013 }
1014 
1015 QT_END_NAMESPACE
1016