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
61QT_BEGIN_NAMESPACE
62
63Q_CORE_EXPORT QString qt_regexp_toCanonical(const QString &, QRegExp::PatternSyntax);
64
65using namespace QV4;
66
67DEFINE_OBJECT_VTABLE(RegExpObject);
68
69void Heap::RegExpObject::init()
70{
71 Object::init();
72 Scope scope(internalClass->engine);
73 Scoped<QV4::RegExpObject> o(scope, this);
74 value.set(e: scope.engine, newVal: QV4::RegExp::create(engine: scope.engine, pattern: QString(), flags: CompiledData::RegExp::RegExp_NoFlags));
75 o->initProperties();
76}
77
78void Heap::RegExpObject::init(QV4::RegExp *value)
79{
80 Object::init();
81 Scope scope(internalClass->engine);
82 this->value.set(e: scope.engine, newVal: 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.
90void 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(asize: 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(e: scope.engine, newVal: QV4::RegExp::create(engine: 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.
144void 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(e: scope.engine, newVal: QV4::RegExp::create(engine: scope.engine, pattern: re.pattern(), flags));
155 o->initProperties();
156}
157#endif
158
159void RegExpObject::initProperties()
160{
161 setProperty(index: Index_LastIndex, v: Value::fromInt32(i: 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.
169QRegExp 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.
179QRegularExpression 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
189QString 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(c: '/', after: QLatin1String("\\/"));
197 }
198 return p;
199}
200
201ReturnedValue 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(string: s, start: 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(ic: scope.engine->internalClasses(icType: EngineBase::Class_RegExpExecArray)));
228 int len = value()->captureCount();
229 array->arrayReserve(n: 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>(args: str->d(), args: start, args: end - start)->asReturnedValue() : Encode::undefined();
238 array->arrayPut(index: i, value: v);
239 }
240 array->setArrayLengthUnchecked(len);
241 array->setProperty(index: Index_ArrayIndex, v: Value::fromInt32(i: int(result)));
242 array->setProperty(index: Index_ArrayInput, v: *str);
243
244 RegExpCtor::Data *dd = regExpCtor->d();
245 dd->lastMatch.set(e: scope.engine, newVal: array);
246 dd->lastInput.set(e: scope.engine, newVal: 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
256DEFINE_OBJECT_VTABLE(RegExpCtor);
257
258void Heap::RegExpCtor::init(QV4::ExecutionContext *scope)
259{
260 Heap::FunctionObject::init(scope, QStringLiteral("RegExp"));
261 clearLastMatch();
262}
263
264void Heap::RegExpCtor::clearLastMatch()
265{
266 lastMatch.set(e: internalClass->engine, newVal: Value::nullValue());
267 lastInput.set(e: internalClass->engine, newVal: internalClass->engine->id_empty()->d());
268 lastMatchStart = 0;
269 lastMatchEnd = 0;
270}
271
272static 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(val: o->get(name: 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
285uint 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(e: 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
313ReturnedValue RegExpCtor::virtualCallAsConstructor(const FunctionObject *fo, const Value *argv, int argc, const Value *newTarget)
314{
315 Scope scope(fo);
316
317 bool patternIsRegExp = argc ? ::isRegExp(e: scope.engine, arg: 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(name: scope.engine->id_constructor()));
323 if (patternConstructor->sameValue(other: *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(re: 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(name: 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(name: 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(engine: 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(re: regexp));
367
368 if (!newTarget)
369 return o;
370 ScopedObject obj(scope, o);
371 obj->setProtoFromNewTarget(newTarget);
372 return obj->asReturnedValue();
373}
374
375ReturnedValue RegExpCtor::virtualCall(const FunctionObject *f, const Value *, const Value *argv, int argc)
376{
377 return virtualCallAsConstructor(fo: f, argv, argc, newTarget: f);
378}
379
380void RegExpPrototype::init(ExecutionEngine *engine, Object *constructor)
381{
382 Scope scope(engine);
383 ScopedObject o(scope);
384 ScopedObject ctor(scope, constructor);
385
386 ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this));
387 ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 2));
388 ctor->addSymbolSpecies();
389
390 // Properties deprecated in the spec but required by "the web" :(
391 ctor->defineAccessorProperty(QStringLiteral("lastMatch"), getter: method_get_lastMatch_n<0>, setter: nullptr);
392 ctor->defineAccessorProperty(QStringLiteral("$&"), getter: method_get_lastMatch_n<0>, setter: nullptr);
393 ctor->defineAccessorProperty(QStringLiteral("$1"), getter: method_get_lastMatch_n<1>, setter: nullptr);
394 ctor->defineAccessorProperty(QStringLiteral("$2"), getter: method_get_lastMatch_n<2>, setter: nullptr);
395 ctor->defineAccessorProperty(QStringLiteral("$3"), getter: method_get_lastMatch_n<3>, setter: nullptr);
396 ctor->defineAccessorProperty(QStringLiteral("$4"), getter: method_get_lastMatch_n<4>, setter: nullptr);
397 ctor->defineAccessorProperty(QStringLiteral("$5"), getter: method_get_lastMatch_n<5>, setter: nullptr);
398 ctor->defineAccessorProperty(QStringLiteral("$6"), getter: method_get_lastMatch_n<6>, setter: nullptr);
399 ctor->defineAccessorProperty(QStringLiteral("$7"), getter: method_get_lastMatch_n<7>, setter: nullptr);
400 ctor->defineAccessorProperty(QStringLiteral("$8"), getter: method_get_lastMatch_n<8>, setter: nullptr);
401 ctor->defineAccessorProperty(QStringLiteral("$9"), getter: method_get_lastMatch_n<9>, setter: nullptr);
402 ctor->defineAccessorProperty(QStringLiteral("lastParen"), getter: method_get_lastParen, setter: nullptr);
403 ctor->defineAccessorProperty(QStringLiteral("$+"), getter: method_get_lastParen, setter: nullptr);
404 ctor->defineAccessorProperty(QStringLiteral("input"), getter: method_get_input, setter: nullptr);
405 ctor->defineAccessorProperty(QStringLiteral("$_"), getter: method_get_input, setter: nullptr);
406 ctor->defineAccessorProperty(QStringLiteral("leftContext"), getter: method_get_leftContext, setter: nullptr);
407 ctor->defineAccessorProperty(QStringLiteral("$`"), getter: method_get_leftContext, setter: nullptr);
408 ctor->defineAccessorProperty(QStringLiteral("rightContext"), getter: method_get_rightContext, setter: nullptr);
409 ctor->defineAccessorProperty(QStringLiteral("$'"), getter: method_get_rightContext, setter: nullptr);
410
411 defineDefaultProperty(QStringLiteral("constructor"), value: (o = ctor));
412 defineAccessorProperty(name: scope.engine->id_flags(), getter: method_get_flags, setter: nullptr);
413 defineAccessorProperty(name: scope.engine->id_global(), getter: method_get_global, setter: nullptr);
414 defineAccessorProperty(name: scope.engine->id_ignoreCase(), getter: method_get_ignoreCase, setter: nullptr);
415 defineDefaultProperty(QStringLiteral("exec"), code: method_exec, argumentCount: 1);
416 defineDefaultProperty(name: engine->symbol_match(), code: method_match, argumentCount: 1);
417 defineAccessorProperty(name: scope.engine->id_multiline(), getter: method_get_multiline, setter: nullptr);
418 defineDefaultProperty(name: engine->symbol_replace(), code: method_replace, argumentCount: 2);
419 defineDefaultProperty(name: engine->symbol_search(), code: method_search, argumentCount: 1);
420 defineAccessorProperty(name: scope.engine->id_source(), getter: method_get_source, setter: nullptr);
421 defineDefaultProperty(name: engine->symbol_split(), code: method_split, argumentCount: 2);
422 defineAccessorProperty(name: scope.engine->id_sticky(), getter: method_get_sticky, setter: nullptr);
423 defineDefaultProperty(QStringLiteral("test"), code: method_test, argumentCount: 1);
424 defineDefaultProperty(name: engine->id_toString(), code: method_toString, argumentCount: 0);
425 defineAccessorProperty(name: scope.engine->id_unicode(), getter: method_get_unicode, setter: nullptr);
426
427 // another web extension
428 defineDefaultProperty(QStringLiteral("compile"), code: method_compile, argumentCount: 2);
429}
430
431/* used by String.match */
432ReturnedValue 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(string: s, start: 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>(args: str->d(), args: start, args: end - start)->asReturnedValue() : Encode::undefined();
465 }
466
467 RegExpCtor::Data *dd = regExpCtor->d();
468 dd->lastInput.set(e: scope.engine, newVal: str->d());
469 dd->lastMatchStart = matchOffsets[0];
470 dd->lastMatchEnd = matchOffsets[1];
471
472 r->setLastIndex(matchOffsets[1]);
473
474 return retVal;
475}
476
477ReturnedValue 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(name: key));
482 if (exec) {
483 ScopedValue result(scope, exec->call(thisObject: o, argv: s, argc: 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, str: s);
494}
495
496ReturnedValue 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(e: scope.engine));
505 if (scope.hasException())
506 RETURN_UNDEFINED();
507
508 return r->builtinExec(engine: scope.engine, str);
509}
510
511ReturnedValue 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(name: scope.engine->id_global());
521 if (scope.hasException())
522 return Encode::undefined();
523 if (v->toBoolean())
524 result += QLatin1Char('g');
525 v = o->get(name: scope.engine->id_ignoreCase());
526 if (scope.hasException())
527 return Encode::undefined();
528 if (v->toBoolean())
529 result += QLatin1Char('i');
530 v = o->get(name: scope.engine->id_multiline());
531 if (scope.hasException())
532 return Encode::undefined();
533 if (v->toBoolean())
534 result += QLatin1Char('m');
535 v = o->get(name: scope.engine->id_unicode());
536 if (scope.hasException())
537 return Encode::undefined();
538 if (v->toBoolean())
539 result += QLatin1Char('u');
540 v = o->get(name: 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(s: result)->asReturnedValue();
546}
547
548ReturnedValue 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(other: *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
562ReturnedValue 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(other: *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
576static int advanceStringIndex(int index, const QString &str, bool unicode)
577{
578 if (unicode) {
579 if (index < str.length() - 1 &&
580 str.at(i: index).isHighSurrogate() &&
581 str.at(i: index + 1).isLowSurrogate())
582 ++index;
583 }
584 ++index;
585 return index;
586}
587
588static 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(name: scope.engine->id_lastIndex()));
593 int lastIndex = advanceStringIndex(index: v->toLength(), str, unicode);
594 if (!rx->put(name: scope.engine->id_lastIndex(), v: QV4::Value::fromInt32(i: lastIndex)))
595 scope.engine->throwTypeError();
596 }
597}
598
599ReturnedValue 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(e: scope.engine));
606 if (scope.hasException())
607 return Encode::undefined();
608 bool global = ScopedValue(scope, rx->get(name: scope.engine->id_global()))->toBoolean();
609
610 if (!global)
611 return exec(engine: scope.engine, o: rx, s);
612
613 bool unicode = ScopedValue(scope, rx->get(name: scope.engine->id_unicode()))->toBoolean();
614
615 rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 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(engine: scope.engine, o: 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(id: PropertyKey::fromArrayIndex(idx: 0));
634 matchString = match->toString(e: scope.engine);
635 if (scope.hasException())
636 return Encode::undefined();
637 a->push_back(v: matchString);
638 advanceLastIndexOnEmptyMatch(e: scope.engine, unicode, rx, matchString, str: s->toQString());
639 ++n;
640 }
641}
642
643ReturnedValue 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(other: *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
657ReturnedValue 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(e: 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(e: scope.engine);
675
676 ScopedValue v(scope);
677 bool global = (v = rx->get(name: scope.engine->id_global()))->toBoolean();
678 bool unicode = false;
679 if (global) {
680 unicode = (v = rx->get(name: scope.engine->id_unicode()))->toBoolean();
681 if (!rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 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(engine: scope.engine, o: rx, s);
691 if (scope.hasException())
692 return Encode::undefined();
693 if (result->isNull())
694 break;
695 results->push_back(v: result);
696 if (!global)
697 break;
698 match = static_cast<Object &>(*result).get(id: PropertyKey::fromArrayIndex(idx: 0));
699 matchString = match->toString(e: scope.engine);
700 if (scope.hasException())
701 return Encode::undefined();
702 advanceLastIndexOnEmptyMatch(e: scope.engine, unicode, rx, matchString, str: 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(id: PropertyKey::fromArrayIndex(idx: i));
710 if (scope.hasException())
711 return Encode::undefined();
712
713 int nCaptures = resultObject->getLength();
714 nCaptures = qMax(a: nCaptures - 1, b: 0);
715 match = resultObject->get(id: PropertyKey::fromArrayIndex(idx: 0));
716 matchString = match->toString(e: scope.engine);
717 if (scope.hasException())
718 return Encode::undefined();
719 QString m = matchString->toQString();
720 int matchLength = m.length();
721 v = resultObject->get(name: scope.engine->id_index());
722 int position = v->toInt32();
723 position = qMax(a: qMin(a: position, b: lengthS), b: 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(id: PropertyKey::fromArrayIndex(idx: n));
732 if (!v->isUndefined())
733 cData->args[n] = v->toString(e: 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(data: cData));
742 if (scope.hasException())
743 return Encode::undefined();
744 replacement = replValue->toQString();
745 } else {
746 replacement = RegExp::getSubstitution(matched: matchString->toQString(), str: s->toQString(), position, captures: cData.args, nCaptures, replacement: replaceValue->toQString());
747 }
748 if (scope.hasException())
749 return Encode::undefined();
750 if (position >= nextSourcePosition) {
751 accumulatedResult += s->toQString().midRef(position: nextSourcePosition, n: position - nextSourcePosition) + replacement;
752 nextSourcePosition = position + matchLength;
753 }
754 }
755 if (nextSourcePosition < lengthS) {
756 accumulatedResult += s->toQString().midRef(position: nextSourcePosition);
757 }
758 return scope.engine->newString(s: accumulatedResult)->asReturnedValue();
759}
760
761ReturnedValue 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(e: scope.engine));
769 if (scope.hasException())
770 return Encode::undefined();
771
772 ScopedValue previousLastIndex(scope, rx->get(name: scope.engine->id_lastIndex()));
773 if (previousLastIndex->toNumber() != 0) {
774 if (!rx->put(name: scope.engine->id_lastIndex(), v: Value::fromInt32(i: 0)))
775 return scope.engine->throwTypeError();
776 }
777
778 ScopedValue result(scope, exec(engine: scope.engine, o: rx, s));
779 if (scope.hasException())
780 return Encode::undefined();
781
782 ScopedValue currentLastIndex(scope, rx->get(name: scope.engine->id_lastIndex()));
783 if (!currentLastIndex->sameValue(other: previousLastIndex)) {
784 if (!rx->put(name: scope.engine->id_lastIndex(), v: 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(name: scope.engine->id_index());
793}
794
795
796ReturnedValue 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(other: *scope.engine->regExpPrototype()))
802 return scope.engine->newString(QStringLiteral("(?:)"))->asReturnedValue();
803 return scope.engine->throwTypeError();
804 }
805
806 return scope.engine->newString(s: re->toString())->asReturnedValue();
807}
808
809ReturnedValue 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(e: scope.engine));
817 if (scope.hasException())
818 return Encode::undefined();
819
820 ScopedValue flagsValue(scope, rx->get(name: scope.engine->id_flags()));
821 ScopedString flags(scope, flagsValue->toString(e: scope.engine));
822 if (scope.hasException())
823 return Encode::undefined();
824 QString flagsString = flags->toQString();
825 if (!flagsString.contains(c: QLatin1Char('y')))
826 flags = scope.engine->newString(s: flagsString + QLatin1Char('y'));
827 bool unicodeMatching = flagsString.contains(c: QLatin1Char('u'));
828
829 const FunctionObject *C = rx->speciesConstructor(scope, defaultConstructor: scope.engine->regExpCtor());
830 if (!C)
831 return Encode::undefined();
832
833 Value *args = scope.alloc(nValues: 2);
834 args[0] = rx;
835 args[1] = flags;
836 ScopedObject splitter(scope, C->callAsConstructor(argv: args, argc: 2, newTarget: 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(engine: scope.engine, o: splitter, s));
850 if (z->isNull())
851 A->push_back(v: 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(i: q);
863 if (!splitter->put(name: scope.engine->id_lastIndex(), v: qq))
864 return scope.engine->throwTypeError();
865 z = exec(engine: scope.engine, o: splitter, s);
866 if (scope.hasException())
867 return Encode::undefined();
868
869 if (z->isNull()) {
870 q = advanceStringIndex(index: q, str: S, unicode: unicodeMatching);
871 continue;
872 }
873
874 v = splitter->get(name: scope.engine->id_lastIndex());
875 int e = qMin(a: v->toInt32(), b: size);
876 if (e == p) {
877 q = advanceStringIndex(index: q, str: S, unicode: unicodeMatching);
878 continue;
879 }
880 QString T = S.mid(position: p, n: q - p);
881 t = scope.engine->newString(s: T);
882 A->push_back(v: t);
883 ++lengthA;
884 if (lengthA == limit)
885 return A->asReturnedValue();
886 p = e;
887 zz = *z;
888 uint numberOfCaptures = qMax(a: zz->getLength() - 1, b: 0ll);
889 for (uint i = 1; i <= numberOfCaptures; ++i) {
890 v = zz->get(id: PropertyKey::fromArrayIndex(idx: 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(position: p);
900 t = scope.engine->newString(s: T);
901 A->push_back(v: t);
902 return A->asReturnedValue();
903}
904
905ReturnedValue 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(other: *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
919ReturnedValue RegExpPrototype::method_test(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
920{
921 Value res = Value::fromReturnedValue(val: method_exec(b, thisObject, argv, argc));
922 return Encode(!res.isNull());
923}
924
925ReturnedValue 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(name: scope.engine->id_source());
934 ScopedString source(scope, v->toString(e: scope.engine));
935 if (scope.hasException())
936 return Encode::undefined();
937 v = r->get(name: scope.engine->id_flags());
938 ScopedString flags(scope, v->toString(e: 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(s: result));
944}
945
946ReturnedValue 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(other: *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
960ReturnedValue 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(e: scope.engine, newVal: re->value());
970 return Encode::undefined();
971}
972
973template <uint index>
974ReturnedValue 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(idx: index) : Encode::undefined());
979 if (res->isUndefined())
980 res = scope.engine->newString();
981 return res->asReturnedValue();
982}
983
984ReturnedValue 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(idx: lastMatch->getLength() - 1) : Encode::undefined());
989 if (res->isUndefined())
990 res = scope.engine->newString();
991 return res->asReturnedValue();
992}
993
994ReturnedValue 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
999ReturnedValue 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(s: lastInput.left(n: regExpCtor->lastMatchStart())));
1005}
1006
1007ReturnedValue 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(s: lastInput.mid(position: regExpCtor->lastMatchEnd())));
1013}
1014
1015QT_END_NAMESPACE
1016

source code of qtdeclarative/src/qml/jsruntime/qv4regexpobject.cpp