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

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