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 Scope scope(b);
871 QString string = getThisString(scope.engine, thisObject);
872 if (scope.hasException())
873 return QV4::Encode::undefined();
874
875 Scoped<RegExpObject> regExp(scope, argc ? argv[0] : Value::undefinedValue());
876 if (!regExp) {
877 regExp = scope.engine->regExpCtor()->callAsConstructor(argv, 1);
878 if (scope.hasException())
879 return QV4::Encode::undefined();
880
881 Q_ASSERT(regExp);
882 }
883 Scoped<RegExp> re(scope, regExp->value());
884 Q_ALLOCA_VAR(uint, matchOffsets, regExp->value()->captureCount() * 2 * sizeof(uint));
885 uint result = re->match(string, /*offset*/0, matchOffsets);
886 if (result == JSC::Yarr::offsetNoMatch)
887 return Encode(-1);
888 else
889 return Encode(result);
890}
891
892ReturnedValue StringPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
893{
894 ExecutionEngine *v4 = b->engine();
895 Scope scope(v4);
896 ScopedString s(scope, thisAsString(v4, thisObject));
897 if (v4->hasException)
898 return QV4::Encode::undefined();
899 Q_ASSERT(s);
900
901 const double length = s->d()->length();
902
903 double start = argc ? argv[0].toInteger() : 0;
904 double end = (argc < 2 || argv[1].isUndefined())
905 ? length : argv[1].toInteger();
906
907 if (start < 0)
908 start = qMax(length + start, 0.);
909 else
910 start = qMin(start, length);
911
912 if (end < 0)
913 end = qMax(length + end, 0.);
914 else
915 end = qMin(end, length);
916
917 const int intStart = int(start);
918 const int intEnd = int(end);
919
920 int count = qMax(0, intEnd - intStart);
921 return Encode(v4->memoryManager->alloc<ComplexString>(s->d(), intStart, count));
922}
923
924ReturnedValue StringPrototype::method_split(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
925{
926 ExecutionEngine *v4 = b->engine();
927 QString text = getThisString(v4, thisObject);
928 if (v4->hasException)
929 return QV4::Encode::undefined();
930
931 Scope scope(v4);
932 ScopedValue separatorValue(scope, argc ? argv[0] : Value::undefinedValue());
933 ScopedValue limitValue(scope, argc > 1 ? argv[1] : Value::undefinedValue());
934
935 ScopedArrayObject array(scope, scope.engine->newArrayObject());
936
937 if (separatorValue->isUndefined()) {
938 if (limitValue->isUndefined()) {
939 ScopedString s(scope, scope.engine->newString(text));
940 array->push_back(s);
941 return array.asReturnedValue();
942 }
943 RETURN_RESULT(scope.engine->newString(text.left(limitValue->toInteger())));
944 }
945
946 uint limit = limitValue->isUndefined() ? UINT_MAX : limitValue->toUInt32();
947
948 if (limit == 0)
949 return array.asReturnedValue();
950
951 Scoped<RegExpObject> re(scope, separatorValue);
952 if (re) {
953 if (re->value()->pattern->isEmpty()) {
954 re = (RegExpObject *)nullptr;
955 separatorValue = scope.engine->newString();
956 }
957 }
958
959 ScopedString s(scope);
960 if (re) {
961 uint offset = 0;
962 Q_ALLOCA_VAR(uint, matchOffsets, re->value()->captureCount() * 2 * sizeof(uint));
963 while (true) {
964 Scoped<RegExp> regexp(scope, re->value());
965 uint result = regexp->match(text, offset, matchOffsets);
966 if (result == JSC::Yarr::offsetNoMatch)
967 break;
968
969 array->push_back((s = scope.engine->newString(text.mid(offset, matchOffsets[0] - offset))));
970 offset = qMax(offset + 1, matchOffsets[1]);
971
972 if (array->getLength() >= limit)
973 break;
974
975 for (int i = 1; i < re->value()->captureCount(); ++i) {
976 uint start = matchOffsets[i * 2];
977 uint end = matchOffsets[i * 2 + 1];
978 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
979 if (array->getLength() >= limit)
980 break;
981 }
982 }
983 if (array->getLength() < limit)
984 array->push_back((s = scope.engine->newString(text.mid(offset))));
985 } else {
986 QString separator = separatorValue->toQString();
987 if (separator.isEmpty()) {
988 for (uint i = 0; i < qMin(limit, uint(text.size())); ++i)
989 array->push_back((s = scope.engine->newString(text.mid(i, 1))));
990 return array.asReturnedValue();
991 }
992
993 int start = 0;
994 int end;
995 while ((end = text.indexOf(separator, start)) != -1) {
996 array->push_back((s = scope.engine->newString(text.mid(start, end - start))));
997 start = end + separator.size();
998 if (array->getLength() >= limit)
999 break;
1000 }
1001 if (array->getLength() < limit && start != -1)
1002 array->push_back((s = scope.engine->newString(text.mid(start))));
1003 }
1004 return array.asReturnedValue();
1005}
1006
1007ReturnedValue StringPrototype::method_startsWith(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1008{
1009 ExecutionEngine *v4 = b->engine();
1010 const QString value = getThisString(v4, thisObject);
1011 if (v4->hasException)
1012 return QV4::Encode::undefined();
1013
1014 if (argc && argv[0].as<RegExpObject>())
1015 return v4->throwTypeError();
1016 QString searchString = (argc ? argv[0] : Value::undefinedValue()).toQString();
1017 if (v4->hasException)
1018 return Encode::undefined();
1019
1020 double pos = 0;
1021 if (argc > 1)
1022 pos = argv[1].toInteger();
1023
1024 pos = std::clamp(
1025 pos,
1026 0.0,
1027 double(value.size()));
1028
1029 if (pos == 0)
1030 return Encode(value.startsWith(searchString));
1031
1032 QStringView stringToSearch = QStringView{value}.mid(pos);
1033 RETURN_RESULT(Encode(stringToSearch.startsWith(searchString)));
1034}
1035
1036ReturnedValue StringPrototype::method_substr(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1037{
1038 ExecutionEngine *v4 = b->engine();
1039 const QString value = getThisString(v4, thisObject);
1040 if (v4->hasException)
1041 return QV4::Encode::undefined();
1042
1043 double start = 0;
1044 if (argc > 0)
1045 start = argv[0].toInteger();
1046
1047 double length = +qInf();
1048 if (argc > 1)
1049 length = argv[1].toInteger();
1050
1051 double count = value.size();
1052 if (start < 0)
1053 start = qMax(count + start, 0.0);
1054
1055 length = qMin(qMax(length, 0.0), count - start);
1056
1057 qint32 x = Value::toInt32(start);
1058 qint32 y = Value::toInt32(length);
1059 return Encode(v4->newString(value.mid(x, y)));
1060}
1061
1062ReturnedValue StringPrototype::method_substring(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1063{
1064 ExecutionEngine *v4 = b->engine();
1065 const QString value = getThisString(v4, thisObject);
1066 if (v4->hasException)
1067 return QV4::Encode::undefined();
1068
1069 int length = value.size();
1070
1071 double start = 0;
1072 double end = length;
1073
1074 if (argc > 0)
1075 start = argv[0].toInteger();
1076
1077 if (argc > 1 && !argv[1].isUndefined())
1078 end = argv[1].toInteger();
1079
1080 if (std::isnan(start) || start < 0)
1081 start = 0;
1082
1083 if (std::isnan(end) || end < 0)
1084 end = 0;
1085
1086 if (start > length)
1087 start = length;
1088
1089 if (end > length)
1090 end = length;
1091
1092 if (start > end) {
1093 double was = start;
1094 start = end;
1095 end = was;
1096 }
1097
1098 qint32 x = (int)start;
1099 qint32 y = (int)(end - start);
1100 return Encode(v4->newString(value.mid(x, y)));
1101}
1102
1103ReturnedValue StringPrototype::method_toLowerCase(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(value.toLower()));
1111}
1112
1113static const QLocale *getLocaleDataResource(const QV4::Value &val)
1114{
1115 if (const QV4::QQmlValueTypeWrapper *wrapper = val.as<QQmlValueTypeWrapper>())
1116 return wrapper->cast<const QLocale>();
1117
1118 return nullptr;
1119}
1120
1121static QLocale getLocaleFromArgs(const FunctionObject *b, const QV4::Value *argv, int argc)
1122{
1123 if (argc == 0)
1124 return QLocale();
1125
1126 ExecutionEngine *v4 = b->engine();
1127 Scope scope(b);
1128 QString stringifiedLocale;
1129 // First argument is a string, we check if it is a valid locale
1130 if (const QV4::String *that = argv[0].as<QV4::String>()) {
1131 stringifiedLocale = that->toQString();
1132 } else if (const QLocale *locale = getLocaleDataResource(argv[0])) {
1133 return *locale;
1134 } else if (argv[0].isObject()) {
1135 // First argument is an array, we check if the first element is
1136 // a string and a valid locale.
1137 ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
1138 ScopedValue kValue(scope);
1139 if (arrayLike->getLength() > 0) {
1140 kValue = arrayLike->get(uint(0));
1141 if (kValue->isString())
1142 stringifiedLocale = kValue->toQString();
1143 else if (const QLocale *locale = getLocaleDataResource(kValue))
1144 return *locale;
1145 }
1146 } else {
1147 v4->throwTypeError();
1148 }
1149
1150 return QLocale(stringifiedLocale);
1151}
1152
1153ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1154{
1155 ExecutionEngine *v4 = b->engine();
1156 const QString value = getThisString(v4, thisObject);
1157 if (v4->hasException)
1158 return QV4::Encode::undefined();
1159
1160 QLocale locale = getLocaleFromArgs(b, argv, argc);
1161 return Encode(v4->newString(locale.toLower(value)));
1162}
1163
1164ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
1165{
1166 ExecutionEngine *v4 = b->engine();
1167 const QString value = getThisString(v4, thisObject);
1168 if (v4->hasException)
1169 return QV4::Encode::undefined();
1170
1171 return Encode(v4->newString(value.toUpper()));
1172}
1173
1174ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1175{
1176 ExecutionEngine *v4 = b->engine();
1177 const QString value = getThisString(v4, thisObject);
1178 if (v4->hasException)
1179 return QV4::Encode::undefined();
1180
1181 QLocale locale = getLocaleFromArgs(b, argv, argc);
1182 return Encode(v4->newString(locale.toUpper(value)));
1183}
1184
1185ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int)
1186{
1187 ExecutionEngine *v4 = b->engine();
1188 QString s = getThisString(v4, thisObject);
1189 if (v4->hasException)
1190 return QV4::Encode::undefined();
1191
1192 const QChar *chars = s.constData();
1193 int start, end;
1194 for (start = 0; start < s.size(); ++start) {
1195 if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff)
1196 break;
1197 }
1198 for (end = s.size() - 1; end >= start; --end) {
1199 if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff)
1200 break;
1201 }
1202
1203 return Encode(v4->newString(QString(chars + start, end - start + 1)));
1204}
1205
1206
1207
1208ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int)
1209{
1210 Scope scope(b);
1211 ScopedString s(scope, thisObject->toString(scope.engine));
1212 if (!s || thisObject->isNullOrUndefined())
1213 return scope.engine->throwTypeError();
1214
1215 Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(s->d(), scope.engine));
1216 return si->asReturnedValue();
1217}
Definition qjsvalue.h:23
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)