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