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