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

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