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#include <QtCore/qsequentialiterable.h>
5
6#include "qv4sequenceobject_p.h"
7
8#include <private/qv4functionobject_p.h>
9#include <private/qv4arrayobject_p.h>
10#include <private/qqmlengine_p.h>
11#include <private/qv4scopedvalue_p.h>
12#include <private/qv4jscall_p.h>
13#include <private/qqmlmetatype_p.h>
14#include <private/qqmltype_p_p.h>
15#include <private/qqmlvaluetypewrapper_p.h>
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 * \class QV4::Sequence
21 * \internal
22 *
23 * A Sequence stores the contents of a sequential container and makes them acccessible
24 * to the JavaScript engine. It behaves mostly like a regular JavaScript array. The
25 * entries of the container are exposed as array elements.
26 *
27 * Sequence is a ReferenceObject. Therefore it writes back its contents to the property
28 * it was retrieved from whenever it changes. It also re-reads the property whenever
29 * that one changes.
30 *
31 * As long as a Sequence is attached to a property this way, it is the responsibility of
32 * the property's surrounding (C++) object to keep the contents valid. It has to, for
33 * example, track pointers to QObjects potentially deleted in other places so that they
34 * don't become dangling.
35 *
36 * However, the Sequence can also be detached. This happens predominantly by assigning
37 * it to a QML-declared property. In that case, it becomes the Sequence's responsibility
38 * to track its contents. To do so, it does not necessarily keep an actual instance of
39 * the original container in this case, but may rather stores its contents as actual
40 * JavaScript array elements. This includes QObjectWrappers for all QObject pointers it
41 * may contain. The contents are then marked like all JavaScript array elements when the
42 * garbage collector runs, and QObjectWrapper also guards against external deletion.
43 * There is no property to read or write back in this case, and neither does the
44 * internal container need to be updated. Therefore, the objects stored as array elements
45 * are created detached and won't read or write back.
46 *
47 * For element types that don't need to be marked, Sequence will still use the original
48 * container for storage, even in the detached case. This is usually more efficient
49 * because because it saves some data conversion. The only types that need to be marked
50 * are pointers to QObject-derived types, either stored as-is or hidden inside QVariant.
51 * Whenever the container cannot possibly hold such elements (directly or indirectly),
52 * the original container is used.
53 */
54
55Q_STATIC_LOGGING_CATEGORY(lcListValueConversion, "qt.qml.listvalueconversion")
56
57namespace QV4 {
58
59DEFINE_OBJECT_VTABLE(Sequence);
60
61static ReturnedValue doGetIndexed(Heap::Sequence *p, qsizetype index)
62{
63 Q_ASSERT(p->isStoredInline());
64 QV4::Scope scope(p->internalClass->engine);
65
66 const QMetaType valueMetaType = p->valueMetaType();
67 const QMetaSequence metaSequence = p->metaSequence();
68
69 Heap::ReferenceObject::Flags flags = Heap::ReferenceObject::EnforcesLocation;
70 if (metaSequence.canSetValueAtIndex())
71 flags |= Heap::ReferenceObject::CanWriteBack;
72
73 const void *container = p->storagePointer();
74 Q_ASSERT(container); // Must readReference() before
75
76 QVariant result;
77 if (valueMetaType == QMetaType::fromType<QVariant>()) {
78 flags |= Heap::ReferenceObject::IsVariant;
79 metaSequence.valueAtIndex(container, index, result: &result);
80 } else {
81 result = QVariant(valueMetaType);
82 metaSequence.valueAtIndex(container, index, result: result.data());
83 }
84
85 QV4::ScopedValue v(scope, scope.engine->fromVariant(variant: result, parent: p, property: index, flags));
86 if (QQmlValueTypeWrapper *ref = v->as<QQmlValueTypeWrapper>()) {
87 if (CppStackFrame *frame = scope.engine->currentStackFrame)
88 ref->d()->setLocation(function: frame->v4Function, statement: frame->statementNumber());
89 // No need to read the reference. We've done that above already.
90 }
91 return v->asReturnedValue();
92}
93
94static void *createVariantData(QMetaType type, QVariant *variant)
95{
96 if (type == QMetaType::fromType<QVariant>())
97 return variant;
98 *variant = QVariant(type);
99 return variant->data();
100}
101
102// helper function to generate valid warnings if errors occur during sequence operations.
103static void generateWarning(QV4::ExecutionEngine *v4, const QString& description)
104{
105 QQmlEngine *engine = v4->qmlEngine();
106 if (!engine)
107 return;
108 QQmlError retn;
109 retn.setDescription(description);
110
111 QV4::CppStackFrame *stackFrame = v4->currentStackFrame;
112
113 retn.setLine(stackFrame->lineNumber());
114 retn.setUrl(QUrl(stackFrame->source()));
115 QQmlEnginePrivate::warning(engine, retn);
116}
117
118static qsizetype sizeInline(const Heap::Sequence *p)
119{
120 Q_ASSERT(p->isStoredInline());
121
122 if (const void *container = p->storagePointer())
123 return p->metaSequence().size(container);
124
125 // It can be stored inline, and the container can still be nullptr.
126 // This happens if we construct it from a nullptr in the first place and never update it.
127 // It means it's empty.
128 return 0;
129}
130
131struct SequenceOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
132{
133 ~SequenceOwnPropertyKeyIterator() override = default;
134 PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override
135 {
136 Heap::Sequence *p = static_cast<const Sequence *>(o)->d();
137 Q_ASSERT(p->isStoredInline());
138
139 if (p->isReference() && !p->loadReference())
140 return PropertyKey::invalid();
141
142 const qsizetype size = sizeInline(p);
143 if (size > 0 && qIsAtMostSizetypeLimit(length: arrayIndex, limit: size - 1)) {
144 const uint index = arrayIndex;
145 ++arrayIndex;
146 if (attrs)
147 *attrs = QV4::Attr_Data;
148 if (pd)
149 pd->value = doGetIndexed(p, index);
150 return PropertyKey::fromArrayIndex(idx: index);
151 }
152
153 if (memberIndex == 0) {
154 ++memberIndex;
155 return o->engine()->id_length()->propertyKey();
156 }
157
158 // You cannot add any own properties via the regular JavaScript interfaces.
159 return PropertyKey::invalid();
160 }
161};
162
163void Heap::Sequence::initTypes(QMetaType listType, QMetaSequence metaSequence)
164{
165 m_listType = listType.iface();
166 Q_ASSERT(m_listType);
167 m_metaSequence = metaSequence.iface();
168 Q_ASSERT(m_metaSequence);
169}
170
171void Heap::Sequence::init(QMetaType listType, QMetaSequence metaSequence, const void *container)
172{
173 ReferenceObject::init(object: nullptr, property: -1, flags: NoFlag);
174 initTypes(listType, metaSequence);
175 if (isStoredInline())
176 createInlineStorage(container);
177 else
178 createElementWrappers(container);
179}
180
181void Heap::Sequence::init(
182 QMetaType listType, QMetaSequence metaSequence, const void *container,
183 Heap::Object *object, int propertyIndex, Heap::ReferenceObject::Flags flags)
184{
185 ReferenceObject::init(object, property: propertyIndex, flags: flags | IsDirty);
186 initTypes(listType, metaSequence);
187
188 if (isStoredInline()) {
189 if (CppStackFrame *frame = internalClass->engine->currentStackFrame)
190 setLocation(function: frame->v4Function, statement: frame->statementNumber());
191 createInlineStorage(container);
192 if (!container && (flags & EnforcesLocation))
193 QV4::ReferenceObject::readReference(ref: this);
194 } else {
195 createElementWrappers(container);
196 }
197}
198
199void Heap::Sequence::createInlineStorage(const void *container)
200{
201 Q_ASSERT(isStoredInline());
202
203 QV4::Scope scope(internalClass->engine);
204 QV4::Scoped<QV4::Sequence> o(scope, this);
205 o->setArrayType(Heap::ArrayData::Custom);
206 if (container)
207 m_container = listType().create(copy: container);
208}
209
210void Heap::Sequence::createElementWrappers(const void *container)
211{
212 Q_ASSERT(!isStoredInline());
213
214 if (!container)
215 return;
216
217 const QMetaSequence metaSequence(m_metaSequence);
218 const QMetaType valueMetaType = metaSequence.valueMetaType();
219 const qsizetype size = metaSequence.size(container);
220
221 QV4::Scope scope(internalClass->engine);
222 if (!qIsAtMostUintLimit(length: size)) {
223 generateWarning(v4: scope.engine, description: QLatin1String("Sequence length out of range"));
224 return;
225 }
226
227 QV4::Scoped<QV4::Sequence> self(scope, this);
228 self->arrayReserve(n: size);
229 QV4::ScopedValue v(scope);
230 if (valueMetaType == QMetaType::fromType<QVariant>()) {
231 QVariant var;
232 for (qsizetype i = 0; i < size; ++i) {
233 metaSequence.valueAtIndex(container, index: i, result: &var);
234 v = scope.engine->metaTypeToJS(type: var.metaType(), data: var.constData());
235 self->arraySet(index: i, value: v);
236 }
237 } else {
238 QVariant var(valueMetaType);
239 for (qsizetype i = 0; i < size; ++i) {
240 metaSequence.valueAtIndex(container, index: i, result: var.data());
241 v = scope.engine->metaTypeToJS(type: valueMetaType, data: var.constData());
242 self->arraySet(index: i, value: v);
243 }
244 }
245
246 m_size = size;
247}
248
249Heap::Sequence *Heap::Sequence::detached()
250{
251 const QMetaType listType(m_listType);
252 const QMetaSequence metaSequence(m_metaSequence);
253 if (isStoredInline()) {
254 return internalClass->engine->memoryManager->allocate<QV4::Sequence>(
255 args: listType, args: metaSequence, args&: m_container);
256 }
257
258 QVariant list(listType);
259
260 const QMetaType valueMetaType(m_metaSequence->valueMetaType);
261 QVariant element;
262 void *elementData = createVariantData(type: valueMetaType, variant: &element);
263
264 QV4::Scope scope(internalClass->engine);
265 if (qIsAtMostSizetypeLimit(length: m_size)) {
266 QV4::Scoped<QV4::Sequence> self(scope, this);
267 QV4::ScopedValue v(scope);
268 for (uint i = 0; i < m_size; ++i) {
269 v = self->get(id: PropertyKey::fromArrayIndex(idx: i));
270 ExecutionEngine::metaTypeFromJS(value: v, type: valueMetaType, data: elementData);
271 metaSequence.addValue(container: list.data(), value: elementData);
272 }
273 } else {
274 generateWarning(v4: scope.engine, description: QLatin1String("Index out of range during toVariant()"));
275 }
276
277 return scope.engine->memoryManager->allocate<QV4::Sequence>(
278 args: listType, args: metaSequence, args: list.constData());
279}
280
281void Heap::Sequence::destroy()
282{
283 if (isStoredInline() && m_container)
284 listType().destroy(data: m_container);
285 ReferenceObject::destroy();
286}
287
288void *Heap::Sequence::storagePointer()
289{
290 if (!isStoredInline())
291 return nullptr;
292 if (!m_container)
293 m_container = listType().create();
294 return m_container;
295}
296
297bool Heap::Sequence::setVariant(const QVariant &variant)
298{
299 // Should only happen from readReference(). Therefore we are attached.
300 Q_ASSERT(isStoredInline());
301
302 const QMetaType variantReferenceType = variant.metaType();
303 if (variantReferenceType != listType()) {
304 // This is a stale reference. That is, the property has been
305 // overwritten with a different type in the meantime.
306 // We need to modify this reference to the updated type, if
307 // possible, or return false if it is not a sequence.
308 const QQmlType newType = QQmlMetaType::qmlListType(metaType: variantReferenceType);
309 if (newType.isSequentialContainer()) {
310 if (m_container)
311 listType().destroy(data: m_container);
312 m_listType = newType.qListTypeId().iface();
313 m_metaSequence = newType.listMetaSequence().iface();
314 m_container = listType().create(copy: variant.constData());
315 return true;
316 } else {
317 return false;
318 }
319 }
320 if (m_container) {
321 variantReferenceType.destruct(data: m_container);
322 variantReferenceType.construct(where: m_container, copy: variant.constData());
323 } else {
324 m_container = variantReferenceType.create(copy: variant.constData());
325 }
326 return true;
327}
328QVariant Heap::Sequence::toVariant() const
329{
330 // Should only happen from readReference(). Therefore we are attached.
331 Q_ASSERT(isStoredInline());
332
333 return QVariant(listType(), m_container);
334}
335
336template<typename Action>
337void convertAndDo(const QVariant &item, const QMetaType v, Action action)
338{
339 if (item.metaType() == v) {
340 action(item.constData());
341 } else if (v == QMetaType::fromType<QVariant>()) {
342 action(&item);
343 } else {
344 QVariant converted = item;
345 if (!converted.convert(type: v))
346 converted = QVariant(v);
347 action(converted.constData());
348 }
349}
350
351static void appendInline(Heap::Sequence *p, const QVariant &item)
352{
353 convertAndDo(item, v: p->valueMetaType(), action: [p](const void *data) {
354 p->metaSequence().addValueAtEnd(container: p->storagePointer(), value: data);
355 });
356}
357
358static void appendDefaultConstructedInline(Heap::Sequence *p, qsizetype num)
359{
360 QVariant item;
361 const void *data = createVariantData(type: p->valueMetaType(), variant: &item);
362 const QMetaSequence m = p->metaSequence();
363 void *container = p->storagePointer();
364 for (qsizetype i = 0; i < num; ++i)
365 m.addValueAtEnd(container, value: data);
366}
367
368static void replaceInline(Heap::Sequence *p, qsizetype index, const QVariant &item)
369{
370 convertAndDo(item, v: p->valueMetaType(), action: [p, index](const void *data) {
371 p->metaSequence().setValueAtIndex(container: p->storagePointer(), index, value: data);
372 });
373}
374
375static void removeLastInline(Heap::Sequence *p, qsizetype num)
376{
377 const QMetaSequence m = p->metaSequence();
378 void *container = p->storagePointer();
379
380 if (m.canEraseRangeAtIterator() && m.hasRandomAccessIterator() && num > 1) {
381 void *i = m.end(container);
382 m.advanceIterator(iterator: i, step: -num);
383 void *j = m.end(container);
384 m.eraseRangeAtIterator(container, iterator1: i, iterator2: j);
385 m.destroyIterator(iterator: i);
386 m.destroyIterator(iterator: j);
387 } else {
388 for (int i = 0; i < num; ++i)
389 m.removeValueAtEnd(container);
390 }
391}
392
393bool Heap::Sequence::loadReference()
394{
395 Q_ASSERT(isReference());
396 // If locations are enforced we only read once
397 return enforcesLocation() || QV4::ReferenceObject::readReference(ref: this);
398}
399
400bool Heap::Sequence::storeReference()
401{
402 Q_ASSERT(isReference());
403 return isAttachedToProperty() && QV4::ReferenceObject::writeBack(ref: this);
404}
405
406ReturnedValue Sequence::virtualGet(const Managed *that, PropertyKey id, const Value *receiver, bool *hasProperty)
407{
408 Heap::Sequence *p = static_cast<const Sequence *>(that)->d();
409 if (!p->isStoredInline() || !id.isArrayIndex())
410 return ReferenceObject::virtualGet(m: that, id, receiver, hasProperty);
411
412 const uint arrayIndex = id.asArrayIndex();
413 if (!qIsAtMostSizetypeLimit(length: arrayIndex)) {
414 generateWarning(v4: that->engine(), description: QLatin1String("Index out of range during indexed get"));
415 return false;
416 }
417
418 if (p->isReference() && !p->loadReference())
419 return Encode::undefined();
420
421 const qsizetype index = arrayIndex;
422 if (index < sizeInline(p)) {
423 if (hasProperty)
424 *hasProperty = true;
425 return doGetIndexed(p, index);
426 }
427
428 if (hasProperty)
429 *hasProperty = false;
430 return Encode::undefined();
431}
432
433qint64 Sequence::virtualGetLength(const Managed *m)
434{
435 Heap::Sequence *p = static_cast<const Sequence *>(m)->d();
436 if (!p->isStoredInline())
437 return p->m_size;
438 if (p->isReference() && !p->loadReference())
439 return 0;
440 return sizeInline(p);
441}
442
443bool Sequence::virtualPut(Managed *that, PropertyKey id, const Value &value, Value *receiver)
444{
445 if (!id.isArrayIndex())
446 return ReferenceObject::virtualPut(m: that, id, value, receiver);
447
448 const uint arrayIndex = id.asArrayIndex();
449 Sequence *s = static_cast<Sequence *>(that);
450 Heap::Sequence *p = s->d();
451
452 if (!p->isStoredInline()) {
453 s->arraySet(index: arrayIndex, value);
454 if (p->m_size <= arrayIndex)
455 p->m_size = arrayIndex + 1;
456 return true;
457 }
458
459 if (!qIsAtMostSizetypeLimit(length: arrayIndex)) {
460 generateWarning(v4: that->engine(), description: QLatin1String("Index out of range during indexed set"));
461 return false;
462 }
463
464 if (p->internalClass->engine->hasException)
465 return false;
466
467 if (p->isReadOnly()) {
468 p->internalClass->engine->throwTypeError(
469 message: QLatin1String("Cannot insert into a readonly container"));
470 return false;
471 }
472
473 if (p->isReference() && !p->loadReference())
474 return false;
475
476 const qsizetype index = arrayIndex;
477 const qsizetype count = sizeInline(p);
478 const QMetaType valueType = p->valueMetaType();
479 const QVariant element = ExecutionEngine::toVariant(value, typeHint: valueType, createJSValueForObjectsAndSymbols: false);
480
481 if (index == count) {
482 appendInline(p, item: element);
483 } else if (index < count) {
484 replaceInline(p, index, item: element);
485 } else {
486 /* according to ECMA262r3 we need to insert */
487 /* the value at the given index, increasing length to index+1. */
488 appendDefaultConstructedInline(p, num: index - count);
489 appendInline(p, item: element);
490 }
491
492 if (p->isReference())
493 p->storeReference();
494 return true;
495}
496
497bool Sequence::virtualDeleteProperty(Managed *that, PropertyKey id)
498{
499 Heap::Sequence *p = static_cast<const Sequence *>(that)->d();
500 if (!p->isStoredInline() || !id.isArrayIndex())
501 return ReferenceObject::virtualDeleteProperty(m: that, id);
502
503 const uint arrayIndex = id.asArrayIndex();
504 if (!qIsAtMostSizetypeLimit(length: arrayIndex)) {
505 generateWarning(v4: that->engine(), description: QLatin1String("Index out of range during indexed delete"));
506 return false;
507 }
508
509 if (p->isReadOnly())
510 return false;
511 if (p->isReference() && !p->loadReference())
512 return false;
513
514 const qsizetype index = arrayIndex;
515 if (index >= sizeInline(p))
516 return false;
517
518 /* according to ECMA262r3 it should be Undefined, */
519 /* but we cannot, so we insert a default-value instead. */
520 replaceInline(p, index, item: QVariant());
521
522 if (p->isReference())
523 p->storeReference();
524
525 return true;
526}
527
528bool Sequence::virtualIsEqualTo(Managed *that, Managed *other)
529{
530 if (!other)
531 return false;
532
533 const Sequence *otherS = other->as<Sequence>();
534 if (!otherS)
535 return false;
536
537 const Sequence *s = static_cast<Sequence *>(that);
538 const Heap::Sequence *p = s->d();
539 const Heap::Sequence *otherP = otherS->d();
540
541 const Heap::Object *object = p->object();
542 const Heap::Object *otherObject = otherP->object();
543
544 if (object && otherObject)
545 return object == otherObject && p->property() == otherP->property();
546
547 if (!object && !otherObject)
548 return s == otherS;
549
550 return false;
551}
552
553OwnPropertyKeyIterator *Sequence::virtualOwnPropertyKeys(const Object *m, Value *target)
554{
555 Heap::Sequence *p = static_cast<const Sequence *>(m)->d();
556 if (!p->isStoredInline())
557 return ReferenceObject::virtualOwnPropertyKeys(m, target);
558
559 *target = *m;
560 return new SequenceOwnPropertyKeyIterator;
561}
562
563int Sequence::virtualMetacall(Object *object, QMetaObject::Call call, int index, void **a)
564{
565 Heap::Sequence *p = static_cast<Sequence *>(object)->d();
566 Q_ASSERT(p);
567
568 // We only create attached wrappers if this sequence is stored inline.
569 // When detaching, we re-create everything. Therefore, we can't get a metaCall if
570 // we aren't stored inline.
571 Q_ASSERT(p->isStoredInline());
572
573 switch (call) {
574 case QMetaObject::ReadProperty: {
575 const QMetaType valueType = p->valueMetaType();
576 if (p->isReference() && !p->loadReference())
577 return 0;
578 const QMetaSequence metaSequence = p->metaSequence();
579 if (metaSequence.valueMetaType() != valueType)
580 return 0; // value metatype is not what the caller expects anymore.
581
582 const void *storagePointer = p->storagePointer();
583 if (index < 0 || index >= metaSequence.size(container: storagePointer))
584 return 0;
585 metaSequence.valueAtIndex(container: storagePointer, index, result: a[0]);
586 break;
587 }
588 case QMetaObject::WriteProperty: {
589 void *storagePointer = p->storagePointer();
590 const QMetaSequence metaSequence = p->metaSequence();
591 if (index < 0 || index >= metaSequence.size(container: storagePointer))
592 return 0;
593 metaSequence.setValueAtIndex(container: storagePointer, index, value: a[0]);
594 if (p->isReference())
595 p->storeReference();
596 break;
597 }
598 default:
599 return 0; // not supported
600 }
601
602 return -1;
603}
604
605QV4::ReturnedValue SequencePrototype::method_getLength(
606 const FunctionObject *b, const Value *thisObject, const Value *, int)
607{
608 QV4::Scope scope(b);
609 QV4::Scoped<Sequence> This(scope, thisObject->as<Sequence>());
610 if (!This)
611 THROW_TYPE_ERROR();
612
613 Heap::Sequence *p = This->d();
614 if (!p->isStoredInline())
615 RETURN_RESULT(p->m_size);
616
617 if (p->isReference() && !p->loadReference())
618 return Encode::undefined();
619
620 const qsizetype size = sizeInline(p);
621 if (qIsAtMostUintLimit(length: size))
622 RETURN_RESULT(Encode(uint(size)));
623
624 return scope.engine->throwRangeError(message: QLatin1String("Sequence length out of range"));
625}
626
627QV4::ReturnedValue SequencePrototype::method_setLength(
628 const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
629{
630 QV4::Scope scope(f);
631 QV4::Scoped<Sequence> This(scope, thisObject->as<Sequence>());
632 if (!This)
633 THROW_TYPE_ERROR();
634
635 Heap::Sequence *p = This->d();
636
637 bool ok = false;
638 const quint32 argv0 = argc ? argv[0].asArrayLength(ok: &ok) : 0;
639 if (!ok) {
640 generateWarning(v4: scope.engine, description: QLatin1String("Index out of range during length set"));
641 RETURN_UNDEFINED();
642 }
643
644 if (!p->isStoredInline()) {
645 if (argv0 < p->m_size)
646 p->arrayData->vtable()->truncate(This, argv0);
647 p->m_size = argv0;
648 RETURN_UNDEFINED();
649 }
650
651 if (!qIsAtMostSizetypeLimit(length: argv0)) {
652 generateWarning(v4: scope.engine, description: QLatin1String("Sequence length out of range"));
653 RETURN_UNDEFINED();
654 }
655
656 if (p->isReadOnly())
657 THROW_TYPE_ERROR();
658
659 const qsizetype newCount = qsizetype(argv0);
660
661 /* Read the sequence from the QObject property if we're a reference */
662 if (p->isReference() && !p->loadReference())
663 RETURN_UNDEFINED();
664
665 /* Determine whether we need to modify the sequence */
666 const qsizetype count = sizeInline(p);
667 if (newCount == count) {
668 RETURN_UNDEFINED();
669 } else if (newCount > count) {
670 /* according to ECMA262r3 we need to insert */
671 /* undefined values increasing length to newLength. */
672 /* We cannot, so we insert default-values instead. */
673 appendDefaultConstructedInline(p, num: newCount - count);
674 } else {
675 /* according to ECMA262r3 we need to remove */
676 /* elements until the sequence is the required length. */
677 Q_ASSERT(newCount < count);
678 removeLastInline(p, num: count - newCount);
679 }
680
681 /* write back if required. */
682 if (p->isReference())
683 p->storeReference();
684
685 RETURN_UNDEFINED();
686}
687
688void SequencePrototype::init()
689{
690 defineDefaultProperty(name: engine()->id_valueOf(), code: method_valueOf, argumentCount: 0);
691 defineAccessorProperty(QStringLiteral("length"), getter: method_getLength, setter: method_setLength);
692 defineDefaultProperty(QStringLiteral("shift"), code: method_shift, argumentCount: 0);
693}
694
695ReturnedValue SequencePrototype::method_valueOf(const FunctionObject *f, const Value *thisObject, const Value *, int)
696{
697 return Encode(thisObject->toString(e: f->engine()));
698}
699
700ReturnedValue SequencePrototype::method_shift(
701 const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
702{
703 Scope scope(b);
704 Scoped<Sequence> s(scope, thisObject);
705 if (!s)
706 return ArrayPrototype::method_shift(b, thisObject, argv, argc);
707
708 Heap::Sequence *p = s->d();
709
710 if (!p->isStoredInline())
711 return ArrayPrototype::method_shift(b, thisObject, argv, argc);
712
713 if (p->isReference() && !p->loadReference())
714 RETURN_UNDEFINED();
715
716 const qsizetype len = sizeInline(p);
717 if (!len)
718 RETURN_UNDEFINED();
719
720 void *storage = p->storagePointer();
721 Q_ASSERT(storage); // Must readReference() before
722 const QMetaType v = p->valueMetaType();
723 const QMetaSequence m = p->metaSequence();
724
725 QVariant shifted;
726 void *resultData = createVariantData(type: v, variant: &shifted);
727 m.valueAtIndex(container: storage, index: 0, result: resultData);
728
729 if (m.canRemoveValueAtBegin()) {
730 m.removeValueAtBegin(container: storage);
731 } else {
732 QVariant t;
733 void *tData = createVariantData(type: v, variant: &t);
734 for (qsizetype i = 1, end = m.size(container: storage); i < end; ++i) {
735 m.valueAtIndex(container: storage, index: i, result: tData);
736 m.setValueAtIndex(container: storage, index: i - 1, value: tData);
737 }
738 m.removeValueAtEnd(container: storage);
739 }
740
741 if (p->isReference())
742 p->storeReference();
743
744 return scope.engine->fromVariant(shifted);
745}
746
747ReturnedValue SequencePrototype::newSequence(
748 QV4::ExecutionEngine *engine, QMetaType type, QMetaSequence metaSequence, const void *data,
749 Heap::Object *object, int propertyIndex, Heap::ReferenceObject::Flags flags)
750{
751 // This function is called when the property is a QObject Q_PROPERTY of
752 // the given sequence type. Internally we store a sequence
753 // (as well as object ptr + property index for updated-read and write-back)
754 // and so access/mutate avoids variant conversion.
755
756 return engine->memoryManager->allocate<Sequence>(
757 args&: type, args&: metaSequence, args&: data, args&: object, args&: propertyIndex, args&: flags)->asReturnedValue();
758}
759
760ReturnedValue SequencePrototype::fromVariant(QV4::ExecutionEngine *engine, const QVariant &v)
761{
762 const QMetaType type = v.metaType();
763 const QQmlType qmlType = QQmlMetaType::qmlListType(metaType: type);
764 if (qmlType.isSequentialContainer())
765 return fromData(engine, type, metaSequence: qmlType.listMetaSequence(), data: v.constData());
766
767 QSequentialIterable iterable;
768 if (QMetaType::convert(
769 fromType: type, from: v.constData(), toType: QMetaType::fromType<QSequentialIterable>(), to: &iterable)) {
770 return fromData(engine, type, metaSequence: iterable.metaContainer(), data: v.constData());
771 }
772
773 return Encode::undefined();
774}
775
776ReturnedValue SequencePrototype::fromData(
777 ExecutionEngine *engine, QMetaType type, QMetaSequence metaSequence, const void *data)
778{
779 // This function is called when assigning a sequence value to a normal JS var
780 // in a JS block. Internally, we store a sequence of the specified type.
781 // Access and mutation is extremely fast since it will not need to modify any
782 // QObject property.
783
784 return engine->memoryManager->allocate<Sequence>(args&: type, args&: metaSequence, args&: data)->asReturnedValue();
785}
786
787/*!
788 * \internal
789 *
790 * Produce a QVariant containing a copy of the list stored in \a object.
791 */
792QVariant SequencePrototype::toVariant(const Sequence *object)
793{
794 Q_ASSERT(object->isV4SequenceType());
795 Heap::Sequence *p = object->d();
796
797 if (p->isStoredInline()) {
798 // Note: For historical reasons, we ignore the result of loadReference()
799 // here. This allows us to retain sequences whose objects have vaninshed
800 // as "var" properties. It comes at the price of potentially returning
801 // outdated data. This is the behavior sequences have always shown.
802 if (p->isReference())
803 p->loadReference();
804
805 if (const void *storage = p->m_container)
806 return QVariant(p->listType(), storage);
807
808 return QVariant();
809 }
810
811 QV4::Scope scope(p->internalClass->engine);
812 QV4::ScopedObject q(scope, p);
813 return toVariant(array: q, targetType: p->listType());
814}
815
816bool convertToIterable(QMetaType metaType, void *data, QV4::Object *sequence)
817{
818 QSequentialIterable iterable;
819 if (!QMetaType::view(fromType: metaType, from: data, toType: QMetaType::fromType<QSequentialIterable>(), to: &iterable))
820 return false;
821
822 const QMetaType elementMetaType = iterable.valueMetaType();
823 QV4::Scope scope(sequence->engine());
824 QV4::ScopedValue v(scope);
825 for (qsizetype i = 0, end = sequence->getLength(); i < end; ++i) {
826 QVariant element(elementMetaType);
827 v = sequence->get(idx: i);
828 ExecutionEngine::metaTypeFromJS(value: v, type: elementMetaType, data: element.data());
829 iterable.addValue(value: element, position: QSequentialIterable::AtEnd);
830 }
831 return true;
832}
833
834/*!
835 * \internal
836 *
837 * Convert the Object \a array to \a targetType. If \a targetType is not a sequential container,
838 * or if \a array is not an Object, return an invalid QVariant. Otherwise return a QVariant of
839 * \a targetType with the converted data. It is assumed that this actual requires a conversion. If
840 * the conversion is not needed, the single-argument toVariant() is faster.
841 */
842QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType targetType)
843{
844 if (!array.as<Object>())
845 return QVariant();
846
847 QMetaSequence meta;
848 QVariant result(targetType);
849 if (const QQmlType type = QQmlMetaType::qmlListType(metaType: targetType);
850 type.isSequentialContainer()) {
851 // If the QML type declares a custom sequential container, use that.
852 meta = type.priv()->extraData.sequentialContainerTypeData;
853 } else if (QSequentialIterable iterable;
854 QMetaType::view(fromType: targetType, from: result.data(), toType: QMetaType::fromType<QSequentialIterable>(),
855 to: &iterable)) {
856 // Otherwise try to convert to QSequentialIterable via QMetaType conversion.
857 meta = iterable.metaContainer();
858 }
859
860 if (!meta.canAddValue())
861 return QVariant();
862
863 QV4::Scope scope(array.as<Object>()->engine());
864 QV4::ScopedObject a(scope, array);
865
866 const QMetaType valueMetaType = meta.valueMetaType();
867 const qint64 length = a->getLength();
868 Q_ASSERT(length >= 0);
869 Q_ASSERT(length <= qint64(std::numeric_limits<quint32>::max()));
870
871 QV4::ScopedValue v(scope);
872 for (quint32 i = 0; i < quint32(length); ++i) {
873 QVariant variant;
874 QV4::ScopedValue element(scope, a->get(idx: i));
875
876 // Note: We can convert to any sequence type here, even those that don't have a specified
877 // order. Therefore the meta.addValue() below. meta.addValue() preferably adds to the
878 // end, but will also add in other places if that's not possible.
879
880 // If the target type is QVariant itself, let the conversion produce any interanl type.
881 if (valueMetaType == QMetaType::fromType<QVariant>()) {
882 variant = ExecutionEngine::toVariant(value: element, typeHint: QMetaType(), createJSValueForObjectsAndSymbols: false);
883 meta.addValue(container: result.data(), value: &variant);
884 continue;
885 }
886
887 // Try to convert to the specific type requested ...
888 variant = QVariant(valueMetaType);
889 if (ExecutionEngine::metaTypeFromJS(value: element, type: valueMetaType, data: variant.data())) {
890 meta.addValue(container: result.data(), value: variant.constData());
891 continue;
892 }
893
894 // If that doesn't work, produce any QVariant and try to convert it afterwards.
895 // toVariant() is free to ignore the type hint and can produce the "original" type.
896 variant = ExecutionEngine::toVariant(value: element, typeHint: valueMetaType, createJSValueForObjectsAndSymbols: false);
897 const QMetaType originalType = variant.metaType();
898
899 // Try value type constructors.
900 const QVariant converted = QQmlValueTypeProvider::createValueType(
901 variant, valueMetaType, scope.engine);
902 if (converted.isValid()) {
903 meta.addValue(container: result.data(), value: converted.constData());
904 continue;
905 }
906
907 const auto warn = [&](QLatin1String base) {
908 // If the original type was void, we're converting a "hole" in a sparse
909 // array. There is no point in warning about that.
910 if (!originalType.isValid())
911 return;
912
913 qCWarning(lcListValueConversion).noquote()
914 << base.arg(args: QString::number(i), args: QString::fromUtf8(utf8: originalType.name()),
915 args: QString::fromUtf8(utf8: valueMetaType.name()));
916 };
917
918 // Note: Ideally you should use constructible value types for everything below, but we'd
919 // probably get a lot of pushback for warning about that.
920
921 // Before attempting a conversion from the concrete types, check if there exists a
922 // conversion from QJSValue to the result type. Prefer that one for compatibility reasons.
923 // This is a rather surprising "feature". Therefore, warn if a concrete conversion wouldn't
924 // be possible. You should at least make your type conversions consistent.
925 if (QMetaType::canConvert(fromType: QMetaType::fromType<QJSValue>(), toType: valueMetaType)) {
926 QVariant wrappedJsValue = QVariant::fromValue(value: QJSValuePrivate::fromReturnedValue(
927 d: element->asReturnedValue()));
928 if (wrappedJsValue.convert(type: valueMetaType)) {
929 if (!QMetaType::canConvert(fromType: originalType, toType: valueMetaType)) {
930 warn(QLatin1String("Converting array value at position %1 from %2 to %3 via "
931 "QJSValue even though they are not directly convertible"));
932 }
933 meta.addValue(container: result.data(), value: wrappedJsValue.constData());
934 continue;
935 }
936 }
937
938 // Last ditch effort: Try QVariant::convert()
939 if (!variant.convert(type: valueMetaType))
940 warn(QLatin1String("Could not convert array value at position %1 from %2 to %3"));
941
942 meta.addValue(container: result.data(), value: variant.constData());
943 }
944 return result;
945
946}
947
948static Heap::Sequence *resolveHeapSequence(const Sequence *sequence, QMetaType typeHint)
949{
950 if (!sequence)
951 return nullptr;
952 Heap::Sequence *p = sequence->d();
953 if (p->listType() != typeHint)
954 return nullptr;
955 return p;
956}
957
958void *SequencePrototype::rawContainerPtr(const Sequence *sequence, QMetaType typeHint)
959{
960 Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
961 if (!p || !p->isStoredInline())
962 return nullptr;
963
964 return p->storagePointer();
965}
966
967SequencePrototype::RawCopyResult SequencePrototype::setRawContainer(
968 Sequence *sequence, const void *container, QMetaType typeHint)
969{
970 Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
971 if (!p)
972 return TypeMismatch;
973
974 if (p->isStoredInline()) {
975 if (typeHint.equals(lhs: p->m_container, rhs: container))
976 return WasEqual;
977 typeHint.destruct(data: p->m_container);
978 typeHint.construct(where: p->storagePointer(), copy: container);
979 return Copied;
980 }
981
982 p->createElementWrappers(container);
983 return Copied;
984}
985
986SequencePrototype::RawCopyResult SequencePrototype::getRawContainer(
987 const Sequence *sequence, void *container, QMetaType typeHint)
988{
989 Heap::Sequence *p = resolveHeapSequence(sequence, typeHint);
990 if (!p)
991 return TypeMismatch;
992
993 if (p->isStoredInline()) {
994 if (typeHint.equals(lhs: p->m_container, rhs: container))
995 return WasEqual;
996 typeHint.destruct(data: container);
997 typeHint.construct(where: container, copy: p->m_container);
998 return Copied;
999 }
1000
1001 typeHint.destruct(data: container);
1002 typeHint.construct(where: container);
1003
1004 const QMetaSequence metaSequence = p->metaSequence();
1005 QV4::Scope scope(p->internalClass->engine);
1006 QV4::ScopedValue val(scope);
1007 const QMetaType metaType = p->valueMetaType();
1008 const qsizetype size = p->m_size;
1009
1010 Q_ASSERT(qIsAtMostUintLimit(size));
1011
1012 if (metaType == QMetaType::fromType<QVariant>()) {
1013 for (qsizetype i = 0; i < size; ++i) {
1014 val = sequence->get(id: PropertyKey::fromArrayIndex(idx: i));
1015 QVariant var = ExecutionEngine::toVariant(value: val, typeHint: QMetaType(), createJSValueForObjectsAndSymbols: false);
1016 metaSequence.addValueAtEnd(container, value: &var);
1017 }
1018 return Copied;
1019 }
1020
1021 QVariant var(metaType);
1022 for (qsizetype i = 0; i < size; ++i) {
1023 val = sequence->get(id: PropertyKey::fromArrayIndex(idx: i));
1024 scope.engine->metaTypeFromJS(value: val, type: metaType, data: var.data());
1025 metaSequence.addValueAtEnd(container, value: var.data());
1026 }
1027 return Copied;
1028}
1029
1030QMetaType SequencePrototype::metaTypeForSequence(const Sequence *object)
1031{
1032 return object->d()->listType();
1033}
1034
1035} // namespace QV4
1036
1037QT_END_NAMESPACE
1038
1039#include "moc_qv4sequenceobject_p.cpp"
1040

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