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