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
77struct StringObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
78{
79 ~StringObjectOwnPropertyKeyIterator() override = default;
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)
92 *attrs = Attr_NotConfigurable|Attr_NotWritable;
93 if (pd)
94 pd->value = s->getIndex(index);
95 return PropertyKey::fromArrayIndex(idx: 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;
111 return new StringObjectOwnPropertyKeyIterator;
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);
126 return Attr_NotConfigurable|Attr_NotWritable;
127 }
128 }
129 return Object::virtualGetOwnProperty(m, id, p);
130}
131
132DEFINE_OBJECT_VTABLE(StringCtor);
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(e: v4);
146 else
147 value = v4->newString();
148 CHECK_EXCEPTION();
149 ReturnedValue o = Encode(v4->newStringObject(string: 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(s: argv[0].symbolValue()->descriptiveString())->asReturnedValue();
165 return argv[0].toString(e: 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(s: 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(ucs4: cp);
195 ++ch;
196 *ch = QChar::lowSurrogate(ucs4: cp);
197 } else {
198 *ch = QChar(cp);
199 }
200 ++ch;
201 }
202 result.truncate(pos: ch - result.constData());
203 return e->newString(s: 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(e: 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(name: rawString));
217 ScopedObject raw(scope, rawValue->toObject(e: 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(idx: nextIndex);
233 result += val->toQString();
234 if (scope.hasException())
235 return Encode::undefined();
236 if (nextIndex + 1 == literalSegments)
237 return scope.engine->newString(s: 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(proto: scope.engine->objectPrototype()->d());
254 d()->internalClass.set(e: scope.engine, newVal: ic);
255 d()->string.set(e: scope.engine, newVal: scope.engine->id_empty()->d());
256 setProperty(engine: scope.engine, index: Heap::StringObject::LengthPropertyIndex, v: Value::fromInt32(i: 0));
257
258 ctor->defineReadonlyProperty(name: engine->id_prototype(), value: (o = this));
259 ctor->defineReadonlyConfigurableProperty(name: engine->id_length(), value: Value::fromInt32(i: 1));
260 ctor->defineDefaultProperty(QStringLiteral("fromCharCode"), code: StringCtor::method_fromCharCode, argumentCount: 1);
261 ctor->defineDefaultProperty(QStringLiteral("fromCodePoint"), code: StringCtor::method_fromCodePoint, argumentCount: 1);
262 ctor->defineDefaultProperty(QStringLiteral("raw"), code: StringCtor::method_raw, argumentCount: 1);
263
264 defineDefaultProperty(QStringLiteral("constructor"), value: (o = ctor));
265 defineDefaultProperty(name: engine->id_toString(), code: method_toString);
266 defineDefaultProperty(name: engine->id_valueOf(), code: method_toString); // valueOf and toString are identical
267 defineDefaultProperty(QStringLiteral("charAt"), code: method_charAt, argumentCount: 1);
268 defineDefaultProperty(QStringLiteral("charCodeAt"), code: method_charCodeAt, argumentCount: 1);
269 defineDefaultProperty(QStringLiteral("codePointAt"), code: method_codePointAt, argumentCount: 1);
270 defineDefaultProperty(QStringLiteral("concat"), code: method_concat, argumentCount: 1);
271 defineDefaultProperty(QStringLiteral("endsWith"), code: method_endsWith, argumentCount: 1);
272 defineDefaultProperty(QStringLiteral("indexOf"), code: method_indexOf, argumentCount: 1);
273 defineDefaultProperty(QStringLiteral("includes"), code: method_includes, argumentCount: 1);
274 defineDefaultProperty(QStringLiteral("lastIndexOf"), code: method_lastIndexOf, argumentCount: 1);
275 defineDefaultProperty(QStringLiteral("localeCompare"), code: method_localeCompare, argumentCount: 1);
276 defineDefaultProperty(QStringLiteral("match"), code: method_match, argumentCount: 1);
277 defineDefaultProperty(QStringLiteral("normalize"), code: method_normalize, argumentCount: 0);
278 defineDefaultProperty(QStringLiteral("padEnd"), code: method_padEnd, argumentCount: 1);
279 defineDefaultProperty(QStringLiteral("padStart"), code: method_padStart, argumentCount: 1);
280 defineDefaultProperty(QStringLiteral("repeat"), code: method_repeat, argumentCount: 1);
281 defineDefaultProperty(QStringLiteral("replace"), code: method_replace, argumentCount: 2);
282 defineDefaultProperty(QStringLiteral("search"), code: method_search, argumentCount: 1);
283 defineDefaultProperty(QStringLiteral("slice"), code: method_slice, argumentCount: 2);
284 defineDefaultProperty(QStringLiteral("split"), code: method_split, argumentCount: 2);
285 defineDefaultProperty(QStringLiteral("startsWith"), code: method_startsWith, argumentCount: 1);
286 defineDefaultProperty(QStringLiteral("substr"), code: method_substr, argumentCount: 2);
287 defineDefaultProperty(QStringLiteral("substring"), code: method_substring, argumentCount: 2);
288 defineDefaultProperty(QStringLiteral("toLowerCase"), code: method_toLowerCase);
289 defineDefaultProperty(QStringLiteral("toLocaleLowerCase"), code: method_toLocaleLowerCase);
290 defineDefaultProperty(QStringLiteral("toUpperCase"), code: method_toUpperCase);
291 defineDefaultProperty(QStringLiteral("toLocaleUpperCase"), code: method_toLocaleUpperCase);
292 defineDefaultProperty(QStringLiteral("trim"), code: method_trim);
293 defineDefaultProperty(name: engine->symbol_iterator(), code: 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(e: 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(i: pos);
344
345 return Encode(v4->newString(s: 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(i: index).unicode();
381 if (QChar::isHighSurrogate(ucs4: first) && index + 1 < value.size()) {
382 uint second = value.at(i: index + 1).unicode();
383 if (QChar::isLowSurrogate(ucs4: second))
384 return Encode(QChar::surrogateToUcs4(high: first, low: 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(e: 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(s: 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(n: pos);
431 return Encode(stringToSearch.endsWith(s: 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(s: searchString, from: qMin(a: qMax(a: pos, b: 0.0), b: 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(d: 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(s: 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(value: argv[1]) : +qInf();
496 if (std::isnan(x: position))
497 position = +qInf();
498 else
499 position = std::trunc(x: position);
500
501 int pos = std::trunc(x: qMin(a: qMax(a: position, b: 0.0), b: 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(s: searchString, from: 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(s1: value, s2: 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(e: scope.engine));
530 if (scope.hasException())
531 return Encode::undefined();
532 ScopedValue f(scope, r->get(name: scope.engine->symbol_match()));
533 if (!f->isNullOrUndefined()) {
534 ScopedFunctionObject fo(scope, f);
535 if (!fo)
536 return scope.engine->throwTypeError();
537 return checkedResult(v4: scope.engine, result: fo->call(thisObject: r, argv: thisObject, argc: 1));
538 }
539 }
540
541 ScopedString s(scope, thisObject->toString(e: 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(f: b, argv, argc, b);
549 if (v4->hasException)
550 return Encode::undefined();
551 }
552 Q_ASSERT(!!that);
553
554 ScopedFunctionObject match(scope, that->get(name: scope.engine->symbol_match()));
555 if (!match)
556 return scope.engine->throwTypeError();
557 return checkedResult(v4: scope.engine, result: match->call(thisObject: that, argv: s, argc: 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(message: QLatin1String("String.prototype.normalize: Invalid normalization form."));
582 }
583 QString normalized = value.normalized(mode: form);
584 return v4->newString(s: 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(size: maxLen);
614 QChar *ch = padded.data() + oldLength;
615 while (toFill) {
616 int copy = qMin(a: fillString.size(), b: toFill);
617 memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar));
618 toFill -= copy;
619 ch += copy;
620 }
621 *ch = QChar::Null;
622
623 return v4->newString(s: 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(size: maxLen);
654 QChar *ch = padded.data();
655 while (toFill) {
656 int copy = qMin(a: fillString.size(), b: toFill);
657 memcpy(dest: ch, src: fillString.constData(), n: copy*sizeof(QChar));
658 toFill -= copy;
659 ch += copy;
660 }
661 memcpy(dest: ch, src: original.constData(), n: oldLength*sizeof(QChar));
662 ch += oldLength;
663 *ch = QChar::Null;
664
665 return v4->newString(s: 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(d: repeats))
679 return v4->throwRangeError(message: QLatin1String("Invalid count value"));
680
681 return Encode(v4->newString(s: value.repeated(times: int(repeats))));
682}
683
684static void appendReplacementString(QString *result, const QString &input, const QString& replaceValue, uint* matchOffsets, int captureCount)
685{
686 result->reserve(asize: 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: 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: 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(pos: substStart, n: 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(a: allocatedMatchOffsets * 2, b: nMatchOffsets + re->captureCount() * 2);
768 uint *newOffsets = (uint *)malloc(size: allocatedMatchOffsets*sizeof(uint));
769 memcpy(dest: newOffsets, src: matchOffsets, n: nMatchOffsets*sizeof(uint));
770 if (matchOffsets != _matchOffsets)
771 free(ptr: matchOffsets);
772 matchOffsets = newOffsets;
773 }
774 if (re->match(string, start: offset, matchOffsets: 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(a: offset + 1, b: 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(s: 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(asize: string.size() + 10*numStringMatches);
814 ScopedValue entry(scope);
815 Value *arguments = scope.alloc(nValues: 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(s: string.mid(position: start, n: 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(i: matchStart);
831 arguments[numCaptures + 1] = scope.engine->newString(s: string);
832
833 Value that = Value::undefinedValue();
834 replacement = searchCallback->call(thisObject: &that, argv: arguments, argc: numCaptures + 2);
835 CHECK_EXCEPTION();
836 result += QStringView{string}.mid(pos: lastEnd, n: matchStart - lastEnd);
837 result += replacement->toQString();
838 lastEnd = matchEnd;
839 }
840 result += QStringView{string}.mid(pos: lastEnd);
841 } else {
842 QString newString = replaceValue->toQString();
843 result.reserve(asize: 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(pos: lastEnd, n: matchStart - lastEnd);
854 appendReplacementString(result: &result, input: string, replaceValue: newString, matchOffsets: matchOffsets + baseIndex, captureCount: numCaptures);
855 lastEnd = matchEnd;
856 }
857 result += QStringView{string}.mid(pos: lastEnd);
858 }
859
860 if (matchOffsets != _matchOffsets)
861 free(ptr: matchOffsets);
862
863 return Encode(scope.engine->newString(s: 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(v4: 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, argc: 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*/start: 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(a: length + start, b: 0.);
907 else
908 start = qMin(a: start, b: length);
909
910 if (end < 0)
911 end = qMax(a: length + end, b: 0.);
912 else
913 end = qMin(a: end, b: length);
914
915 const int intStart = int(start);
916 const int intEnd = int(end);
917
918 int count = qMax(a: 0, b: intEnd - intStart);
919 return Encode(v4->memoryManager->alloc<ComplexString>(args: s->d(), args: intStart, args&: 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(s: text));
938 array->push_back(v: 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(string: text, start: offset, matchOffsets);
964 if (result == JSC::Yarr::offsetNoMatch)
965 break;
966
967 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset, n: matchOffsets[0] - offset))));
968 offset = qMax(a: offset + 1, b: 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(v: (s = scope.engine->newString(s: text.mid(position: start, n: end - start))));
977 if (array->getLength() >= limit)
978 break;
979 }
980 }
981 if (array->getLength() < limit)
982 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: offset))));
983 } else {
984 QString separator = separatorValue->toQString();
985 if (separator.isEmpty()) {
986 for (uint i = 0; i < qMin(a: limit, b: uint(text.size())); ++i)
987 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: i, n: 1))));
988 return array.asReturnedValue();
989 }
990
991 int start = 0;
992 int end;
993 while ((end = text.indexOf(s: separator, from: start)) != -1) {
994 array->push_back(v: (s = scope.engine->newString(s: text.mid(position: start, n: 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(v: (s = scope.engine->newString(s: text.mid(position: 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 val: pos,
1024 lo: 0.0,
1025 hi: double(value.size()));
1026
1027 if (pos == 0)
1028 return Encode(value.startsWith(s: 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(a: count + start, b: 0.0);
1052
1053 length = qMin(a: qMax(a: length, b: 0.0), b: count - start);
1054
1055 qint32 x = Value::toInt32(d: start);
1056 qint32 y = Value::toInt32(d: length);
1057 return Encode(v4->newString(s: value.mid(position: x, n: 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(x: start) || start < 0)
1079 start = 0;
1080
1081 if (std::isnan(x: 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(s: value.mid(position: x, n: 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(s: value.toLower()));
1109}
1110
1111ReturnedValue StringPrototype::method_toLocaleLowerCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1112{
1113 return method_toLowerCase(b, thisObject, argv, argc);
1114}
1115
1116ReturnedValue StringPrototype::method_toUpperCase(const FunctionObject *b, const Value *thisObject, const Value *, int)
1117{
1118 ExecutionEngine *v4 = b->engine();
1119 const QString value = getThisString(v4, thisObject);
1120 if (v4->hasException)
1121 return QV4::Encode::undefined();
1122
1123 return Encode(v4->newString(s: value.toUpper()));
1124}
1125
1126ReturnedValue StringPrototype::method_toLocaleUpperCase(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
1127{
1128 return method_toUpperCase(b, thisObject, argv, argc);
1129}
1130
1131ReturnedValue StringPrototype::method_trim(const FunctionObject *b, const Value *thisObject, const Value *, int)
1132{
1133 ExecutionEngine *v4 = b->engine();
1134 QString s = getThisString(v4, thisObject);
1135 if (v4->hasException)
1136 return QV4::Encode::undefined();
1137
1138 const QChar *chars = s.constData();
1139 int start, end;
1140 for (start = 0; start < s.size(); ++start) {
1141 if (!chars[start].isSpace() && chars[start].unicode() != 0xfeff)
1142 break;
1143 }
1144 for (end = s.size() - 1; end >= start; --end) {
1145 if (!chars[end].isSpace() && chars[end].unicode() != 0xfeff)
1146 break;
1147 }
1148
1149 return Encode(v4->newString(s: QString(chars + start, end - start + 1)));
1150}
1151
1152
1153
1154ReturnedValue StringPrototype::method_iterator(const FunctionObject *b, const Value *thisObject, const Value *, int)
1155{
1156 Scope scope(b);
1157 ScopedString s(scope, thisObject->toString(e: scope.engine));
1158 if (!s || thisObject->isNullOrUndefined())
1159 return scope.engine->throwTypeError();
1160
1161 Scoped<StringIteratorObject> si(scope, scope.engine->memoryManager->allocate<StringIteratorObject>(args: s->d(), args&: scope.engine));
1162 return si->asReturnedValue();
1163}
1164

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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