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