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