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