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

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