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