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
41#include "qv4stringobject_p.h"
42#include "qv4regexp_p.h"
43#include "qv4regexpobject_p.h"
44#include "qv4objectproto_p.h"
45#include <private/qv4mm_p.h>
46#include "qv4scopedvalue_p.h"
47#include "qv4symbol_p.h"
48#include <private/qv4alloca_p.h>
49#include "qv4jscall_p.h"
50#include "qv4stringiterator_p.h"
51#include <QtCore/QDateTime>
52#include <QtCore/QDebug>
53#include <QtCore/QStringList>
54
55#include <cassert>
56
57#ifndef Q_OS_WIN
58# include <time.h>
59# ifndef Q_OS_VXWORKS
60# include <sys/time.h>
61# else
62# include "qplatformdefs.h"
63# endif
64#else
65# include <windows.h>
66#endif
67
68using namespace QV4;
69
70DEFINE_OBJECT_VTABLE(StringObject);
71
72void Heap::StringObject::init()
73{
74 Object::init();
75 Q_ASSERT(vtable() == QV4::StringObject::staticVTable());
76 string.set(e: internalClass->engine, newVal: internalClass->engine->id_empty()->d());
77 setProperty(e: internalClass->engine, index: LengthPropertyIndex, v: Value::fromInt32(i: 0));
78}
79
80void Heap::StringObject::init(const QV4::String *str)
81{
82 Object::init();
83 string.set(e: internalClass->engine, newVal: str->d());
84 setProperty(e: internalClass->engine, index: LengthPropertyIndex, v: Value::fromInt32(i: length()));
85}
86
87Heap::String *Heap::StringObject::getIndex(uint index) const
88{
89 QString str = string->toQString();
90 if (index >= (uint)str.length())
91 return nullptr;
92 return internalClass->engine->newString(s: str.mid(position: index, n: 1));
93}
94
95uint Heap::StringObject::length() const
96{
97 return string->length();
98}
99
100bool StringObject::virtualDeleteProperty(Managed *m, PropertyKey id)
101{
102 Q_ASSERT(m->as<StringObject>());
103 if (id.isArrayIndex()) {
104 StringObject *o = static_cast<StringObject *>(m);
105 uint index = id.asArrayIndex();
106 if (index < static_cast<uint>(o->d()->string->toQString().length()))
107 return false;
108 }
109 return Object::virtualDeleteProperty(m, id);
110}
111
112struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
113{
114 ~StringObjectOwnPropertyKeyIterator() override = default;
115 PropertyKey next(const QV4::Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;
116
117};
118
119PropertyKey StringObjectOwnPropertyKeyIterator::next(const QV4::Object *o, Property *pd, PropertyAttributes *attrs)
120{
121 const StringObject *s = static_cast<const StringObject *>(o);
122 uint slen = s->d()->string->toQString().length();
123 if (arrayIndex < slen) {
124 uint index = arrayIndex;
125 ++arrayIndex;
126 if (attrs)
127 *attrs = Attr_NotConfigurable|Attr_NotWritable;
128 if (pd)
129 pd->value = s->getIndex(index);
130 return PropertyKey::fromArrayIndex(idx: index);
131 } else if (arrayIndex == slen) {
132 if (s->arrayData()) {
133 SparseArrayNode *arrayNode = s->sparseBegin();
134 // iterate until we're past the end of the string
135 while (arrayNode && arrayNode->key() < slen)
136 arrayNode = arrayNode->nextNode();
137 }
138 }
139
140 return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
141}
142
143OwnPropertyKeyIterator *StringObject::virtualOwnPropertyKeys(const Object *m, Value *target)
144{
145 *target = *m;
146 return new StringObjectOwnPropertyKeyIterator;
147}
148
149PropertyAttributes StringObject::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
150{
151 PropertyAttributes attributes = Object::virtualGetOwnProperty(m, id, p);
152 if (attributes != Attr_Invalid)
153 return attributes;
154
155 if (id.isArrayIndex()) {
156 const uint index = id.asArrayIndex();
157 const auto s = static_cast<const StringObject *>(m);
158 if (index < uint(s->d()->string->toQString().length())) {
159 if (p)
160 p->value = s->getIndex(index);
161 return Attr_NotConfigurable|Attr_NotWritable;
162 }
163 }
164 return Object::virtualGetOwnProperty(m, id, p);
165}
166
167DEFINE_OBJECT_VTABLE(StringCtor);
168
169void Heap::StringCtor::init(QV4::ExecutionContext *scope)
170{
171 Heap::FunctionObject::init(scope, QStringLiteral("String"));
172}
173
174ReturnedValue StringCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
175{
176 ExecutionEngine *v4 = static_cast<const Object *>(f)->engine();
177 Scope scope(v4);
178 ScopedString value(scope);
179 if (argc)
180 value = argv[0].toString(e: v4);
181 else
182 value = v4->newString();
183 CHECK_EXCEPTION();
184 ReturnedValue o = Encode(v4->newStringObject(string: value));
185
186 if (!newTarget)
187 return o;
188 ScopedObject obj(scope, o);
189 obj->setProtoFromNewTarget(newTarget);
190 return obj->asReturnedValue();
191}
192
193ReturnedValue StringCtor::virtualCall(const FunctionObject *m, const Value *, const Value *argv, int argc)
194{
195 ExecutionEngine *v4 = m->engine();
196 if (!argc)
197 return v4->newString()->asReturnedValue();
198 if (argv[0].isSymbol())
199 return v4->newString(s: argv[0].symbolValue()->descriptiveString())->asReturnedValue();
200 return argv[0].toString(e: v4)->asReturnedValue();
201}
202
203ReturnedValue StringCtor::method_fromCharCode(const FunctionObject *b, const Value *, const Value *argv, int argc)
204{
205 QString str(argc, Qt::Uninitialized);
206 QChar *ch = str.data();
207 for (int i = 0, ei = argc; i < ei; ++i) {
208 *ch = QChar(argv[i].toUInt16());
209 ++ch;
210 }
211 *ch = 0;
212 return Encode(b->engine()->newString(s: str));
213}
214
215
216
217ReturnedValue StringCtor::method_fromCodePoint(const FunctionObject *f, const Value *, const Value *argv, int argc)
218{
219 ExecutionEngine *e = f->engine();
220 QString result(argc*2, Qt::Uninitialized); // assume worst case
221 QChar *ch = result.data();
222 for (int i = 0; i < argc; ++i) {
223 double num = argv[i].toNumber();
224 if (e->hasException)
225 return Encode::undefined();
226 int cp = static_cast<int>(num);
227 if (cp != num || cp < 0 || cp > 0x10ffff)
228 return e->throwRangeError(QStringLiteral("String.fromCodePoint: argument out of range."));
229 if (cp > 0xffff) {
230 *ch = QChar::highSurrogate(ucs4: cp);
231 ++ch;
232 *ch = QChar::lowSurrogate(ucs4: cp);
233 } else {
234 *ch = cp;
235 }
236 ++ch;
237 }
238 *ch = 0;
239 result.truncate(pos: ch - result.constData());
240 return e->newString(s: result)->asReturnedValue();
241}
242
243ReturnedValue StringCtor::method_raw(const FunctionObject *f, const Value *, const Value *argv, int argc)
244{
245 Scope scope(f);
246 if (!argc)
247 return scope.engine->throwTypeError();
248
249 ScopedObject cooked(scope, argv[0].toObject(e: scope.engine));
250 if (!cooked)
251 return scope.engine->throwTypeError();
252 ScopedString rawString(scope, scope.engine->newIdentifier(QStringLiteral("raw")));
253 ScopedValue rawValue(scope, cooked->get(name: rawString));
254 ScopedObject raw(scope, rawValue->toObject(e: scope.engine));
255 if (scope.hasException())
256 return Encode::undefined();
257
258 ++argv;
259 --argc;
260
261 QString result;
262 uint literalSegments = raw->getLength();
263 if (!literalSegments)
264 return scope.engine->id_empty()->asReturnedValue();
265
266 uint nextIndex = 0;
267 ScopedValue val(scope);
268 while (1) {
269 val = raw->get(idx: nextIndex);
270 result += val->toQString();
271 if (scope.engine->hasException)
272 return Encode::undefined();
273 if (nextIndex + 1 == literalSegments)
274 return scope.engine->newString(s: result)->asReturnedValue();
275
276 if (nextIndex < static_cast<uint>(argc))
277 result += argv[nextIndex].toQString();
278 if (scope.engine->hasException)
279 return Encode::undefined();
280 ++nextIndex;
281 }
282}
283
284void StringPrototype::init(ExecutionEngine *engine, Object *ctor)
285{
286 Scope scope(engine);
287 ScopedObject o(scope);
288
289 // need to set this once again, as these were not fully defined when creating the string proto
290 Heap::InternalClass *ic = scope.engine->classes[ExecutionEngine::Class_StringObject]->changePrototype(proto: scope.engine->objectPrototype()->d());
291 d()->internalClass.set(e: scope.engine, newVal: ic);
292 d()->string.set(e: scope.engine, newVal: scope.engine->id_empty()->d());
293 setProperty(engine: scope.engine, index: Heap::StringObject::LengthPropertyIndex, v: Value::fromInt32(i: 0));
294
295 ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this));
296 ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 1));
297 ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), code: StringCtor::method_fromCharCode, argumentCount: 1);
298 ctor->defineDefaultProperty(QStringLiteral("fromCodePoint"), code: StringCtor::method_fromCodePoint, argumentCount: 1);
299 ctor->defineDefaultProperty(QStringLiteral("raw"), code: StringCtor::method_raw, argumentCount: 1);
300
301 defineDefaultProperty(QStringLiteral("constructor"), value: (o = ctor));
302 defineDefaultProperty(name: engine->id_toString(), code: method_toString);
303 defineDefaultProperty(name: engine->id_valueOf(), code: method_toString); // valueOf and toString are identical
304 defineDefaultProperty(QStringLiteral("charAt"), code: method_charAt, argumentCount: 1);
305 defineDefaultProperty(QStringLiteral("charCodeAt"), code: method_charCodeAt, argumentCount: 1);
306 defineDefaultProperty(QStringLiteral("codePointAt"), code: method_codePointAt, argumentCount: 1);
307 defineDefaultProperty(QStringLiteral("concat"), code: method_concat, argumentCount: 1);
308 defineDefaultProperty(QStringLiteral("endsWith"), code: method_endsWith, argumentCount: 1);
309 defineDefaultProperty(QStringLiteral("indexOf"), code: method_indexOf, argumentCount: 1);
310 defineDefaultProperty(QStringLiteral("includes"), code: method_includes, argumentCount: 1);
311 defineDefaultProperty(QStringLiteral("lastIndexOf"), code: method_lastIndexOf, argumentCount: 1);
312 defineDefaultProperty(QStringLiteral("localeCompare"), code: method_localeCompare, argumentCount: 1);
313 defineDefaultProperty(QStringLiteral("match"), code: method_match, argumentCount: 1);
314 defineDefaultProperty(QStringLiteral("normalize"), code: method_normalize, argumentCount: 0);
315 defineDefaultProperty(QStringLiteral("padEnd"), code: method_padEnd, argumentCount: 1);
316 defineDefaultProperty(QStringLiteral("padStart"), code: method_padStart, argumentCount: 1);
317 defineDefaultProperty(QStringLiteral("repeat"), code: method_repeat, argumentCount: 1);
318 defineDefaultProperty(QStringLiteral("replace"), code: method_replace, argumentCount: 2);
319 defineDefaultProperty(QStringLiteral("search"), code: method_search, argumentCount: 1);
320 defineDefaultProperty(QStringLiteral("slice"), code: method_slice, argumentCount: 2);
321 defineDefaultProperty(QStringLiteral("split"), code: method_split, argumentCount: 2);
322 defineDefaultProperty(QStringLiteral("startsWith"), code: method_startsWith, argumentCount: 1);
323 defineDefaultProperty(QStringLiteral("substr"), code: method_substr, argumentCount: 2);
324 defineDefaultProperty(QStringLiteral("substring"), code: method_substring, argumentCount: 2);
325 defineDefaultProperty(QStringLiteral("toLowerCase"), code: method_toLowerCase);
326 defineDefaultProperty(QStringLiteral("toLocaleLowerCase"), code: method_toLocaleLowerCase);
327 defineDefaultProperty(QStringLiteral("toUpperCase"), code: method_toUpperCase);
328 defineDefaultProperty(QStringLiteral("toLocaleUpperCase"), code: method_toLocaleUpperCase);
329 defineDefaultProperty(QStringLiteral("trim"), code: method_trim);
330 defineDefaultProperty(name: engine->symbol_iterator(), code: method_iterator);
331}
332
333static Heap::String *thisAsString(ExecutionEngine *v4, const QV4::Value *thisObject)
334{
335 if (String *s = thisObject->stringValue())
336 return s->d();
337 if (const StringObject *thisString = thisObject->as<StringObject>())
338 return thisString->d()->string;
339 return thisObject->toString(e: v4);
340}
341
342static QString getThisString(ExecutionEngine *v4, const QV4::Value *thisObject)
343{
344 if (String *s = thisObject->stringValue())
345 return s->toQString();
346 if (const StringObject *thisString = thisObject->as<StringObject>())
347 return thisString->d()->string->toQString();
348 if (thisObject->isUndefined() || thisObject->isNull()) {
349 v4->throwTypeError();
350 return QString();
351 }
352 return thisObject->toQString();
353}
354
355ReturnedValue StringPrototype::method_toString(const FunctionObject *b, const Value *thisObject, const Value *, int)
356{
357 if (thisObject->isString())
358 return thisObject->asReturnedValue();
359
360 ExecutionEngine *v4 = b->engine();
361 const StringObject *o = thisObject->as<StringObject>();
362 if (!o)
363 return v4->throwTypeError();
364 return o->d()->string->asReturnedValue();
365}
366
367ReturnedValue StringPrototype::method_charAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
368{
369 ExecutionEngine *v4 = b->engine();
370 const QString str = getThisString(v4, thisObject);
371 if (v4->hasException)
372 return QV4::Encode::undefined();
373
374 int pos = 0;
375 if (argc > 0)
376 pos = (int) argv[0].toInteger();
377
378 QString result;
379 if (pos >= 0 && pos < str.length())
380 result += str.at(i: pos);
381
382 return Encode(v4->newString(s: result));
383}
384
385ReturnedValue StringPrototype::method_charCodeAt(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
386{
387 ExecutionEngine *v4 = b->engine();
388 const QString str = getThisString(v4, thisObject);
389 if (v4->hasException)
390 return QV4::Encode::undefined();
391
392 int pos = 0;
393 if (argc > 0)
394 pos = (int) argv[0].toInteger();
395
396
397 if (pos >= 0 && pos < str.length())
398 RETURN_RESULT(Encode(str.at(pos).unicode()));
399
400 return Encode(qt_qnan());
401}
402
403ReturnedValue StringPrototype::method_codePointAt(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
404{
405 ExecutionEngine *v4 = f->engine();
406 QString value = getThisString(v4, thisObject);
407 if (v4->hasException)
408 return QV4::Encode::undefined();
409
410 int index = argc ? argv[0].toInteger() : 0;
411 if (v4->hasException)
412 return QV4::Encode::undefined();
413
414 if (index < 0 || index >= value.size())
415 return Encode::undefined();
416
417 uint first = value.at(i: index).unicode();
418 if (QChar::isHighSurrogate(ucs4: first) && index + 1 < value.size()) {
419 uint second = value.at(i: index + 1).unicode();
420 if (QChar::isLowSurrogate(ucs4: second))
421 return Encode(QChar::surrogateToUcs4(high: first, low: second));
422 }
423 return Encode(first);
424}
425
426ReturnedValue StringPrototype::method_concat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
427{
428 ExecutionEngine *v4 = b->engine();
429 QString value = getThisString(v4, thisObject);
430 if (v4->hasException)
431 return QV4::Encode::undefined();
432
433 Scope scope(v4);
434 ScopedString s(scope);
435 for (int i = 0; i < argc; ++i) {
436 s = argv[i].toString(e: scope.engine);
437 if (v4->hasException)
438 return QV4::Encode::undefined();
439
440 Q_ASSERT(s->isString());
441 value += s->toQString();
442 }
443
444 return Encode(v4->newString(s: value));
445}
446
447ReturnedValue StringPrototype::method_endsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
448{
449 ExecutionEngine *v4 = b->engine();
450 const QString value = getThisString(v4, thisObject);
451 if (v4->hasException)
452 return QV4::Encode::undefined();
453
454 if (argc && argv[0].as<RegExpObject>())
455 return v4->throwTypeError();
456 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
457 if (v4->hasException)
458 return Encode::undefined();
459
460 int pos = value.length();
461 if (argc > 1)
462 pos = (int) argv[1].toInteger();
463
464 if (pos == value.length())
465 RETURN_RESULT(Encode(value.endsWith(searchString)));
466
467 QStringRef stringToSearch = value.leftRef(n: pos);
468 return Encode(stringToSearch.endsWith(s: searchString));
469}
470
471ReturnedValue StringPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
472{
473 ExecutionEngine *v4 = b->engine();
474 const QString value = getThisString(v4, thisObject);
475 if (v4->hasException)
476 return QV4::Encode::undefined();
477
478 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
479 if (v4->hasException)
480 return Encode::undefined();
481
482 int pos = 0;
483 if (argc > 1)
484 pos = (int) argv[1].toInteger();
485
486 int index = -1;
487 if (! value.isEmpty())
488 index = value.indexOf(s: searchString, from: qMin(a: qMax(a: pos, b: 0), b: value.length()));
489
490 return Encode(index);
491}
492
493ReturnedValue StringPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
494{
495 ExecutionEngine *v4 = b->engine();
496 const QString value = getThisString(v4, thisObject);
497 if (v4->hasException)
498 return QV4::Encode::undefined();
499
500 if (argc && argv[0].as<RegExpObject>())
501 return v4->throwTypeError();
502 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
503 if (v4->hasException)
504 return Encode::undefined();
505
506 int pos = 0;
507 if (argc > 1) {
508 const Value &posArg = argv[1];
509 pos = (int) posArg.toInteger();
510 if (!posArg.isInteger() && posArg.isNumber() && qIsInf(d: posArg.toNumber()))
511 pos = value.length();
512 }
513
514 if (pos == 0)
515 RETURN_RESULT(Encode(value.contains(searchString)));
516
517 QStringRef stringToSearch = value.midRef(position: pos);
518 return Encode(stringToSearch.contains(s: searchString));
519}
520
521ReturnedValue StringPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
522{
523 ExecutionEngine *v4 = b->engine();
524 const QString value = getThisString(v4, thisObject);
525 if (v4->hasException)
526 return QV4::Encode::undefined();
527
528 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
529 if (v4->hasException)
530 return Encode::undefined();
531
532 double position = argc > 1 ? RuntimeHelpers::toNumber(value: argv[1]) : +qInf();
533 if (std::isnan(x: position))
534 position = +qInf();
535 else
536 position = std::trunc(x: position);
537
538 int pos = std::trunc(x: qMin(a: qMax(a: position, b: 0.0), b: double(value.length())));
539 if (!searchString.isEmpty() && pos == value.length())
540 --pos;
541 if (searchString.isNull() && pos == 0)
542 RETURN_RESULT(Encode(-1));
543 int index = value.lastIndexOf(s: searchString, from: pos);
544 return Encode(index);
545}
546
547ReturnedValue StringPrototype::method_localeCompare(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
548{
549 ExecutionEngine *v4 = b->engine();
550 const QString value = getThisString(v4, thisObject);
551 if (v4->hasException)
552 return QV4::Encode::undefined();
553
554 const QString that = (argc ? argv[0] : Value::undefinedValue()).toQString();
555 return Encode(QString::localeAwareCompare(s1: value, s2: that));
556}
557
558ReturnedValue StringPrototype::method_match(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
559{
560 ExecutionEngine *v4 = b->engine();
561 if (thisObject->isNullOrUndefined())
562 return v4->throwTypeError();
563
564 Scope scope(v4);
565 if (argc && !argv[0].isNullOrUndefined()) {
566 ScopedObject r(scope, argv[0].toObject(e: scope.engine));
567 if (scope.hasException())
568 return Encode::undefined();
569 ScopedValue f(scope, r->get(name: scope.engine->symbol_match()));
570 if (!f->isNullOrUndefined()) {
571 ScopedFunctionObject fo(scope, f);
572 if (!fo)
573 return scope.engine->throwTypeError();
574 return checkedResult(v4: scope.engine, result: fo->call(thisObject: r, argv: thisObject, argc: 1));
575 }
576 }
577
578 ScopedString s(scope, thisObject->toString(e: v4));
579 if (v4->hasException)
580 return Encode::undefined();
581
582 Scoped<RegExpObject> that(scope, argc ? argv[0] : Value::undefinedValue());
583 if (!that) {
584 // convert args[0] to a regexp
585 that = RegExpCtor::virtualCallAsConstructor(f: b, argv, argc, b);
586 if (v4->hasException)
587 return Encode::undefined();
588 }
589 Q_ASSERT(!!that);
590
591 ScopedFunctionObject match(scope, that->get(name: scope.engine->symbol_match()));
592 if (!match)
593 return scope.engine->throwTypeError();
594 return checkedResult(v4: scope.engine, result: match->call(thisObject: that, argv: s, argc: 1));
595}
596
597ReturnedValue StringPrototype::method_normalize(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
598{
599 ExecutionEngine *v4 = f->engine();
600 const QString value = getThisString(v4, thisObject);
601 if (v4->hasException)
602 return Encode::undefined();
603
604 QString::NormalizationForm form = QString::NormalizationForm_C;
605 if (argc >= 1 && !argv[0].isUndefined()) {
606 QString f = argv[0].toQString();
607 if (v4->hasException)
608 return Encode::undefined();
609 if (f == QLatin1String("NFC"))
610 form = QString::NormalizationForm_C;
611 else if (f == QLatin1String("NFD"))
612 form = QString::NormalizationForm_D;
613 else if (f == QLatin1String("NFKC"))
614 form = QString::NormalizationForm_KC;
615 else if (f == QLatin1String("NFKD"))
616 form = QString::NormalizationForm_KD;
617 else
618 return v4->throwRangeError(message: QLatin1String("String.prototype.normalize: Invalid normalization form."));
619 }
620 QString normalized = value.normalized(mode: form);
621 return v4->newString(s: normalized)->asReturnedValue();
622}
623
624ReturnedValue StringPrototype::method_padEnd(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
625{
626 ExecutionEngine *v4 = f->engine();
627 if (thisObject->isNullOrUndefined())
628 return v4->throwTypeError();
629
630 Scope scope(v4);
631 ScopedString s(scope, thisAsString(v4, thisObject));
632 if (v4->hasException)
633 return Encode::undefined();
634 if (!argc)
635 return s->asReturnedValue();
636
637 int maxLen = argv[0].toInteger();
638 if (maxLen <= s->d()->length())
639 return s->asReturnedValue();
640 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(str: " ");
641 if (v4->hasException)
642 return Encode::undefined();
643
644 if (fillString.isEmpty())
645 return s->asReturnedValue();
646
647 QString padded = s->toQString();
648 int oldLength = padded.length();
649 int toFill = maxLen - oldLength;
650 padded.resize(size: maxLen);
651 QChar *ch = padded.data() + oldLength;
652 while (toFill) {
653 int copy = qMin(a: fillString.length(), b: toFill);
654 memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar));
655 toFill -= copy;
656 ch += copy;
657 }
658 *ch = 0;
659
660 return v4->newString(s: padded)->asReturnedValue();
661}
662
663ReturnedValue StringPrototype::method_padStart(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
664{
665 ExecutionEngine *v4 = f->engine();
666 if (thisObject->isNullOrUndefined())
667 return v4->throwTypeError();
668
669 Scope scope(v4);
670 ScopedString s(scope, thisAsString(v4, thisObject));
671 if (v4->hasException)
672 return Encode::undefined();
673 if (!argc)
674 return s->asReturnedValue();
675
676 int maxLen = argv[0].toInteger();
677 if (maxLen <= s->d()->length())
678 return s->asReturnedValue();
679 QString fillString = (argc > 1 && !argv[1].isUndefined()) ? argv[1].toQString() : QString::fromLatin1(str: " ");
680 if (v4->hasException)
681 return Encode::undefined();
682
683 if (fillString.isEmpty())
684 return s->asReturnedValue();
685
686 QString original = s->toQString();
687 int oldLength = original.length();
688 int toFill = maxLen - oldLength;
689 QString padded;
690 padded.resize(size: maxLen);
691 QChar *ch = padded.data();
692 while (toFill) {
693 int copy = qMin(a: fillString.length(), b: toFill);
694 memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar));
695 toFill -= copy;
696 ch += copy;
697 }
698 memcpy(dest: ch, src: original.constData(), n: oldLength*sizeof(QChar));
699 ch += oldLength;
700 *ch = 0;
701
702 return v4->newString(s: padded)->asReturnedValue();
703}
704
705
706ReturnedValue StringPrototype::method_repeat(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
707{
708 ExecutionEngine *v4 = b->engine();
709 const QString value = getThisString(v4, thisObject);
710 if (v4->hasException)
711 return QV4::Encode::undefined();
712
713 double repeats = (argc ? argv[0] : Value::undefinedValue()).toInteger();
714
715 if (repeats < 0 || qIsInf(d: repeats))
716 return v4->throwRangeError(message: QLatin1String("Invalid count value"));
717
718 return Encode(v4->newString(s: value.repeated(times: int(repeats))));
719}
720
721static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount)
722{
723 result->reserve(asize: result->length() + replaceValue.length());
724 for (int i = 0; i < replaceValue.length(); ++i) {
725 if (replaceValue.at(i) == QLatin1Char('$') && i < replaceValue.length() - 1) {
726 ushort ch = replaceValue.at(i: i + 1).unicode();
727 uint substStart = JSC::Yarr::offsetNoMatch;
728 uint substEnd = JSC::Yarr::offsetNoMatch;
729 int skip = 0;
730 if (ch == '$') {
731 *result += QChar(ch);
732 ++i;
733 continue;
734 } else if (ch == '&') {
735 substStart = matchOffsets[0];
736 substEnd = matchOffsets[1];
737 skip = 1;
738 } else if (ch == '`') {
739 substStart = 0;
740 substEnd = matchOffsets[0];
741 skip = 1;
742 } else if (ch == '\'') {
743 substStart = matchOffsets[1];
744 substEnd = input.length();
745 skip = 1;
746 } else if (ch >= '0' && ch <= '9') {
747 uint capture = ch - '0';
748 skip = 1;
749 if (i < replaceValue.length() - 2) {
750 ch = replaceValue.at(i: i + 2).unicode();
751 if (ch >= '0' && ch <= '9') {
752 uint c = capture*10 + ch - '0';
753 if (c < static_cast<uint>(captureCount)) {
754 capture = c;
755 skip = 2;
756 }
757 }
758 }
759 if (capture > 0 && capture < static_cast<uint>(captureCount)) {
760 substStart = matchOffsets[capture * 2];
761 substEnd = matchOffsets[capture * 2 + 1];
762 } else {
763 skip = 0;
764 }
765 }
766 i += skip;
767 if (substStart != JSC::Yarr::offsetNoMatch && substEnd != JSC::Yarr::offsetNoMatch)
768 *result += input.midRef(position: substStart, n: substEnd - substStart);
769 else if (skip == 0) // invalid capture reference. Taken as literal value
770 *result += replaceValue.at(i);
771 } else {
772 *result += replaceValue.at(i);
773 }
774 }
775}
776
777ReturnedValue StringPrototype::method_replace(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
778{
779 QString string;
780 if (const StringObject *thisString = thisObject->as<StringObject>())
781 string = thisString->d()->string->toQString();
782 else
783 string = thisObject->toQString();
784
785 int numCaptures = 0;
786 int numStringMatches = 0;
787
788 uint allocatedMatchOffsets = 64;
789 uint _matchOffsets[64];
790 uint *matchOffsets = _matchOffsets;
791
792 Scope scope(b);
793 ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
794 Scoped<RegExpObject> regExp(scope, searchValue);
795 if (regExp) {
796 uint offset = 0;
797 uint nMatchOffsets = 0;
798
799 // We extract the pointer here to work around a compiler bug on Android.
800 Scoped<RegExp> re(scope, regExp->value());
801 while (true) {
802 int oldSize = nMatchOffsets;
803 if (allocatedMatchOffsets < nMatchOffsets + re->captureCount() * 2) {
804 allocatedMatchOffsets = qMax(a: allocatedMatchOffsets * 2, b: nMatchOffsets + re->captureCount() * 2);
805 uint *newOffsets = (uint *)malloc(size: allocatedMatchOffsets*sizeof(uint));
806 memcpy(dest: newOffsets, src: matchOffsets, n: nMatchOffsets*sizeof(uint));
807 if (matchOffsets != _matchOffsets)
808 free(ptr: matchOffsets);
809 matchOffsets = newOffsets;
810 }
811 if (re->match(string, start: offset, matchOffsets: matchOffsets + oldSize) == JSC::Yarr::offsetNoMatch) {
812 nMatchOffsets = oldSize;
813 break;
814 }
815 nMatchOffsets += re->captureCount() * 2;
816 if (!regExp->global())
817 break;
818 offset = qMax(a: offset + 1, b: matchOffsets[oldSize + 1]);
819 }
820 if (regExp->global()) {
821 regExp->setLastIndex(0);
822 if (scope.hasException())
823 return Encode::undefined();
824 }
825 numStringMatches = nMatchOffsets / (regExp->value()->captureCount() * 2);
826 numCaptures = regExp->value()->captureCount();
827 } else {
828 numCaptures = 1;
829 QString searchString = searchValue->toQString();
830 int idx = string.indexOf(s: searchString);
831 if (idx != -1) {
832 numStringMatches = 1;
833 matchOffsets[0] = idx;
834 matchOffsets[1] = idx + searchString.length();
835 }
836 }
837
838 QString result;
839 ScopedValue replacement(scope);
840 ScopedValue replaceValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
841 ScopedFunctionObject searchCallback(scope, replaceValue);
842 if (!!searchCallback) {
843 result.reserve(asize: string.length() + 10*numStringMatches);
844 ScopedValue entry(scope);
845 Value *arguments = scope.alloc(nValues: numCaptures + 2);
846 int lastEnd = 0;
847 for (int i = 0; i < numStringMatches; ++i) {
848 for (int k = 0; k < numCaptures; ++k) {
849 int idx = (i * numCaptures + k) * 2;
850 uint start = matchOffsets[idx];
851 uint end = matchOffsets[idx + 1];
852 entry = Value::undefinedValue();
853 if (start != JSC::Yarr::offsetNoMatch && end != JSC::Yarr::offsetNoMatch)
854 entry = scope.engine->newString(s: string.mid(position: start, n: end - start));
855 arguments[k] = entry;
856 }
857 uint matchStart = matchOffsets[i * numCaptures * 2];
858 Q_ASSERT(matchStart >= static_cast<uint>(lastEnd));
859 uint matchEnd = matchOffsets[i * numCaptures * 2 + 1];
860 arguments[numCaptures] = Value::fromUInt32(i: matchStart);
861 arguments[numCaptures + 1] = scope.engine->newString(s: string);
862
863 Value that = Value::undefinedValue();
864 replacement = searchCallback->call(thisObject: &that, argv: arguments, argc: numCaptures + 2);
865 CHECK_EXCEPTION();
866 result += string.midRef(position: lastEnd, n: matchStart - lastEnd);
867 result += replacement->toQString();
868 lastEnd = matchEnd;
869 }
870 result += string.midRef(position: lastEnd);
871 } else {
872 QString newString = replaceValue->toQString();
873 result.reserve(asize: string.length() + numStringMatches*newString.size());
874
875 int lastEnd = 0;
876 for (int i = 0; i < numStringMatches; ++i) {
877 int baseIndex = i * numCaptures * 2;
878 uint matchStart = matchOffsets[baseIndex];
879 uint matchEnd = matchOffsets[baseIndex + 1];
880 if (matchStart == JSC::Yarr::offsetNoMatch)
881 continue;
882
883 result += string.midRef(position: lastEnd, n: matchStart - lastEnd);
884 appendReplacementString(result: &result, input: string, replaceValue: newString, matchOffsets: matchOffsets + baseIndex, captureCount: numCaptures);
885 lastEnd = matchEnd;
886 }
887 result += string.midRef(position: lastEnd);
888 }
889
890 if (matchOffsets != _matchOffsets)
891 free(ptr: matchOffsets);
892
893 return Encode(scope.engine->newString(s: result));
894}
895
896ReturnedValue StringPrototype::method_search(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
897{
898 Scope scope(b);
899 QString string = getThisString(v4: scope.engine, thisObject);
900 if (scope.engine->hasException)
901 return QV4::Encode::undefined();
902
903 Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
904 if (!regExp) {
905 regExp = scope.engine->regExpCtor()->callAsConstructor(argv, argc: 1);
906 if (scope.engine->hasException)
907 return QV4::Encode::undefined();
908
909 Q_ASSERT(regExp);
910 }
911 Scoped<RegExp> re(scope, regExp->value());
912 Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint));
913 uint result = re->match(string, /*offset*/start: 0, matchOffsets);
914 if (result == JSC::Yarr::offsetNoMatch)
915 return Encode(-1);
916 else
917 return Encode(result);
918}
919
920ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
921{
922 ExecutionEngine *v4 = b->engine();
923 Scope scope(v4);
924 ScopedString s(scope, thisAsString(v4, thisObject));
925 if (v4->hasException)
926 return QV4::Encode::undefined();
927 Q_ASSERT(s);
928
929 const double length = s->d()->length();
930
931 double start = argc ? argv[0].toInteger() : 0;
932 double end = (argc < 2 || argv[1].isUndefined())
933 ? length : argv[1].toInteger();
934
935 if (start < 0)
936 start = qMax(a: length + start, b: 0.);
937 else
938 start = qMin(a: start, b: length);
939
940 if (end < 0)
941 end = qMax(a: length + end, b: 0.);
942 else
943 end = qMin(a: end, b: length);
944
945 const int intStart = int(start);
946 const int intEnd = int(end);
947
948 int count = qMax(a: 0, b: intEnd - intStart);
949 return Encode(v4->memoryManager->alloc<ComplexString>(args: s->d(), args: intStart, args: count));
950}
951
952ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
953{
954 ExecutionEngine *v4 = b->engine();
955 QString text = getThisString(v4, thisObject);
956 if (v4->hasException)
957 return QV4::Encode::undefined();
958
959 Scope scope(v4);
960 ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
961 ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
962
963 ScopedArrayObject array(scope, scope.engine->newArrayObject());
964
965 if (separatorValue->isUndefined()) {
966 if (limitValue->isUndefined()) {
967 ScopedString s(scope, scope.engine->newString(s: text));
968 array->push_back(v: s);
969 return array.asReturnedValue();
970 }
971 RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
972 }
973
974 uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
975
976 if (limit == 0)
977 return array.asReturnedValue();
978
979 Scoped<RegExpObject> re(scope, separatorValue);
980 if (re) {
981 if (re->value()->pattern->isEmpty()) {
982 re = (RegExpObject *)nullptr;
983 separatorValue = scope.engine->newString();
984 }
985 }
986
987 ScopedString s(scope);
988 if (re) {
989 uint offset = 0;
990 Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 * sizeof(uint));
991 while (true) {
992 Scoped<RegExp> regexp(scope, re->value());
993 uint result = regexp->match(string: text, start: offset, matchOffsets);
994 if (result == JSC::Yarr::offsetNoMatch)
995 break;
996
997 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset, n: matchOffsets[0] - offset))));
998 offset = qMax(a: offset + 1, b: matchOffsets[1]);
999
1000 if (array->getLength() >= limit)
1001 break;
1002
1003 for (int i = 1; i < re->value()->captureCount(); ++i) {
1004 uint start = matchOffsets[i * 2];
1005 uint end = matchOffsets[i * 2 + 1];
1006 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start, n: end - start))));
1007 if (array->getLength() >= limit)
1008 break;
1009 }
1010 }
1011 if (array->getLength() < limit)
1012 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset))));
1013 } else {
1014 QString separator = separatorValue->toQString();
1015 if (separator.isEmpty()) {
1016 for (uint i = 0; i < qMin(a: limit, b: uint(text.length())); ++i)
1017 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: i, n: 1))));
1018 return array.asReturnedValue();
1019 }
1020
1021 int start = 0;
1022 int end;
1023 while ((end = text.indexOf(s: separator, from: start)) != -1) {
1024 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start, n: end - start))));
1025 start = end + separator.size();
1026 if (array->getLength() >= limit)
1027 break;
1028 }
1029 if (array->getLength() < limit && start != -1)
1030 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start))));
1031 }
1032 return array.asReturnedValue();
1033}
1034
1035ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1036{
1037 ExecutionEngine *v4 = b->engine();
1038 const QString value = getThisString(v4, thisObject);
1039 if (v4->hasException)
1040 return QV4::Encode::undefined();
1041
1042 if (argc && argv[0].as<RegExpObject>())
1043 return v4->throwTypeError();
1044 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
1045 if (v4->hasException)
1046 return Encode::undefined();
1047
1048 int pos = 0;
1049 if (argc > 1)
1050 pos = (int) argv[1].toInteger();
1051
1052 if (pos == 0)
1053 return Encode(value.startsWith(s: searchString));
1054
1055 QStringRef stringToSearch = value.midRef(position: pos);
1056 RETURN_RESULT(Encode(stringToSearch.startsWith(searchString)));
1057}
1058
1059ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1060{
1061 ExecutionEngine *v4 = b->engine();
1062 const QString value = getThisString(v4, thisObject);
1063 if (v4->hasException)
1064 return QV4::Encode::undefined();
1065
1066 double start = 0;
1067 if (argc > 0)
1068 start = argv[0].toInteger();
1069
1070 double length = +qInf();
1071 if (argc > 1)
1072 length = argv[1].toInteger();
1073
1074 double count = value.length();
1075 if (start < 0)
1076 start = qMax(a: count + start, b: 0.0);
1077
1078 length = qMin(a: qMax(a: length, b: 0.0), b: count - start);
1079
1080 qint32 x = Value::toInt32(d: start);
1081 qint32 y = Value::toInt32(d: length);
1082 return Encode(v4->newString(s: value.mid(position: x, n: y)));
1083}
1084
1085ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1086{
1087 ExecutionEngine *v4 = b->engine();
1088 const QString value = getThisString(v4, thisObject);
1089 if (v4->hasException)
1090 return QV4::Encode::undefined();
1091
1092 int length = value.length();
1093
1094 double start = 0;
1095 double end = length;
1096
1097 if (argc > 0)
1098 start = argv[0].toInteger();
1099
1100 if (argc > 1 && !argv[1].isUndefined())
1101 end = argv[1].toInteger();
1102
1103 if (std::isnan(x: start) || start < 0)
1104 start = 0;
1105
1106 if (std::isnan(x: end) || end < 0)
1107 end = 0;
1108
1109 if (start > length)
1110 start = length;
1111
1112 if (end > length)
1113 end = length;
1114
1115 if (start > end) {
1116 double was = start;
1117 start = end;
1118 end = was;
1119 }
1120
1121 qint32 x = (int)start;
1122 qint32 y = (int)(end - start);
1123 return Encode(v4->newString(s: value.mid(position: x, n: y)));
1124}
1125
1126ReturnedValue StringPrototype::method_toLowerCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
1127{
1128 ExecutionEngine *v4 = b->engine();
1129 const QString value = getThisString(v4, thisObject);
1130 if (v4->hasException)
1131 return QV4::Encode::undefined();
1132
1133 return Encode(v4->newString(s: value.toLower()));
1134}
1135
1136ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1137{
1138 return method_toLowerCase(b, thisObject, argv, argc);
1139}
1140
1141ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
1142{
1143 ExecutionEngine *v4 = b->engine();
1144 const QString value = getThisString(v4, thisObject);
1145 if (v4->hasException)
1146 return QV4::Encode::undefined();
1147
1148 return Encode(v4->newString(s: value.toUpper()));
1149}
1150
1151ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1152{
1153 return method_toUpperCase(b, thisObject, argv, argc);
1154}
1155
1156ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int)
1157{
1158 ExecutionEngine *v4 = b->engine();
1159 QString s = getThisString(v4, thisObject);
1160 if (v4->hasException)
1161 return QV4::Encode::undefined();
1162
1163 const QChar *chars = s.constData();
1164 int start, end;
1165 for (start = 0; start < s.length(); ++start) {
1166 if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff)
1167 break;
1168 }
1169 for (end = s.length() - 1; end >= start; --end) {
1170 if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff)
1171 break;
1172 }
1173
1174 return Encode(v4->newString(s: QString(chars + start, end - start + 1)));
1175}
1176
1177
1178
1179ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int)
1180{
1181 Scope scope(b);
1182 ScopedString s(scope, thisObject->toString(e: scope.engine));
1183 if (!s || thisObject->isNullOrUndefined())
1184 return scope.engine->throwTypeError();
1185
1186 Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(args: s->d(), args: scope.engine));
1187 return si->asReturnedValue();
1188}
1189

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