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 "qv4object_p.h" |
5 | |
6 | #include <private/qv4argumentsobject_p.h> |
7 | #include <private/qv4identifiertable_p.h> |
8 | #include <private/qv4jscall_p.h> |
9 | #include <private/qv4lookup_p.h> |
10 | #include <private/qv4memberdata_p.h> |
11 | #include <private/qv4mm_p.h> |
12 | #include <private/qv4proxy_p.h> |
13 | #include <private/qv4scopedvalue_p.h> |
14 | #include <private/qv4stackframe_p.h> |
15 | #include <private/qv4stringobject_p.h> |
16 | #include <private/qv4symbol_p.h> |
17 | |
18 | #include <QtCore/qloggingcategory.h> |
19 | |
20 | #include <stdint.h> |
21 | |
22 | using namespace QV4; |
23 | using namespace Qt::Literals::StringLiterals; |
24 | |
25 | Q_LOGGING_CATEGORY(lcJavaScriptGlobals, "qt.qml.js.globals") |
26 | |
27 | DEFINE_OBJECT_VTABLE(Object); |
28 | |
29 | void Object::setInternalClass(Heap::InternalClass *ic) |
30 | { |
31 | Q_ASSERT(ic && ic->vtable); |
32 | Heap::Object *p = d(); |
33 | |
34 | if (ic->numRedundantTransitions < p->internalClass.get()->numRedundantTransitions) { |
35 | // IC was rebuilt. The indices are different now. We need to move everything. |
36 | |
37 | Scope scope(engine()); |
38 | |
39 | // We allocate before setting the new IC. Protect it from GC. |
40 | Scoped<InternalClass> newIC(scope, ic); |
41 | |
42 | // Pick the members of the old IC that are still valid in the new IC. |
43 | // Order them by index in memberData (or inline data). |
44 | Scoped<MemberData> newMembers(scope, MemberData::allocate(e: scope.engine, n: ic->size)); |
45 | for (uint i = 0; i < ic->size; ++i) { |
46 | // Note that some members might have been deleted. The key may be invalid. |
47 | const PropertyKey key = ic->nameMap.at(i); |
48 | // fromReturnedValue is safe, we directly write it through the WriteBarrier |
49 | newMembers->set(e: scope.engine, index: i, |
50 | v: QV4::Value::fromReturnedValue(val: key.isValid() ? get(id: key) : Encode::undefined())); |
51 | } |
52 | |
53 | p->internalClass.set(e: scope.engine, newVal: ic); |
54 | const uint nInline = p->vtable()->nInlineProperties; |
55 | |
56 | if (ic->size > nInline) |
57 | p->memberData.set(e: scope.engine, newVal: MemberData::allocate(e: ic->engine, n: ic->size - nInline)); |
58 | else |
59 | p->memberData.set(e: scope.engine, newVal: nullptr); |
60 | |
61 | const auto &memberValues = newMembers->d()->values; |
62 | for (uint i = 0; i < ic->size; ++i) |
63 | setProperty(index: i, v: memberValues[i]); |
64 | } else { |
65 | // The old indices are still the same. No need to move any values. |
66 | // We may need to re-allocate, though. |
67 | |
68 | p->internalClass.set(e: ic->engine, newVal: ic); |
69 | const uint nInline = p->vtable()->nInlineProperties; |
70 | if (ic->size > nInline) { |
71 | const uint requiredSize = ic->size - nInline; |
72 | if ((p->memberData ? p->memberData->values.size : 0) < requiredSize) { |
73 | p->memberData.set(e: ic->engine, newVal: MemberData::allocate( |
74 | e: ic->engine, n: requiredSize, old: p->memberData)); |
75 | } |
76 | } |
77 | } |
78 | |
79 | // Before the engine is done initializing, we cannot have any lookups. |
80 | // Therefore, there is no point in updating the proto IDs. |
81 | if (ic->engine->isInitialized && ic->isUsedAsProto()) |
82 | ic->updateProtoUsage(o: p); |
83 | |
84 | } |
85 | |
86 | void Object::getProperty(const InternalClassEntry &entry, Property *p) const |
87 | { |
88 | p->value = *propertyData(index: entry.index); |
89 | if (entry.attributes.isAccessor()) |
90 | p->set = *propertyData(index: entry.setterIndex); |
91 | } |
92 | |
93 | void Object::setProperty(const InternalClassEntry &entry, const Property *p) |
94 | { |
95 | setProperty(index: entry.index, v: p->value); |
96 | if (entry.attributes.isAccessor()) |
97 | setProperty(index: entry.setterIndex, v: p->set); |
98 | } |
99 | |
100 | void Heap::Object::setUsedAsProto() |
101 | { |
102 | internalClass.set(e: internalClass->engine, newVal: internalClass->asProtoClass()); |
103 | } |
104 | |
105 | ReturnedValue Object::getValueAccessor(const Value *thisObject, const Value &v, PropertyAttributes attrs) |
106 | { |
107 | if (!attrs.isAccessor()) |
108 | return v.asReturnedValue(); |
109 | const QV4::FunctionObject *f = v.as<FunctionObject>(); |
110 | if (!f) |
111 | return Encode::undefined(); |
112 | |
113 | Scope scope(f->engine()); |
114 | JSCallArguments jsCallData(scope); |
115 | if (thisObject) |
116 | *jsCallData.thisObject = *thisObject; |
117 | return checkedResult(v4: scope.engine, result: f->call(data: jsCallData)); |
118 | } |
119 | |
120 | bool Object::putValue(uint memberIndex, PropertyAttributes attrs, const Value &value) |
121 | { |
122 | Heap::InternalClass *ic = internalClass(); |
123 | if (ic->engine->hasException) |
124 | return false; |
125 | |
126 | if (attrs.isAccessor()) { |
127 | const FunctionObject *set = propertyData(index: memberIndex)->as<FunctionObject>(); |
128 | if (set) { |
129 | Scope scope(ic->engine); |
130 | ScopedFunctionObject setter(scope, set); |
131 | JSCallArguments jsCallData(scope, 1); |
132 | jsCallData.args[0] = value; |
133 | *jsCallData.thisObject = this; |
134 | setter->call(data: jsCallData); |
135 | return !ic->engine->hasException; |
136 | } |
137 | return false; |
138 | } |
139 | |
140 | if (!attrs.isWritable()) |
141 | return false; |
142 | |
143 | setProperty(index: memberIndex, v: value); |
144 | return true; |
145 | } |
146 | |
147 | void Object::defineDefaultProperty(const QString &name, const Value &value, PropertyAttributes attributes) |
148 | { |
149 | ExecutionEngine *e = engine(); |
150 | Scope scope(e); |
151 | ScopedString s(scope, e->newIdentifier(text: name)); |
152 | defineDefaultProperty(name: s, value, attributes); |
153 | } |
154 | |
155 | void Object::defineDefaultProperty(const QString &name, VTable::Call code, |
156 | int argumentCount, PropertyAttributes attributes) |
157 | { |
158 | ExecutionEngine *e = engine(); |
159 | Scope scope(e); |
160 | ScopedString s(scope, e->newIdentifier(text: name)); |
161 | ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(engine: e, nameOrSymbol: s, code, argumentCount)); |
162 | defineDefaultProperty(name: s, value: function, attributes); |
163 | } |
164 | |
165 | void Object::defineDefaultProperty(StringOrSymbol *nameOrSymbol, VTable::Call code, |
166 | int argumentCount, PropertyAttributes attributes) |
167 | { |
168 | ExecutionEngine *e = engine(); |
169 | Scope scope(e); |
170 | ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(engine: e, nameOrSymbol, code, argumentCount)); |
171 | defineDefaultProperty(name: nameOrSymbol, value: function, attributes); |
172 | } |
173 | |
174 | void Object::defineAccessorProperty(const QString &name, VTable::Call getter, VTable::Call setter) |
175 | { |
176 | ExecutionEngine *e = engine(); |
177 | Scope scope(e); |
178 | ScopedString s(scope, e->newIdentifier(text: name)); |
179 | defineAccessorProperty(name: s, getter, setter); |
180 | } |
181 | |
182 | void Object::defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, VTable::Call setter) |
183 | { |
184 | ExecutionEngine *v4 = engine(); |
185 | QV4::Scope scope(v4); |
186 | ScopedProperty p(scope); |
187 | QString n = name->toQString(); |
188 | if (!n.isEmpty() && n.at(i: 0) == '@'_L1) |
189 | n = '['_L1 + QStringView{n}.mid(pos: 1) + ']'_L1; |
190 | if (getter) { |
191 | ScopedString getName(scope, v4->newString(s: "get "_L1+ n)); |
192 | p->setGetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(engine: v4, nameOrSymbol: getName, code: getter, argumentCount: 0))); |
193 | } else { |
194 | p->setGetter(nullptr); |
195 | } |
196 | if (setter) { |
197 | ScopedString setName(scope, v4->newString(s: "set "_L1+ n)); |
198 | p->setSetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(engine: v4, nameOrSymbol: setName, code: setter, argumentCount: 0))); |
199 | } else { |
200 | p->setSetter(nullptr); |
201 | } |
202 | insertMember(s: name, p, attributes: QV4::Attr_Accessor|QV4::Attr_NotEnumerable); |
203 | } |
204 | |
205 | |
206 | |
207 | void Object::defineReadonlyProperty(const QString &name, const Value &value) |
208 | { |
209 | QV4::ExecutionEngine *e = engine(); |
210 | Scope scope(e); |
211 | ScopedString s(scope, e->newIdentifier(text: name)); |
212 | defineReadonlyProperty(name: s, value); |
213 | } |
214 | |
215 | void Object::defineReadonlyProperty(String *name, const Value &value) |
216 | { |
217 | insertMember(s: name, v: value, attributes: Attr_ReadOnly); |
218 | } |
219 | |
220 | void Object::defineReadonlyConfigurableProperty(const QString &name, const Value &value) |
221 | { |
222 | QV4::ExecutionEngine *e = engine(); |
223 | Scope scope(e); |
224 | ScopedString s(scope, e->newIdentifier(text: name)); |
225 | defineReadonlyConfigurableProperty(name: s, value); |
226 | } |
227 | |
228 | void Object::defineReadonlyConfigurableProperty(StringOrSymbol *name, const Value &value) |
229 | { |
230 | insertMember(s: name, v: value, attributes: Attr_ReadOnly_ButConfigurable); |
231 | } |
232 | |
233 | void Object::addSymbolSpecies() |
234 | { |
235 | Scope scope(engine()); |
236 | ScopedProperty p(scope); |
237 | p->setGetter(scope.engine->getSymbolSpecies()); |
238 | p->setSetter(nullptr); |
239 | insertMember(s: scope.engine->symbol_species(), p, attributes: QV4::Attr_Accessor|QV4::Attr_NotWritable|QV4::Attr_NotEnumerable); |
240 | } |
241 | |
242 | void Heap::Object::markObjects(Heap::Base *b, MarkStack *stack) |
243 | { |
244 | Base::markObjects(b, stack); |
245 | Object *o = static_cast<Object *>(b); |
246 | if (o->memberData) |
247 | o->memberData->mark(markStack: stack); |
248 | if (o->arrayData) |
249 | o->arrayData->mark(markStack: stack); |
250 | uint nInline = o->vtable()->nInlineProperties; |
251 | Value *v = reinterpret_cast<Value *>(o) + o->vtable()->inlinePropertyOffset; |
252 | const Value *end = v + nInline; |
253 | while (v < end) { |
254 | v->mark(markStack: stack); |
255 | ++v; |
256 | } |
257 | } |
258 | |
259 | void Object::insertMember(StringOrSymbol *s, const Property *p, PropertyAttributes attributes) |
260 | { |
261 | InternalClassEntry idx; |
262 | PropertyKey key = s->toPropertyKey(); |
263 | Heap::InternalClass::addMember(object: this, id: key, data: attributes, entry: &idx); |
264 | |
265 | setProperty(index: idx.index, v: p->value); |
266 | if (attributes.isAccessor()) |
267 | setProperty(index: idx.setterIndex, v: p->set); |
268 | } |
269 | |
270 | void Object::setPrototypeUnchecked(const Object *p) |
271 | { |
272 | setInternalClass(internalClass()->changePrototype(proto: p ? p->d() : nullptr)); |
273 | } |
274 | |
275 | // Section 8.12.2 |
276 | PropertyIndex Object::getValueOrSetter(PropertyKey id, PropertyAttributes *attrs) |
277 | { |
278 | if (id.isArrayIndex()) { |
279 | uint index = id.asArrayIndex(); |
280 | Heap::Object *o = d(); |
281 | while (o) { |
282 | if (o->arrayData) { |
283 | uint idx = o->arrayData->mappedIndex(index); |
284 | if (idx != UINT_MAX) { |
285 | *attrs = o->arrayData->attributes(i: index); |
286 | return { .base: o->arrayData , .slot: o->arrayData->values.values + (attrs->isAccessor() ? idx + SetterOffset : idx) }; |
287 | } |
288 | } |
289 | if (o->vtable()->type == Type_StringObject) { |
290 | if (index < static_cast<const Heap::StringObject *>(o)->length()) { |
291 | // this is an evil hack, but it works, as the method is only ever called from put, |
292 | // where we don't use the returned pointer there for non writable attributes |
293 | *attrs = (Attr_NotWritable|Attr_NotConfigurable); |
294 | return { .base: reinterpret_cast<Heap::ArrayData *>(0x1), .slot: nullptr }; |
295 | } |
296 | } |
297 | o = o->prototype(); |
298 | } |
299 | } else { |
300 | Heap::Object *o = d(); |
301 | while (o) { |
302 | auto idx = o->internalClass->findValueOrSetter(id); |
303 | if (idx.isValid()) { |
304 | *attrs = idx.attrs; |
305 | return o->writablePropertyData(index: idx.index); |
306 | } |
307 | |
308 | o = o->prototype(); |
309 | } |
310 | } |
311 | *attrs = Attr_Invalid; |
312 | return { .base: nullptr, .slot: nullptr }; |
313 | } |
314 | |
315 | ReturnedValue Object::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) |
316 | { |
317 | return static_cast<const Object *>(m)->internalGet(id, receiver, hasProperty); |
318 | } |
319 | |
320 | bool Object::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) |
321 | { |
322 | return static_cast<Object *>(m)->internalPut(id, value, receiver); |
323 | } |
324 | |
325 | bool Object::virtualDeleteProperty(Managed *m, PropertyKey id) |
326 | { |
327 | return static_cast<Object *>(m)->internalDeleteProperty(id); |
328 | } |
329 | |
330 | PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) |
331 | { |
332 | if (arrayIndex != UINT_MAX && o->arrayData()) { |
333 | SparseArrayNode *arrayNode = nullptr; |
334 | if (o->arrayType() == Heap::ArrayData::Sparse) { |
335 | SparseArray *sparse = o->arrayData()->sparse; |
336 | arrayNode = arrayIndex ? sparse->lowerBound(akey: arrayIndex) : sparse->begin(); |
337 | } |
338 | |
339 | // sparse arrays |
340 | if (arrayNode) { |
341 | while (arrayNode != o->sparseEnd()) { |
342 | uint k = arrayNode->key(); |
343 | uint pidx = arrayNode->value; |
344 | Heap::SparseArrayData *sa = o->d()->arrayData.cast<Heap::SparseArrayData>(); |
345 | const Property *p = reinterpret_cast<const Property *>(sa->values.data() + pidx); |
346 | arrayNode = arrayNode->nextNode(); |
347 | PropertyAttributes a = sa->attrs ? sa->attrs[pidx] : Attr_Data; |
348 | arrayIndex = k + 1; |
349 | if (pd) |
350 | pd->copy(other: p, attrs: a); |
351 | if (attrs) |
352 | *attrs = a; |
353 | return PropertyKey::fromArrayIndex(idx: k); |
354 | } |
355 | arrayIndex = UINT_MAX; |
356 | } |
357 | // dense arrays |
358 | while (arrayIndex < o->d()->arrayData->values.size) { |
359 | Heap::SimpleArrayData *sa = o->d()->arrayData.cast<Heap::SimpleArrayData>(); |
360 | const Value &val = sa->data(index: arrayIndex); |
361 | PropertyAttributes a = o->arrayData()->attributes(i: arrayIndex); |
362 | int index = arrayIndex; |
363 | ++arrayIndex; |
364 | if (!val.isEmpty()) { |
365 | if (pd) |
366 | pd->value = val; |
367 | if (attrs) |
368 | *attrs = a; |
369 | return PropertyKey::fromArrayIndex(idx: index); |
370 | } |
371 | } |
372 | arrayIndex = UINT_MAX; |
373 | } |
374 | |
375 | while (true) { |
376 | while (memberIndex < o->internalClass()->size) { |
377 | PropertyKey n = o->internalClass()->nameMap.at(i: memberIndex); |
378 | ++memberIndex; |
379 | if (!n.isStringOrSymbol()) |
380 | // accessor properties have a dummy entry with n == 0 |
381 | continue; |
382 | if (!iterateOverSymbols && n.isSymbol()) |
383 | continue; |
384 | if (iterateOverSymbols && !n.isSymbol()) |
385 | continue; |
386 | |
387 | InternalClassEntry e = o->internalClass()->find(id: n); |
388 | if (!e.isValid()) |
389 | continue; |
390 | if (pd) { |
391 | pd->value = *o->propertyData(index: e.index); |
392 | if (e.attributes.isAccessor()) |
393 | pd->set = *o->propertyData(index: e.setterIndex); |
394 | } |
395 | if (attrs) |
396 | *attrs = e.attributes; |
397 | return n; |
398 | } |
399 | if (iterateOverSymbols) |
400 | break; |
401 | iterateOverSymbols = true; |
402 | memberIndex = 0; |
403 | } |
404 | |
405 | return PropertyKey::invalid(); |
406 | } |
407 | |
408 | OwnPropertyKeyIterator *Object::virtualOwnPropertyKeys(const Object *o, Value *target) |
409 | { |
410 | *target = *o; |
411 | return new ObjectOwnPropertyKeyIterator; |
412 | } |
413 | |
414 | // Section 8.12.3 |
415 | ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *hasProperty) const |
416 | { |
417 | Heap::Object *o = d(); |
418 | |
419 | if (id.isArrayIndex()) { |
420 | const uint index = id.asArrayIndex(); |
421 | Scope scope(this); |
422 | PropertyAttributes attrs; |
423 | ScopedProperty pd(scope); |
424 | while (1) { |
425 | if (o->arrayData && o->arrayData->getProperty(index, p: pd, attrs: &attrs)) { |
426 | if (hasProperty) |
427 | *hasProperty = true; |
428 | return Object::getValue(thisObject: receiver, v: pd->value, attrs); |
429 | } |
430 | if (o->internalClass->vtable->type == Type_StringObject) { |
431 | ScopedString str(scope, static_cast<Heap::StringObject *>(o)->getIndex(index)); |
432 | if (str) { |
433 | attrs = (Attr_NotWritable|Attr_NotConfigurable); |
434 | if (hasProperty) |
435 | *hasProperty = true; |
436 | return str.asReturnedValue(); |
437 | } |
438 | } |
439 | o = o->prototype(); |
440 | if (!o || o->internalClass->vtable->get != Object::virtualGet) |
441 | break; |
442 | } |
443 | } else { |
444 | while (1) { |
445 | auto idx = o->internalClass->findValueOrGetter(id); |
446 | if (idx.isValid()) { |
447 | if (hasProperty) |
448 | *hasProperty = true; |
449 | return Object::getValue(thisObject: receiver, v: *o->propertyData(index: idx.index), attrs: idx.attrs); |
450 | } |
451 | o = o->prototype(); |
452 | if (!o || o->internalClass->vtable->get != Object::virtualGet) |
453 | break; |
454 | } |
455 | } |
456 | |
457 | if (o) { |
458 | const Value v = Value::fromHeapObject(m: o); |
459 | const Object &obj = static_cast<const Object &>(v); |
460 | return obj.get(id, receiver, hasProperty); |
461 | } |
462 | |
463 | if (hasProperty) |
464 | *hasProperty = false; |
465 | return Encode::undefined(); |
466 | } |
467 | |
468 | // Section 8.12.5 |
469 | bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver) |
470 | { |
471 | Scope scope(this); |
472 | if (scope.hasException()) |
473 | return false; |
474 | |
475 | Object *r = receiver->objectValue(); |
476 | if (r && r->d() == d()) { |
477 | // receiver and this object are the same |
478 | if (d()->internalClass->vtable->getOwnProperty == Object::virtualGetOwnProperty) { |
479 | // This object standard methods in the vtable, so we can take a shortcut |
480 | // and avoid the calls to getOwnProperty and defineOwnProperty |
481 | |
482 | PropertyAttributes attrs; |
483 | PropertyIndex propertyIndex{.base: nullptr, .slot: nullptr}; |
484 | |
485 | if (id.isArrayIndex()) { |
486 | if (arrayData()) |
487 | propertyIndex = arrayData()->getValueOrSetter(index: id.asArrayIndex(), attrs: &attrs); |
488 | } else { |
489 | auto member = internalClass()->findValueOrSetter(id); |
490 | if (member.isValid()) { |
491 | attrs = member.attrs; |
492 | propertyIndex = d()->writablePropertyData(index: member.index); |
493 | } |
494 | } |
495 | |
496 | if (!propertyIndex.isNull() && !attrs.isAccessor()) { |
497 | if (!attrs.isWritable()) |
498 | return false; |
499 | else if (isArrayObject() && id == scope.engine->id_length()->propertyKey()) { |
500 | bool ok; |
501 | uint l = value.asArrayLength(ok: &ok); |
502 | if (!ok) { |
503 | scope.engine->throwRangeError(value); |
504 | return false; |
505 | } |
506 | ok = setArrayLength(l); |
507 | if (!ok) |
508 | return false; |
509 | } else { |
510 | propertyIndex.set(e: scope.engine, newVal: value); |
511 | } |
512 | return true; |
513 | } |
514 | } |
515 | } |
516 | |
517 | ScopedProperty p(scope); |
518 | PropertyAttributes attrs; |
519 | attrs = getOwnProperty(id, p); |
520 | if (attrs == Attr_Invalid) { |
521 | ScopedObject p(scope, getPrototypeOf()); |
522 | if (p) |
523 | return p->put(id, v: value, receiver); |
524 | attrs = Attr_Data; |
525 | } |
526 | |
527 | if (attrs.isAccessor()) { |
528 | ScopedFunctionObject setter(scope, p->setter()); |
529 | if (!setter) |
530 | return false; |
531 | JSCallArguments jsCallData(scope, 1); |
532 | jsCallData.args[0] = value; |
533 | *jsCallData.thisObject = *receiver; |
534 | setter->call(data: jsCallData); |
535 | return !scope.hasException(); |
536 | } |
537 | |
538 | // Data property |
539 | if (!attrs.isWritable()) |
540 | return false; |
541 | if (!r) |
542 | return false; |
543 | attrs = r->getOwnProperty(id, p); |
544 | |
545 | if (attrs != Attr_Invalid) { |
546 | if (attrs.isAccessor() || !attrs.isWritable()) |
547 | return false; |
548 | } else { |
549 | if (!r->isExtensible()) |
550 | return false; |
551 | attrs = Attr_Data; |
552 | } |
553 | |
554 | if (r->internalClass()->vtable->defineOwnProperty == virtualDefineOwnProperty) { |
555 | // standard object, we can avoid some more checks |
556 | if (id.isArrayIndex()) { |
557 | r->arraySet(index: id.asArrayIndex(), value); |
558 | } else { |
559 | ScopedStringOrSymbol s(scope, id.asStringOrSymbol()); |
560 | r->insertMember(s, v: value); |
561 | } |
562 | return true; |
563 | } |
564 | |
565 | p->value = value; |
566 | return r->defineOwnProperty(id, p, attrs); |
567 | } |
568 | |
569 | // Section 8.12.7 |
570 | bool Object::internalDeleteProperty(PropertyKey id) |
571 | { |
572 | if (internalClass()->engine->hasException) |
573 | return false; |
574 | |
575 | if (id.isArrayIndex()) { |
576 | uint index = id.asArrayIndex(); |
577 | Scope scope(engine()); |
578 | if (scope.hasException()) |
579 | return false; |
580 | |
581 | Scoped<ArrayData> ad(scope, arrayData()); |
582 | if (!ad || ad->vtable()->del(this, index)) |
583 | return true; |
584 | |
585 | return false; |
586 | } |
587 | |
588 | auto memberIdx = internalClass()->findValueOrGetter(id); |
589 | if (memberIdx.isValid()) { |
590 | if (memberIdx.attrs.isConfigurable()) { |
591 | Heap::InternalClass::removeMember(object: this, identifier: id); |
592 | return true; |
593 | } |
594 | return false; |
595 | } |
596 | |
597 | return true; |
598 | } |
599 | |
600 | bool Object::internalDefineOwnProperty(ExecutionEngine *engine, uint index, const InternalClassEntry *memberEntry, const Property *p, PropertyAttributes attrs) |
601 | { |
602 | // clause 5 |
603 | if (attrs.isEmpty()) |
604 | return true; |
605 | |
606 | Scope scope(engine); |
607 | ScopedProperty current(scope); |
608 | PropertyAttributes cattrs; |
609 | if (memberEntry) { |
610 | getProperty(entry: *memberEntry, p: current); |
611 | cattrs = memberEntry->attributes; |
612 | } else if (arrayData()) { |
613 | arrayData()->getProperty(index, p: current, attrs: &cattrs); |
614 | cattrs = arrayData()->attributes(i: index); |
615 | } |
616 | |
617 | // clause 6 |
618 | if (p->isSubset(attrs, other: current, otherAttrs: cattrs)) |
619 | return true; |
620 | |
621 | // clause 7 |
622 | if (!cattrs.isConfigurable()) { |
623 | if (attrs.isConfigurable()) |
624 | return false; |
625 | if (attrs.hasEnumerable() && attrs.isEnumerable() != cattrs.isEnumerable()) |
626 | return false; |
627 | } |
628 | |
629 | // clause 8 |
630 | if (attrs.isGeneric() || current->value.isEmpty()) |
631 | goto accept; |
632 | |
633 | // clause 9 |
634 | if (cattrs.isData() != attrs.isData()) { |
635 | // 9a |
636 | if (!cattrs.isConfigurable()) |
637 | return false; |
638 | if (cattrs.isData()) { |
639 | // 9b |
640 | cattrs.setType(PropertyAttributes::Accessor); |
641 | cattrs.clearWritable(); |
642 | if (!memberEntry) { |
643 | // need to convert the array and the slot |
644 | initSparseArray(); |
645 | Q_ASSERT(arrayData()); |
646 | setArrayAttributes(i: index, a: cattrs); |
647 | } |
648 | current->setGetter(nullptr); |
649 | current->setSetter(nullptr); |
650 | } else { |
651 | // 9c |
652 | cattrs.setType(PropertyAttributes::Data); |
653 | cattrs.setWritable(false); |
654 | if (!memberEntry) { |
655 | // need to convert the array and the slot |
656 | setArrayAttributes(i: index, a: cattrs); |
657 | } |
658 | current->value = Value::undefinedValue(); |
659 | } |
660 | } else if (cattrs.isData() && attrs.isData()) { // clause 10 |
661 | if (!cattrs.isConfigurable() && !cattrs.isWritable()) { |
662 | if (attrs.isWritable() || !current->value.sameValue(other: p->value)) |
663 | return false; |
664 | } |
665 | } else { // clause 10 |
666 | Q_ASSERT(cattrs.isAccessor() && attrs.isAccessor()); |
667 | if (!cattrs.isConfigurable()) { |
668 | if (!p->value.isEmpty() && current->value.rawValue() != p->value.rawValue()) |
669 | return false; |
670 | if (!p->set.isEmpty() && current->set.rawValue() != p->set.rawValue()) |
671 | return false; |
672 | } |
673 | } |
674 | |
675 | accept: |
676 | |
677 | current->merge(attrs&: cattrs, other: p, otherAttrs: attrs); |
678 | if (memberEntry) { |
679 | PropertyKey key = internalClass()->nameMap.at(i: memberEntry->index); |
680 | InternalClassEntry e; |
681 | Heap::InternalClass::changeMember(object: this, id: key, data: cattrs, entry: &e); |
682 | setProperty(entry: e, p: current); |
683 | } else { |
684 | setArrayAttributes(i: index, a: cattrs); |
685 | arrayData()->setProperty(e: scope.engine, index, p: current); |
686 | } |
687 | return true; |
688 | } |
689 | |
690 | void Object::copyArrayData(Object *other) |
691 | { |
692 | Q_ASSERT(isArrayObject()); |
693 | Scope scope(engine()); |
694 | |
695 | if (other->protoHasArray() || ArgumentsObject::isNonStrictArgumentsObject(m: other) || |
696 | (other->arrayType() == Heap::ArrayData::Sparse && other->arrayData()->attrs)) { |
697 | uint len = other->getLength(); |
698 | Q_ASSERT(len); |
699 | |
700 | ScopedValue v(scope); |
701 | for (uint i = 0; i < len; ++i) { |
702 | arraySet(index: i, value: (v = other->get(idx: i))); |
703 | } |
704 | } else if (!other->arrayData()) { |
705 | ; |
706 | } else { |
707 | Q_ASSERT(!arrayData() && other->arrayData()); |
708 | ArrayData::realloc(o: this, newType: static_cast<ArrayData::Type>(other->d()->arrayData->type), |
709 | alloc: other->d()->arrayData->values.alloc, enforceAttributes: false); |
710 | if (other->arrayType() == Heap::ArrayData::Sparse) { |
711 | Heap::ArrayData *od = other->d()->arrayData; |
712 | Heap::ArrayData *dd = d()->arrayData; |
713 | dd->sparse = new SparseArray(*od->sparse); |
714 | } else { |
715 | Heap::ArrayData *dd = d()->arrayData; |
716 | dd->values.size = other->d()->arrayData->values.size; |
717 | dd->offset = other->d()->arrayData->offset; |
718 | } |
719 | // ### need a write barrier |
720 | memcpy(dest: d()->arrayData->values.values, src: other->d()->arrayData->values.values, n: other->d()->arrayData->values.alloc*sizeof(Value)); |
721 | } |
722 | setArrayLengthUnchecked(other->getLength()); |
723 | } |
724 | |
725 | qint64 Object::virtualGetLength(const Managed *m) |
726 | { |
727 | Scope scope(static_cast<const Object *>(m)->engine()); |
728 | ScopedValue v(scope, static_cast<Object *>(const_cast<Managed *>(m))->get(name: scope.engine->id_length())); |
729 | return v->toLength(); |
730 | } |
731 | |
732 | // 'var' is 'V' in 15.3.5.3. |
733 | ReturnedValue Object::virtualInstanceOf(const Object *typeObject, const Value &var) |
734 | { |
735 | QV4::ExecutionEngine *engine = typeObject->internalClass()->engine; |
736 | |
737 | // 15.3.5.3, Assume F is a Function object. |
738 | const FunctionObject *function = typeObject->as<FunctionObject>(); |
739 | if (!function) |
740 | return engine->throwTypeError(); |
741 | |
742 | return checkedInstanceOf(engine, typeObject: function, var); |
743 | } |
744 | |
745 | ReturnedValue Object::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) |
746 | { |
747 | // Otherwise we cannot trust the protoIds |
748 | Q_ASSERT(engine->isInitialized); |
749 | |
750 | Heap::Object *obj = object->d(); |
751 | PropertyKey name = engine->identifierTable->asPropertyKey(str: engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); |
752 | if (object->as<QV4::ProxyObject>()) { |
753 | // proxies invalidate assumptions that we normally maek in lookups |
754 | // so we always need to use the fallback path |
755 | lookup->getter = Lookup::getterFallback; |
756 | return lookup->getter(lookup, engine, *object); |
757 | } |
758 | if (name.isArrayIndex()) { |
759 | lookup->indexedLookup.index = name.asArrayIndex(); |
760 | lookup->getter = Lookup::getterIndexed; |
761 | return lookup->getter(lookup, engine, *object); |
762 | } |
763 | |
764 | auto index = obj->internalClass->findValueOrGetter(id: name); |
765 | if (index.isValid()) { |
766 | PropertyAttributes attrs = index.attrs; |
767 | uint nInline = obj->vtable()->nInlineProperties; |
768 | if (attrs.isData()) { |
769 | if (index.index < obj->vtable()->nInlineProperties) { |
770 | index.index += obj->vtable()->inlinePropertyOffset; |
771 | lookup->getter = Lookup::getter0Inline; |
772 | } else { |
773 | index.index -= nInline; |
774 | lookup->getter = Lookup::getter0MemberData; |
775 | } |
776 | } else { |
777 | lookup->getter = Lookup::getterAccessor; |
778 | } |
779 | lookup->objectLookup.ic.set(engine, heapObject: obj->internalClass.get()); |
780 | lookup->objectLookup.offset = index.index; |
781 | return lookup->getter(lookup, engine, *object); |
782 | } |
783 | |
784 | lookup->protoLookup.protoId = obj->internalClass->protoId; |
785 | lookup->resolveProtoGetter(name, proto: obj->prototype()); |
786 | return lookup->getter(lookup, engine, *object); |
787 | } |
788 | |
789 | bool Object::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value) |
790 | { |
791 | // Otherwise we cannot trust the protoIds |
792 | Q_ASSERT(engine->isInitialized); |
793 | |
794 | Scope scope(engine); |
795 | ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); |
796 | |
797 | Heap::InternalClass *c = object->internalClass(); |
798 | PropertyKey key = name->toPropertyKey(); |
799 | auto idx = c->findValueOrSetter(id: key); |
800 | if (idx.isValid()) { |
801 | if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) { |
802 | Q_ASSERT(!idx.attrs.isAccessor()); |
803 | lookup->setter = Lookup::arrayLengthSetter; |
804 | return lookup->setter(lookup, engine, *object, value); |
805 | } else if (idx.attrs.isData() && idx.attrs.isWritable()) { |
806 | lookup->objectLookup.ic.set(engine, heapObject: object->internalClass()); |
807 | lookup->objectLookup.index = idx.index; |
808 | const auto nInline = object->d()->vtable()->nInlineProperties; |
809 | if (idx.index < nInline) { |
810 | lookup->setter = Lookup::setter0Inline; |
811 | lookup->objectLookup.offset = idx.index + object->d()->vtable()->inlinePropertyOffset; |
812 | } else { |
813 | lookup->setter = Lookup::setter0MemberData; |
814 | lookup->objectLookup.offset = idx.index - nInline; |
815 | } |
816 | return lookup->setter(lookup, engine, *object, value); |
817 | } else { |
818 | // ### handle setter |
819 | lookup->setter = Lookup::setterFallback; |
820 | } |
821 | return lookup->setter(lookup, engine, *object, value); |
822 | } |
823 | |
824 | lookup->insertionLookup.protoId = c->protoId; |
825 | if (!object->put(id: key, v: value)) { |
826 | lookup->setter = Lookup::setterFallback; |
827 | return false; |
828 | } |
829 | |
830 | if (object->internalClass() == c) { |
831 | // ### setter in the prototype, should handle this |
832 | lookup->setter = Lookup::setterFallback; |
833 | return true; |
834 | } |
835 | idx = object->internalClass()->findValueOrSetter(id: key); |
836 | if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen? |
837 | lookup->setter = Lookup::setterFallback; |
838 | return false; |
839 | } |
840 | lookup->insertionLookup.newClass.set(engine, heapObject: object->internalClass()); |
841 | lookup->insertionLookup.offset = idx.index; |
842 | lookup->setter = Lookup::setterInsert; |
843 | return true; |
844 | } |
845 | |
846 | /*! |
847 | * \internal |
848 | * |
849 | * This method is modeled after \l{QMetaObject::metacall}. It takes a JavaScript |
850 | * \a object and performs \a call on it, using \a index as identifier for the |
851 | * property/method/etc to be used and \a a as arguments. Like |
852 | * \l{QMetaObject::metacall} this method is overly generic in order to reduce the |
853 | * API surface. On a plain Object it does nothing and returns 0. Specific |
854 | * objects can override it and do some meaningful work. If the metacall succeeds |
855 | * they should return a non-0 value. Otherwise they should return 0. |
856 | * |
857 | * Most prominently, \l QMetaObject::ReadProperty and \l QMetaObject::WriteProperty |
858 | * calls can be used to manipulate properties of QObjects and Q_GADGETs wrapped |
859 | * by QV4::QObjectWrapper, QV4::QQmlTypeWrapper and QV4::QQmlValueTypeWrapper. |
860 | * They can also be used to manipulate elements in QV4::Sequence. |
861 | */ |
862 | int Object::virtualMetacall(Object *object, QMetaObject::Call call, int index, void **a) |
863 | { |
864 | Q_UNUSED(object); |
865 | Q_UNUSED(call); |
866 | Q_UNUSED(index); |
867 | Q_UNUSED(a); |
868 | return 0; |
869 | } |
870 | |
871 | ReturnedValue Object::checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *f, const Value &var) |
872 | { |
873 | Scope scope(engine); |
874 | if (f->isBoundFunction()) { |
875 | ScopedValue v(scope, static_cast<const BoundFunction *>(f)->target()); |
876 | f = v->as<FunctionObject>(); |
877 | } |
878 | |
879 | // 15.3.5.3, 1: HasInstance can only be used on an object |
880 | const Object *lhs = var.as<Object>(); |
881 | if (!lhs) |
882 | return Encode(false); |
883 | |
884 | // 15.3.5.3, 2 |
885 | Value p = Value::fromReturnedValue(val: f->protoProperty()); |
886 | const Object *o = p.objectValue(); |
887 | if (!o) // 15.3.5.3, 3 |
888 | return f->engine()->throwTypeError(); |
889 | |
890 | Heap::Object *v = lhs->d(); |
891 | |
892 | // 15.3.5.3, 4 |
893 | while (v) { |
894 | // 15.3.5.3, 4, a |
895 | v = v->prototype(); |
896 | |
897 | // 15.3.5.3, 4, b |
898 | if (!v) |
899 | break; // will return false |
900 | |
901 | // 15.3.5.3, 4, c |
902 | else if (o->d() == v) |
903 | return Encode(true); |
904 | } |
905 | |
906 | return Encode(false); |
907 | } |
908 | |
909 | bool Object::virtualHasProperty(const Managed *m, PropertyKey id) |
910 | { |
911 | Scope scope(m->engine()); |
912 | ScopedObject o(scope, m); |
913 | ScopedProperty p(scope); |
914 | |
915 | if (o->getOwnProperty(id, p) != Attr_Invalid) |
916 | return true; |
917 | |
918 | o = o->getPrototypeOf(); |
919 | if (o) |
920 | return o->hasProperty(id); |
921 | |
922 | return false; |
923 | } |
924 | |
925 | PropertyAttributes Object::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) |
926 | { |
927 | PropertyAttributes attrs; |
928 | const Object *o = static_cast<const Object *>(m); |
929 | if (id.isArrayIndex()) { |
930 | uint index = id.asArrayIndex(); |
931 | if (o->arrayData()) { |
932 | if (o->arrayData()->getProperty(index, p, attrs: &attrs)) |
933 | return attrs; |
934 | } |
935 | } else { |
936 | Q_ASSERT(id.asStringOrSymbol()); |
937 | |
938 | auto member = o->internalClass()->find(id); |
939 | if (member.isValid()) { |
940 | attrs = member.attributes; |
941 | if (p) { |
942 | p->value = *o->propertyData(index: member.index); |
943 | if (attrs.isAccessor()) |
944 | p->set = *o->propertyData(index: member.setterIndex); |
945 | } |
946 | return attrs; |
947 | } |
948 | } |
949 | |
950 | return Attr_Invalid; |
951 | } |
952 | |
953 | bool Object::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) |
954 | { |
955 | Object *o = static_cast<Object *>(m); |
956 | Scope scope(o); |
957 | |
958 | if (id.isArrayIndex()) { |
959 | uint index = id.asArrayIndex(); |
960 | |
961 | bool hasProperty = false; |
962 | |
963 | if (o->arrayData()) { |
964 | hasProperty = o->arrayData()->mappedIndex(index) != UINT_MAX; |
965 | if (!hasProperty && o->isStringObject()) |
966 | hasProperty = (index < static_cast<StringObject *>(o)->length()); |
967 | } |
968 | |
969 | if (!hasProperty) { |
970 | if (!o->isExtensible()) |
971 | return false; |
972 | |
973 | ScopedProperty pp(scope); |
974 | pp->copy(other: p, attrs); |
975 | pp->fullyPopulated(attrs: &attrs); |
976 | if (attrs == Attr_Data) { |
977 | ScopedValue v(scope, pp->value); |
978 | o->arraySet(index, value: v); |
979 | } else { |
980 | o->arraySet(index, p: pp, attributes: attrs); |
981 | } |
982 | return true; |
983 | } |
984 | |
985 | return o->internalDefineOwnProperty(engine: scope.engine, index, memberEntry: nullptr, p, attrs); |
986 | } |
987 | |
988 | Scoped<InternalClass> ic(scope, o->internalClass()); |
989 | auto memberIndex = ic->d()->find(id); |
990 | |
991 | if (!memberIndex.isValid()) { |
992 | if (!o->isExtensible()) |
993 | return false; |
994 | |
995 | // If the IC is locked, you're not allowed to shadow any unconfigurable properties. |
996 | if (ic->d()->isLocked()) { |
997 | while (Heap::Object *prototype = ic->d()->prototype) { |
998 | ic = prototype->internalClass; |
999 | const auto entry = ic->d()->find(id); |
1000 | if (entry.isValid()) { |
1001 | if (entry.attributes.isConfigurable()) |
1002 | break; |
1003 | qCWarning(lcJavaScriptGlobals).noquote() |
1004 | << QStringLiteral("You cannot shadow the locked property " |
1005 | "'%1' in QML.").arg(a: id.toQString()); |
1006 | return false; |
1007 | } |
1008 | } |
1009 | } |
1010 | |
1011 | Scoped<StringOrSymbol> name(scope, id.asStringOrSymbol()); |
1012 | ScopedProperty pd(scope); |
1013 | pd->copy(other: p, attrs); |
1014 | pd->fullyPopulated(attrs: &attrs); |
1015 | o->insertMember(s: name, p: pd, attributes: attrs); |
1016 | return true; |
1017 | } |
1018 | |
1019 | return o->internalDefineOwnProperty(engine: scope.engine, UINT_MAX, memberEntry: &memberIndex, p, attrs); |
1020 | } |
1021 | |
1022 | bool Object::virtualIsExtensible(const Managed *m) |
1023 | { |
1024 | return m->d()->internalClass->isExtensible(); |
1025 | } |
1026 | |
1027 | bool Object::virtualPreventExtensions(Managed *m) |
1028 | { |
1029 | Q_ASSERT(m->isObject()); |
1030 | Object *o = static_cast<Object *>(m); |
1031 | o->setInternalClass(o->internalClass()->nonExtensible()); |
1032 | return true; |
1033 | } |
1034 | |
1035 | Heap::Object *Object::virtualGetPrototypeOf(const Managed *m) |
1036 | { |
1037 | return m->internalClass()->prototype; |
1038 | } |
1039 | |
1040 | bool Object::virtualSetPrototypeOf(Managed *m, const Object *proto) |
1041 | { |
1042 | Q_ASSERT(m->isObject()); |
1043 | Object *o = static_cast<Object *>(m); |
1044 | Heap::InternalClass *ic = o->internalClass(); |
1045 | Heap::Object *current = ic->prototype; |
1046 | Heap::Object *protod = proto ? proto->d() : nullptr; |
1047 | if (current == protod) |
1048 | return true; |
1049 | if (!ic->isExtensible() || ic->isLocked()) |
1050 | return false; |
1051 | Heap::Object *p = protod; |
1052 | while (p) { |
1053 | if (p == o->d()) |
1054 | return false; |
1055 | if (p->vtable()->getPrototypeOf != Object::staticVTable()->getPrototypeOf) |
1056 | break; |
1057 | p = p->prototype(); |
1058 | } |
1059 | o->setInternalClass(ic->changePrototype(proto: protod)); |
1060 | return true; |
1061 | } |
1062 | |
1063 | bool Object::setArrayLength(uint newLen) |
1064 | { |
1065 | Q_ASSERT(isArrayObject()); |
1066 | if (!internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) |
1067 | return false; |
1068 | uint oldLen = getLength(); |
1069 | bool ok = true; |
1070 | if (newLen < oldLen) { |
1071 | if (arrayData()) { |
1072 | uint l = arrayData()->vtable()->truncate(this, newLen); |
1073 | if (l != newLen) |
1074 | ok = false; |
1075 | newLen = l; |
1076 | } |
1077 | } else { |
1078 | if (newLen >= 0x100000) |
1079 | initSparseArray(); |
1080 | else |
1081 | ArrayData::realloc(o: this, newType: arrayType(), alloc: newLen, enforceAttributes: false); |
1082 | } |
1083 | setArrayLengthUnchecked(newLen); |
1084 | return ok; |
1085 | } |
1086 | |
1087 | void Object::initSparseArray() |
1088 | { |
1089 | if (arrayType() == Heap::ArrayData::Sparse) |
1090 | return; |
1091 | |
1092 | ArrayData::realloc(o: this, newType: Heap::ArrayData::Sparse, alloc: 0, enforceAttributes: false); |
1093 | } |
1094 | |
1095 | bool Object::isConcatSpreadable() const |
1096 | { |
1097 | Scope scope(this); |
1098 | ScopedValue spreadable(scope, get(name: scope.engine->symbol_isConcatSpreadable())); |
1099 | if (!spreadable->isUndefined()) |
1100 | return spreadable->toBoolean(); |
1101 | return isArray(); |
1102 | } |
1103 | |
1104 | bool Object::isArray() const |
1105 | { |
1106 | if (isArrayObject()) |
1107 | return true; |
1108 | if (vtable() == ProxyObject::staticVTable()) { |
1109 | const ProxyObject *p = static_cast<const ProxyObject *>(this); |
1110 | Scope scope(this); |
1111 | if (!p->d()->handler) { |
1112 | scope.engine->throwTypeError(); |
1113 | return false; |
1114 | } |
1115 | ScopedObject o(scope, p->d()->target); |
1116 | return o->isArray(); |
1117 | } |
1118 | return false; |
1119 | } |
1120 | |
1121 | const FunctionObject *Object::speciesConstructor(Scope &scope, const FunctionObject *defaultConstructor) const |
1122 | { |
1123 | ScopedValue C(scope, get(name: scope.engine->id_constructor())); |
1124 | if (C->isUndefined()) |
1125 | return defaultConstructor; |
1126 | const Object *c = C->objectValue(); |
1127 | if (!c) { |
1128 | scope.engine->throwTypeError(); |
1129 | return nullptr; |
1130 | } |
1131 | ScopedValue S(scope, c->get(name: scope.engine->symbol_species())); |
1132 | if (S->isNullOrUndefined()) |
1133 | return defaultConstructor; |
1134 | const FunctionObject *f = S->as<FunctionObject>(); |
1135 | if (!f || !f->isConstructor()) { |
1136 | scope.engine->throwTypeError(); |
1137 | return nullptr; |
1138 | } |
1139 | Q_ASSERT(f->isFunctionObject()); |
1140 | return static_cast<const FunctionObject *>(f); |
1141 | } |
1142 | |
1143 | bool Object::setProtoFromNewTarget(const Value *newTarget) |
1144 | { |
1145 | if (!newTarget || newTarget->isUndefined()) |
1146 | return false; |
1147 | |
1148 | Q_ASSERT(newTarget->isFunctionObject()); |
1149 | Scope scope(this); |
1150 | ScopedObject proto(scope, static_cast<const FunctionObject *>(newTarget)->protoProperty()); |
1151 | if (proto) { |
1152 | setPrototypeOf(proto); |
1153 | return true; |
1154 | } |
1155 | return false; |
1156 | } |
1157 | |
1158 | |
1159 | DEFINE_OBJECT_VTABLE(ArrayObject); |
1160 | |
1161 | void Heap::ArrayObject::init(const QStringList &list) |
1162 | { |
1163 | Object::init(); |
1164 | commonInit(); |
1165 | Scope scope(internalClass->engine); |
1166 | ScopedObject a(scope, this); |
1167 | |
1168 | // Converts a QStringList to JS. |
1169 | // The result is a new Array object with length equal to the length |
1170 | // of the QStringList, and the elements being the QStringList's |
1171 | // elements converted to JS Strings. |
1172 | int len = list.size(); |
1173 | a->arrayReserve(n: len); |
1174 | ScopedValue v(scope); |
1175 | for (int ii = 0; ii < len; ++ii) |
1176 | a->arrayPut(index: ii, value: (v = scope.engine->newString(s: list.at(i: ii)))); |
1177 | a->setArrayLengthUnchecked(len); |
1178 | } |
1179 | |
1180 | qint64 ArrayObject::virtualGetLength(const Managed *m) |
1181 | { |
1182 | const ArrayObject *a = static_cast<const ArrayObject *>(m); |
1183 | return a->propertyData(index: Heap::ArrayObject::LengthPropertyIndex)->toLength(); |
1184 | } |
1185 | |
1186 | QStringList ArrayObject::toQStringList() const |
1187 | { |
1188 | QStringList result; |
1189 | |
1190 | QV4::ExecutionEngine *engine = internalClass()->engine; |
1191 | Scope scope(engine); |
1192 | ScopedValue v(scope); |
1193 | |
1194 | uint length = getLength(); |
1195 | result.reserve(asize: length); |
1196 | for (uint i = 0; i < length; ++i) { |
1197 | v = const_cast<ArrayObject *>(this)->get(idx: i); |
1198 | result.append(t: v->toQStringNoThrow()); |
1199 | } |
1200 | return result; |
1201 | } |
1202 | |
1203 | bool ArrayObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) |
1204 | { |
1205 | Q_ASSERT(m->isArrayObject()); |
1206 | ArrayObject *a = static_cast<ArrayObject *>(m); |
1207 | |
1208 | if (id.isArrayIndex()) { |
1209 | uint index = id.asArrayIndex(); |
1210 | uint len = a->getLength(); |
1211 | if (index >= len && !a->internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) |
1212 | return false; |
1213 | |
1214 | bool succeeded = Object::virtualDefineOwnProperty(m, id, p, attrs); |
1215 | if (!succeeded) |
1216 | return false; |
1217 | |
1218 | if (index >= len) |
1219 | a->setArrayLengthUnchecked(index + 1); |
1220 | |
1221 | return true; |
1222 | } |
1223 | |
1224 | ExecutionEngine *engine = m->engine(); |
1225 | if (id == engine->id_length()->propertyKey()) { |
1226 | Scope scope(engine); |
1227 | Q_ASSERT(a->internalClass()->verifyIndex(engine->id_length()->propertyKey(), Heap::ArrayObject::LengthPropertyIndex)); |
1228 | ScopedProperty lp(scope); |
1229 | InternalClassEntry e = a->internalClass()->find(id: scope.engine->id_length()->propertyKey()); |
1230 | a->getProperty(entry: e, p: lp); |
1231 | if (attrs.isEmpty() || p->isSubset(attrs, other: lp, otherAttrs: e.attributes)) |
1232 | return true; |
1233 | if (!e.attributes.isWritable() || attrs.type() == PropertyAttributes::Accessor || attrs.isConfigurable() || attrs.isEnumerable()) |
1234 | return false; |
1235 | bool succeeded = true; |
1236 | if (attrs.type() == PropertyAttributes::Data) { |
1237 | bool ok; |
1238 | uint l = p->value.asArrayLength(ok: &ok); |
1239 | if (!ok) { |
1240 | ScopedValue v(scope, p->value); |
1241 | engine->throwRangeError(value: v); |
1242 | return false; |
1243 | } |
1244 | succeeded = a->setArrayLength(l); |
1245 | } |
1246 | if (attrs.hasWritable() && !attrs.isWritable()) { |
1247 | e.attributes.setWritable(false); |
1248 | Heap::InternalClass::changeMember(object: a, id: engine->id_length()->propertyKey(), data: e.attributes); |
1249 | } |
1250 | if (!succeeded) |
1251 | return false; |
1252 | return true; |
1253 | } |
1254 | return Object::virtualDefineOwnProperty(m, id, p, attrs); |
1255 | } |
1256 |
Definitions
- lcJavaScriptGlobals
- setInternalClass
- getProperty
- setProperty
- setUsedAsProto
- getValueAccessor
- putValue
- defineDefaultProperty
- defineDefaultProperty
- defineDefaultProperty
- defineAccessorProperty
- defineAccessorProperty
- defineReadonlyProperty
- defineReadonlyProperty
- defineReadonlyConfigurableProperty
- defineReadonlyConfigurableProperty
- addSymbolSpecies
- markObjects
- insertMember
- setPrototypeUnchecked
- getValueOrSetter
- virtualGet
- virtualPut
- virtualDeleteProperty
- next
- virtualOwnPropertyKeys
- internalGet
- internalPut
- internalDeleteProperty
- internalDefineOwnProperty
- copyArrayData
- virtualGetLength
- virtualInstanceOf
- virtualResolveLookupGetter
- virtualResolveLookupSetter
- virtualMetacall
- checkedInstanceOf
- virtualHasProperty
- virtualGetOwnProperty
- virtualDefineOwnProperty
- virtualIsExtensible
- virtualPreventExtensions
- virtualGetPrototypeOf
- virtualSetPrototypeOf
- setArrayLength
- initSparseArray
- isConcatSpreadable
- isArray
- speciesConstructor
- setProtoFromNewTarget
- init
- virtualGetLength
- toQStringList
Learn Advanced QML with KDAB
Find out more