/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQml module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qv4object_p.h" #include "qv4objectproto_p.h" #include "qv4stringobject_p.h" #include "qv4argumentsobject_p.h" #include #include "qv4lookup_p.h" #include "qv4scopedvalue_p.h" #include "qv4memberdata_p.h" #include "qv4objectiterator_p.h" #include "qv4identifier_p.h" #include "qv4string_p.h" #include "qv4identifiertable_p.h" #include "qv4jscall_p.h" #include "qv4symbol_p.h" #include "qv4proxy_p.h" #include using namespace QV4; DEFINE_OBJECT_VTABLE(Object); void Object::setInternalClass(Heap::InternalClass *ic) { d()->internalClass.set(engine(), ic); if (ic->isUsedAsProto) ic->updateProtoUsage(d()); Q_ASSERT(ic && ic->vtable); uint nInline = d()->vtable()->nInlineProperties; if (ic->size <= nInline) return; bool hasMD = d()->memberData != nullptr; uint requiredSize = ic->size - nInline; if (!(hasMD && requiredSize) || (hasMD && d()->memberData->values.size < requiredSize)) d()->memberData.set(ic->engine, MemberData::allocate(ic->engine, requiredSize, d()->memberData)); } void Object::getProperty(const InternalClassEntry &entry, Property *p) const { p->value = *propertyData(entry.index); if (entry.attributes.isAccessor()) p->set = *propertyData(entry.setterIndex); } void Object::setProperty(const InternalClassEntry &entry, const Property *p) { setProperty(entry.index, p->value); if (entry.attributes.isAccessor()) setProperty(entry.setterIndex, p->set); } void Heap::Object::setUsedAsProto() { internalClass.set(internalClass->engine, internalClass->asProtoClass()); } ReturnedValue Object::getValueAccessor(const Value *thisObject, const Value &v, PropertyAttributes attrs) { if (!attrs.isAccessor()) return v.asReturnedValue(); const QV4::FunctionObject *f = v.as(); if (!f) return Encode::undefined(); Scope scope(f->engine()); JSCallData jsCallData(scope); if (thisObject) *jsCallData->thisObject = *thisObject; return f->call(jsCallData); } bool Object::putValue(uint memberIndex, PropertyAttributes attrs, const Value &value) { Heap::InternalClass *ic = internalClass(); if (ic->engine->hasException) return false; if (attrs.isAccessor()) { const FunctionObject *set = propertyData(memberIndex)->as(); if (set) { Scope scope(ic->engine); ScopedFunctionObject setter(scope, set); JSCallData jsCallData(scope, 1); jsCallData->args[0] = value; *jsCallData->thisObject = this; setter->call(jsCallData); return !ic->engine->hasException; } return false; } if (!attrs.isWritable()) return false; setProperty(memberIndex, value); return true; } void Object::defineDefaultProperty(const QString &name, const Value &value, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); defineDefaultProperty(s, value, attributes); } void Object::defineDefaultProperty(const QString &name, VTable::Call code, int argumentCount, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, s, code, argumentCount)); defineDefaultProperty(s, function, attributes); } void Object::defineDefaultProperty(StringOrSymbol *nameOrSymbol, VTable::Call code, int argumentCount, PropertyAttributes attributes) { ExecutionEngine *e = engine(); Scope scope(e); ScopedFunctionObject function(scope, FunctionObject::createBuiltinFunction(e, nameOrSymbol, code, argumentCount)); defineDefaultProperty(nameOrSymbol, function, attributes); } void Object::defineAccessorProperty(const QString &name, VTable::Call getter, VTable::Call setter) { ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); defineAccessorProperty(s, getter, setter); } void Object::defineAccessorProperty(StringOrSymbol *name, VTable::Call getter, VTable::Call setter) { ExecutionEngine *v4 = engine(); QV4::Scope scope(v4); ScopedProperty p(scope); QString n = name->toQString(); if (n.at(0) == QLatin1Char('@')) n = QChar::fromLatin1('[') + n.midRef(1) + QChar::fromLatin1(']'); if (getter) { ScopedString getName(scope, v4->newString(QString::fromLatin1("get ") + n)); p->setGetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, getName, getter, 0))); } else { p->setGetter(nullptr); } if (setter) { ScopedString setName(scope, v4->newString(QString::fromLatin1("set ") + n)); p->setSetter(ScopedFunctionObject(scope, FunctionObject::createBuiltinFunction(v4, setName, setter, 0))); } else { p->setSetter(nullptr); } insertMember(name, p, QV4::Attr_Accessor|QV4::Attr_NotEnumerable); } void Object::defineReadonlyProperty(const QString &name, const Value &value) { QV4::ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); defineReadonlyProperty(s, value); } void Object::defineReadonlyProperty(String *name, const Value &value) { insertMember(name, value, Attr_ReadOnly); } void Object::defineReadonlyConfigurableProperty(const QString &name, const Value &value) { QV4::ExecutionEngine *e = engine(); Scope scope(e); ScopedString s(scope, e->newIdentifier(name)); defineReadonlyConfigurableProperty(s, value); } void Object::defineReadonlyConfigurableProperty(StringOrSymbol *name, const Value &value) { insertMember(name, value, Attr_ReadOnly_ButConfigurable); } void Object::addSymbolSpecies() { Scope scope(engine()); ScopedProperty p(scope); p->setGetter(scope.engine->getSymbolSpecies()); p->setSetter(nullptr); insertMember(scope.engine->symbol_species(), p, QV4::Attr_Accessor|QV4::Attr_NotWritable|QV4::Attr_NotEnumerable); } void Heap::Object::markObjects(Heap::Base *b, MarkStack *stack) { Base::markObjects(b, stack); Object *o = static_cast(b); if (o->memberData) o->memberData->mark(stack); if (o->arrayData) o->arrayData->mark(stack); uint nInline = o->vtable()->nInlineProperties; Value *v = reinterpret_cast(o) + o->vtable()->inlinePropertyOffset; const Value *end = v + nInline; while (v < end) { v->mark(stack); ++v; } } void Object::insertMember(StringOrSymbol *s, const Property *p, PropertyAttributes attributes) { InternalClassEntry idx; PropertyKey key = s->toPropertyKey(); Heap::InternalClass::addMember(this, key, attributes, &idx); setProperty(idx.index, p->value); if (attributes.isAccessor()) setProperty(idx.setterIndex, p->set); } void Object::setPrototypeUnchecked(const Object *p) { setInternalClass(internalClass()->changePrototype(p ? p->d() : nullptr)); } // Section 8.12.2 PropertyIndex Object::getValueOrSetter(PropertyKey id, PropertyAttributes *attrs) { if (id.isArrayIndex()) { uint index = id.asArrayIndex(); Heap::Object *o = d(); while (o) { if (o->arrayData) { uint idx = o->arrayData->mappedIndex(index); if (idx != UINT_MAX) { *attrs = o->arrayData->attributes(index); return { o->arrayData , o->arrayData->values.values + (attrs->isAccessor() ? idx + SetterOffset : idx) }; } } if (o->vtable()->type == Type_StringObject) { if (index < static_cast(o)->length()) { // this is an evil hack, but it works, as the method is only ever called from put, // where we don't use the returned pointer there for non writable attributes *attrs = (Attr_NotWritable|Attr_NotConfigurable); return { reinterpret_cast(0x1), nullptr }; } } o = o->prototype(); } } else { Heap::Object *o = d(); while (o) { auto idx = o->internalClass->findValueOrSetter(id); if (idx.isValid()) { *attrs = idx.attrs; return o->writablePropertyData(idx.index); } o = o->prototype(); } } *attrs = Attr_Invalid; return { nullptr, nullptr }; } ReturnedValue Object::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty) { return static_cast(m)->internalGet(id, receiver, hasProperty); } bool Object::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver) { return static_cast(m)->internalPut(id, value, receiver); } bool Object::virtualDeleteProperty(Managed *m, PropertyKey id) { return static_cast(m)->internalDeleteProperty(id); } PropertyKey ObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs) { if (arrayIndex != UINT_MAX && o->arrayData()) { SparseArrayNode *arrayNode = nullptr; if (o->arrayType() == Heap::ArrayData::Sparse) { SparseArray *sparse = o->arrayData()->sparse; arrayNode = arrayIndex ? sparse->lowerBound(arrayIndex) : sparse->begin(); } // sparse arrays if (arrayNode) { while (arrayNode != o->sparseEnd()) { uint k = arrayNode->key(); uint pidx = arrayNode->value; Heap::SparseArrayData *sa = o->d()->arrayData.cast(); const Property *p = reinterpret_cast(sa->values.data() + pidx); arrayNode = arrayNode->nextNode(); PropertyAttributes a = sa->attrs ? sa->attrs[pidx] : Attr_Data; arrayIndex = k + 1; if (pd) pd->copy(p, a); if (attrs) *attrs = a; return PropertyKey::fromArrayIndex(k); } arrayIndex = UINT_MAX; } // dense arrays while (arrayIndex < o->d()->arrayData->values.size) { Heap::SimpleArrayData *sa = o->d()->arrayData.cast(); const Value &val = sa->data(arrayIndex); PropertyAttributes a = o->arrayData()->attributes(arrayIndex); int index = arrayIndex; ++arrayIndex; if (!val.isEmpty()) { if (pd) pd->value = val; if (attrs) *attrs = a; return PropertyKey::fromArrayIndex(index); } } arrayIndex = UINT_MAX; } while (true) { while (memberIndex < o->internalClass()->size) { PropertyKey n = o->internalClass()->nameMap.at(memberIndex); ++memberIndex; if (!n.isStringOrSymbol()) // accessor properties have a dummy entry with n == 0 continue; if (!iterateOverSymbols && n.isSymbol()) continue; if (iterateOverSymbols && !n.isSymbol()) continue; InternalClassEntry e = o->internalClass()->find(n); if (!e.isValid()) continue; if (pd) { pd->value = *o->propertyData(e.index); if (e.attributes.isAccessor()) pd->set = *o->propertyData(e.setterIndex); } if (attrs) *attrs = e.attributes; return n; } if (iterateOverSymbols) break; iterateOverSymbols = true; memberIndex = 0; } return PropertyKey::invalid(); } OwnPropertyKeyIterator *Object::virtualOwnPropertyKeys(const Object *o, Value *target) { *target = *o; return new ObjectOwnPropertyKeyIterator; } // Section 8.12.3 ReturnedValue Object::internalGet(PropertyKey id, const Value *receiver, bool *hasProperty) const { Heap::Object *o = d(); if (id.isArrayIndex()) { const uint index = id.asArrayIndex(); Scope scope(this); PropertyAttributes attrs; ScopedProperty pd(scope); while (1) { if (o->arrayData && o->arrayData->getProperty(index, pd, &attrs)) { if (hasProperty) *hasProperty = true; return Object::getValue(receiver, pd->value, attrs); } if (o->internalClass->vtable->type == Type_StringObject) { ScopedString str(scope, static_cast(o)->getIndex(index)); if (str) { attrs = (Attr_NotWritable|Attr_NotConfigurable); if (hasProperty) *hasProperty = true; return str.asReturnedValue(); } } o = o->prototype(); if (!o || o->internalClass->vtable->get != Object::virtualGet) break; } } else { while (1) { auto idx = o->internalClass->findValueOrGetter(id); if (idx.isValid()) { if (hasProperty) *hasProperty = true; return Object::getValue(receiver, *o->propertyData(idx.index), idx.attrs); } o = o->prototype(); if (!o || o->internalClass->vtable->get != Object::virtualGet) break; } } if (o) { const Value v = Value::fromHeapObject(o); const Object &obj = static_cast(v); return obj.get(id, receiver, hasProperty); } if (hasProperty) *hasProperty = false; return Encode::undefined(); } // Section 8.12.5 bool Object::internalPut(PropertyKey id, const Value &value, Value *receiver) { Scope scope(this); if (scope.engine->hasException) return false; Object *r = receiver->objectValue(); if (r && r->d() == d()) { // receiver and this object are the same if (d()->internalClass->vtable->getOwnProperty == Object::virtualGetOwnProperty) { // This object standard methods in the vtable, so we can take a shortcut // and avoid the calls to getOwnProperty and defineOwnProperty PropertyAttributes attrs; PropertyIndex propertyIndex{nullptr, nullptr}; if (id.isArrayIndex()) { if (arrayData()) propertyIndex = arrayData()->getValueOrSetter(id.asArrayIndex(), &attrs); } else { auto member = internalClass()->findValueOrSetter(id); if (member.isValid()) { attrs = member.attrs; propertyIndex = d()->writablePropertyData(member.index); } } if (!propertyIndex.isNull() && !attrs.isAccessor()) { if (!attrs.isWritable()) return false; else if (isArrayObject() && id == scope.engine->id_length()->propertyKey()) { bool ok; uint l = value.asArrayLength(&ok); if (!ok) { scope.engine->throwRangeError(value); return false; } ok = setArrayLength(l); if (!ok) return false; } else { propertyIndex.set(scope.engine, value); } return true; } } } ScopedProperty p(scope); PropertyAttributes attrs; attrs = getOwnProperty(id, p); if (attrs == Attr_Invalid) { ScopedObject p(scope, getPrototypeOf()); if (p) return p->put(id, value, receiver); attrs = Attr_Data; } if (attrs.isAccessor()) { ScopedFunctionObject setter(scope, p->setter()); if (!setter) return false; JSCallData jsCallData(scope, 1); jsCallData->args[0] = value; *jsCallData->thisObject = *receiver; setter->call(jsCallData); return !scope.engine->hasException; } // Data property if (!attrs.isWritable()) return false; if (!r) return false; attrs = r->getOwnProperty(id, p); if (attrs != Attr_Invalid) { if (attrs.isAccessor() || !attrs.isWritable()) return false; } else { if (!r->isExtensible()) return false; attrs = Attr_Data; } if (r->internalClass()->vtable->defineOwnProperty == virtualDefineOwnProperty) { // standard object, we can avoid some more checks if (id.isArrayIndex()) { r->arraySet(id.asArrayIndex(), value); } else { ScopedStringOrSymbol s(scope, id.asStringOrSymbol()); r->insertMember(s, value); } return true; } p->value = value; return r->defineOwnProperty(id, p, attrs); } // Section 8.12.7 bool Object::internalDeleteProperty(PropertyKey id) { if (internalClass()->engine->hasException) return false; if (id.isArrayIndex()) { uint index = id.asArrayIndex(); Scope scope(engine()); if (scope.engine->hasException) return false; Scoped ad(scope, arrayData()); if (!ad || ad->vtable()->del(this, index)) return true; return false; } auto memberIdx = internalClass()->findValueOrGetter(id); if (memberIdx.isValid()) { if (memberIdx.attrs.isConfigurable()) { Heap::InternalClass::removeMember(this, id); return true; } return false; } return true; } bool Object::internalDefineOwnProperty(ExecutionEngine *engine, uint index, const InternalClassEntry *memberEntry, const Property *p, PropertyAttributes attrs) { // clause 5 if (attrs.isEmpty()) return true; Scope scope(engine); ScopedProperty current(scope); PropertyAttributes cattrs; if (memberEntry) { getProperty(*memberEntry, current); cattrs = memberEntry->attributes; } else if (arrayData()) { arrayData()->getProperty(index, current, &cattrs); cattrs = arrayData()->attributes(index); } // clause 6 if (p->isSubset(attrs, current, cattrs)) return true; // clause 7 if (!cattrs.isConfigurable()) { if (attrs.isConfigurable()) return false; if (attrs.hasEnumerable() && attrs.isEnumerable() != cattrs.isEnumerable()) return false; } // clause 8 if (attrs.isGeneric() || current->value.isEmpty()) goto accept; // clause 9 if (cattrs.isData() != attrs.isData()) { // 9a if (!cattrs.isConfigurable()) return false; if (cattrs.isData()) { // 9b cattrs.setType(PropertyAttributes::Accessor); cattrs.clearWritable(); if (!memberEntry) { // need to convert the array and the slot initSparseArray(); Q_ASSERT(arrayData()); setArrayAttributes(index, cattrs); } current->setGetter(nullptr); current->setSetter(nullptr); } else { // 9c cattrs.setType(PropertyAttributes::Data); cattrs.setWritable(false); if (!memberEntry) { // need to convert the array and the slot setArrayAttributes(index, cattrs); } current->value = Value::undefinedValue(); } } else if (cattrs.isData() && attrs.isData()) { // clause 10 if (!cattrs.isConfigurable() && !cattrs.isWritable()) { if (attrs.isWritable() || !current->value.sameValue(p->value)) return false; } } else { // clause 10 Q_ASSERT(cattrs.isAccessor() && attrs.isAccessor()); if (!cattrs.isConfigurable()) { if (!p->value.isEmpty() && current->value.rawValue() != p->value.rawValue()) return false; if (!p->set.isEmpty() && current->set.rawValue() != p->set.rawValue()) return false; } } accept: current->merge(cattrs, p, attrs); if (memberEntry) { PropertyKey key = internalClass()->nameMap.at(memberEntry->index); InternalClassEntry e; Heap::InternalClass::changeMember(this, key, cattrs, &e); setProperty(e, current); } else { setArrayAttributes(index, cattrs); arrayData()->setProperty(scope.engine, index, current); } return true; } void Object::copyArrayData(Object *other) { Q_ASSERT(isArrayObject()); Scope scope(engine()); if (other->protoHasArray() || ArgumentsObject::isNonStrictArgumentsObject(other) || (other->arrayType() == Heap::ArrayData::Sparse && other->arrayData()->attrs)) { uint len = other->getLength(); Q_ASSERT(len); ScopedValue v(scope); for (uint i = 0; i < len; ++i) { arraySet(i, (v = other->get(i))); } } else if (!other->arrayData()) { ; } else { Q_ASSERT(!arrayData() && other->arrayData()); ArrayData::realloc(this, static_cast(other->d()->arrayData->type), other->d()->arrayData->values.alloc, false); if (other->arrayType() == Heap::ArrayData::Sparse) { Heap::ArrayData *od = other->d()->arrayData; Heap::ArrayData *dd = d()->arrayData; dd->sparse = new SparseArray(*od->sparse); } else { Heap::ArrayData *dd = d()->arrayData; dd->values.size = other->d()->arrayData->values.size; dd->offset = other->d()->arrayData->offset; } // ### need a write barrier memcpy(d()->arrayData->values.values, other->d()->arrayData->values.values, other->d()->arrayData->values.alloc*sizeof(Value)); } setArrayLengthUnchecked(other->getLength()); } qint64 Object::virtualGetLength(const Managed *m) { Scope scope(static_cast(m)->engine()); ScopedValue v(scope, static_cast(const_cast(m))->get(scope.engine->id_length())); return v->toLength(); } // 'var' is 'V' in 15.3.5.3. ReturnedValue Object::virtualInstanceOf(const Object *typeObject, const Value &var) { QV4::ExecutionEngine *engine = typeObject->internalClass()->engine; // 15.3.5.3, Assume F is a Function object. const FunctionObject *function = typeObject->as(); if (!function) return engine->throwTypeError(); return checkedInstanceOf(engine, function, var); } ReturnedValue Object::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup) { Heap::Object *obj = object->d(); PropertyKey name = engine->identifierTable->asPropertyKey(engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); if (name.isArrayIndex()) { lookup->indexedLookup.index = name.asArrayIndex(); lookup->getter = Lookup::getterIndexed; return lookup->getter(lookup, engine, *object); } auto index = obj->internalClass->findValueOrGetter(name); if (index.isValid()) { PropertyAttributes attrs = index.attrs; uint nInline = obj->vtable()->nInlineProperties; if (attrs.isData()) { if (index.index < obj->vtable()->nInlineProperties) { index.index += obj->vtable()->inlinePropertyOffset; lookup->getter = Lookup::getter0Inline; } else { index.index -= nInline; lookup->getter = Lookup::getter0MemberData; } } else { lookup->getter = Lookup::getterAccessor; } lookup->objectLookup.ic = obj->internalClass; lookup->objectLookup.offset = index.index; return lookup->getter(lookup, engine, *object); } lookup->protoLookup.protoId = obj->internalClass->protoId; lookup->resolveProtoGetter(name, obj->prototype()); return lookup->getter(lookup, engine, *object); } bool Object::virtualResolveLookupSetter(Object *object, ExecutionEngine *engine, Lookup *lookup, const Value &value) { Scope scope(engine); ScopedString name(scope, scope.engine->currentStackFrame->v4Function->compilationUnit->runtimeStrings[lookup->nameIndex]); Heap::InternalClass *c = object->internalClass(); PropertyKey key = name->toPropertyKey(); auto idx = c->findValueOrSetter(key); if (idx.isValid()) { if (object->isArrayObject() && idx.index == Heap::ArrayObject::LengthPropertyIndex) { Q_ASSERT(!idx.attrs.isAccessor()); lookup->setter = Lookup::arrayLengthSetter; return lookup->setter(lookup, engine, *object, value); } else if (idx.attrs.isData() && idx.attrs.isWritable()) { lookup->objectLookup.ic = object->internalClass(); lookup->objectLookup.index = idx.index; const auto nInline = object->d()->vtable()->nInlineProperties; if (idx.index < nInline) { lookup->setter = Lookup::setter0Inline; lookup->objectLookup.offset = idx.index + object->d()->vtable()->inlinePropertyOffset; } else { lookup->setter = Lookup::setter0MemberData; lookup->objectLookup.offset = idx.index - nInline; } return lookup->setter(lookup, engine, *object, value); } else { // ### handle setter lookup->setter = Lookup::setterFallback; } return lookup->setter(lookup, engine, *object, value); } lookup->insertionLookup.protoId = c->protoId; if (!object->put(key, value)) { lookup->setter = Lookup::setterFallback; return false; } if (object->internalClass() == c) { // ### setter in the prototype, should handle this lookup->setter = Lookup::setterFallback; return true; } idx = object->internalClass()->findValueOrSetter(key); if (!idx.isValid() || idx.attrs.isAccessor()) { // ### can this even happen? lookup->setter = Lookup::setterFallback; return false; } lookup->insertionLookup.newClass = object->internalClass(); lookup->insertionLookup.offset = idx.index; lookup->setter = Lookup::setterInsert; return true; } ReturnedValue Object::checkedInstanceOf(ExecutionEngine *engine, const FunctionObject *f, const Value &var) { Scope scope(engine); if (f->isBoundFunction()) { ScopedValue v(scope, static_cast(f)->target()); f = v->as(); } // 15.3.5.3, 1: HasInstance can only be used on an object const Object *lhs = var.as(); if (!lhs) return Encode(false); // 15.3.5.3, 2 Value p = Value::fromReturnedValue(f->protoProperty()); const Object *o = p.objectValue(); if (!o) // 15.3.5.3, 3 return f->engine()->throwTypeError(); Heap::Object *v = lhs->d(); // 15.3.5.3, 4 while (v) { // 15.3.5.3, 4, a v = v->prototype(); // 15.3.5.3, 4, b if (!v) break; // will return false // 15.3.5.3, 4, c else if (o->d() == v) return Encode(true); } return Encode(false); } bool Object::virtualHasProperty(const Managed *m, PropertyKey id) { Scope scope(m->engine()); ScopedObject o(scope, m); ScopedProperty p(scope); if (o->getOwnProperty(id, p) != Attr_Invalid) return true; o = o->getPrototypeOf(); if (o) return o->hasProperty(id); return false; } PropertyAttributes Object::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p) { PropertyAttributes attrs; const Object *o = static_cast(m); if (id.isArrayIndex()) { uint index = id.asArrayIndex(); if (o->arrayData()) { if (o->arrayData()->getProperty(index, p, &attrs)) return attrs; } } else { Q_ASSERT(id.asStringOrSymbol()); auto member = o->internalClass()->find(id); if (member.isValid()) { attrs = member.attributes; if (p) { p->value = *o->propertyData(member.index); if (attrs.isAccessor()) p->set = *o->propertyData(member.setterIndex); } return attrs; } } return Attr_Invalid; } bool Object::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) { Object *o = static_cast(m); Scope scope(o); if (id.isArrayIndex()) { uint index = id.asArrayIndex(); bool hasProperty = false; if (o->arrayData()) { hasProperty = o->arrayData()->mappedIndex(index) != UINT_MAX; if (!hasProperty && o->isStringObject()) hasProperty = (index < static_cast(o)->length()); } if (!hasProperty) { if (!o->isExtensible()) return false; ScopedProperty pp(scope); pp->copy(p, attrs); pp->fullyPopulated(&attrs); if (attrs == Attr_Data) { ScopedValue v(scope, pp->value); o->arraySet(index, v); } else { o->arraySet(index, pp, attrs); } return true; } return o->internalDefineOwnProperty(scope.engine, index, nullptr, p, attrs); } auto memberIndex = o->internalClass()->find(id); if (!memberIndex.isValid()) { if (!o->isExtensible()) return false; Scoped name(scope, id.asStringOrSymbol()); ScopedProperty pd(scope); pd->copy(p, attrs); pd->fullyPopulated(&attrs); o->insertMember(name, pd, attrs); return true; } return o->internalDefineOwnProperty(scope.engine, UINT_MAX, &memberIndex, p, attrs); } bool Object::virtualIsExtensible(const Managed *m) { return m->d()->internalClass->extensible; } bool Object::virtualPreventExtensions(Managed *m) { Q_ASSERT(m->isObject()); Object *o = static_cast(m); o->setInternalClass(o->internalClass()->nonExtensible()); return true; } Heap::Object *Object::virtualGetPrototypeOf(const Managed *m) { return m->internalClass()->prototype; } bool Object::virtualSetPrototypeOf(Managed *m, const Object *proto) { Q_ASSERT(m->isObject()); Object *o = static_cast(m); Heap::Object *current = o->internalClass()->prototype; Heap::Object *protod = proto ? proto->d() : nullptr; if (current == protod) return true; if (!o->internalClass()->extensible) return false; Heap::Object *p = protod; while (p) { if (p == o->d()) return false; if (p->vtable()->getPrototypeOf != Object::staticVTable()->getPrototypeOf) break; p = p->prototype(); } o->setInternalClass(o->internalClass()->changePrototype(protod)); return true; } bool Object::setArrayLength(uint newLen) { Q_ASSERT(isArrayObject()); if (!internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) return false; uint oldLen = getLength(); bool ok = true; if (newLen < oldLen) { if (arrayData()) { uint l = arrayData()->vtable()->truncate(this, newLen); if (l != newLen) ok = false; newLen = l; } } else { if (newLen >= 0x100000) initSparseArray(); } setArrayLengthUnchecked(newLen); return ok; } void Object::initSparseArray() { if (arrayType() == Heap::ArrayData::Sparse) return; ArrayData::realloc(this, Heap::ArrayData::Sparse, 0, false); } bool Object::isConcatSpreadable() const { Scope scope(this); ScopedValue spreadable(scope, get(scope.engine->symbol_isConcatSpreadable())); if (!spreadable->isUndefined()) return spreadable->toBoolean(); return isArray(); } bool Object::isArray() const { if (isArrayObject()) return true; if (vtable() == ProxyObject::staticVTable()) { const ProxyObject *p = static_cast(this); Scope scope(this); if (!p->d()->handler) { scope.engine->throwTypeError(); return false; } ScopedObject o(scope, p->d()->target); return o->isArray(); } return false; } const FunctionObject *Object::speciesConstructor(Scope &scope, const FunctionObject *defaultConstructor) const { ScopedValue C(scope, get(scope.engine->id_constructor())); if (C->isUndefined()) return defaultConstructor; const Object *c = C->objectValue(); if (!c) { scope.engine->throwTypeError(); return nullptr; } ScopedValue S(scope, c->get(scope.engine->symbol_species())); if (S->isNullOrUndefined()) return defaultConstructor; const FunctionObject *f = S->as(); if (!f || !f->isConstructor()) { scope.engine->throwTypeError(); return nullptr; } Q_ASSERT(f->isFunctionObject()); return static_cast(f); } bool Object::setProtoFromNewTarget(const Value *newTarget) { if (!newTarget || newTarget->isUndefined()) return false; Q_ASSERT(newTarget->isFunctionObject()); Scope scope(this); ScopedObject proto(scope, static_cast(newTarget)->protoProperty()); if (proto) { setPrototypeOf(proto); return true; } return false; } DEFINE_OBJECT_VTABLE(ArrayObject); void Heap::ArrayObject::init(const QStringList &list) { Object::init(); commonInit(); Scope scope(internalClass->engine); ScopedObject a(scope, this); // Converts a QStringList to JS. // The result is a new Array object with length equal to the length // of the QStringList, and the elements being the QStringList's // elements converted to JS Strings. int len = list.count(); a->arrayReserve(len); ScopedValue v(scope); for (int ii = 0; ii < len; ++ii) a->arrayPut(ii, (v = scope.engine->newString(list.at(ii)))); a->setArrayLengthUnchecked(len); } qint64 ArrayObject::virtualGetLength(const Managed *m) { const ArrayObject *a = static_cast(m); return a->propertyData(Heap::ArrayObject::LengthPropertyIndex)->toLength(); } QStringList ArrayObject::toQStringList() const { QStringList result; QV4::ExecutionEngine *engine = internalClass()->engine; Scope scope(engine); ScopedValue v(scope); uint length = getLength(); for (uint i = 0; i < length; ++i) { v = const_cast(this)->get(i); result.append(v->toQStringNoThrow()); } return result; } bool ArrayObject::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs) { Q_ASSERT(m->isArrayObject()); ArrayObject *a = static_cast(m); if (id.isArrayIndex()) { uint index = id.asArrayIndex(); uint len = a->getLength(); if (index >= len && !a->internalClass()->propertyData[Heap::ArrayObject::LengthPropertyIndex].isWritable()) return false; bool succeeded = Object::virtualDefineOwnProperty(m, id, p, attrs); if (!succeeded) return false; if (index >= len) a->setArrayLengthUnchecked(index + 1); return true; } ExecutionEngine *engine = m->engine(); if (id == engine->id_length()->propertyKey()) { Scope scope(engine); Q_ASSERT(a->internalClass()->verifyIndex(engine->id_length()->propertyKey(), Heap::ArrayObject::LengthPropertyIndex)); ScopedProperty lp(scope); InternalClassEntry e = a->internalClass()->find(scope.engine->id_length()->propertyKey()); a->getProperty(e, lp); if (attrs.isEmpty() || p->isSubset(attrs, lp, e.attributes)) return true; if (!e.attributes.isWritable() || attrs.type() == PropertyAttributes::Accessor || attrs.isConfigurable() || attrs.isEnumerable()) return false; bool succeeded = true; if (attrs.type() == PropertyAttributes::Data) { bool ok; uint l = p->value.asArrayLength(&ok); if (!ok) { ScopedValue v(scope, p->value); engine->throwRangeError(v); return false; } succeeded = a->setArrayLength(l); } if (attrs.hasWritable() && !attrs.isWritable()) { e.attributes.setWritable(false); Heap::InternalClass::changeMember(a, engine->id_length()->propertyKey(), e.attributes); } if (!succeeded) return false; return true; } return Object::virtualDefineOwnProperty(m, id, p, attrs); }