diff options
Diffstat (limited to 'js/src/vm/NativeObject-inl.h')
-rw-r--r-- | js/src/vm/NativeObject-inl.h | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h new file mode 100644 index 000000000..48a42a8db --- /dev/null +++ b/js/src/vm/NativeObject-inl.h @@ -0,0 +1,612 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_NativeObject_inl_h +#define vm_NativeObject_inl_h + +#include "vm/NativeObject.h" + +#include "jscntxt.h" + +#include "builtin/TypedObject.h" +#include "proxy/Proxy.h" +#include "vm/ProxyObject.h" +#include "vm/TypedArrayObject.h" + +#include "jsobjinlines.h" + +namespace js { + +inline uint8_t* +NativeObject::fixedData(size_t nslots) const +{ + MOZ_ASSERT(ClassCanHaveFixedData(getClass())); + MOZ_ASSERT(nslots == numFixedSlots() + (hasPrivate() ? 1 : 0)); + return reinterpret_cast<uint8_t*>(&fixedSlots()[nslots]); +} + +inline void +NativeObject::removeLastProperty(ExclusiveContext* cx) +{ + MOZ_ASSERT(canRemoveLastProperty()); + JS_ALWAYS_TRUE(setLastProperty(cx, lastProperty()->previous())); +} + +inline bool +NativeObject::canRemoveLastProperty() +{ + /* + * Check that the information about the object stored in the last + * property's base shape is consistent with that stored in the previous + * shape. If not consistent, then the last property cannot be removed as it + * will induce a change in the object itself, and the object must be + * converted to dictionary mode instead. See BaseShape comment in jsscope.h + */ + MOZ_ASSERT(!inDictionaryMode()); + Shape* previous = lastProperty()->previous().get(); + return previous->getObjectFlags() == lastProperty()->getObjectFlags(); +} + +inline void +NativeObject::setShouldConvertDoubleElements() +{ + MOZ_ASSERT(is<ArrayObject>() && !hasEmptyElements()); + getElementsHeader()->setShouldConvertDoubleElements(); +} + +inline void +NativeObject::clearShouldConvertDoubleElements() +{ + MOZ_ASSERT(is<ArrayObject>() && !hasEmptyElements()); + getElementsHeader()->clearShouldConvertDoubleElements(); +} + +inline void +NativeObject::setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +{ + // Avoid a slow AddTypePropertyId call if the type is the same as the type + // of the previous element. + TypeSet::Type thisType = TypeSet::GetValueType(val); + if (index == 0 || TypeSet::GetValueType(elements_[index - 1]) != thisType) + AddTypePropertyId(cx, this, JSID_VOID, thisType); + setDenseElementMaybeConvertDouble(index, val); +} + +inline void +NativeObject::initDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val) +{ + MOZ_ASSERT(!shouldConvertDoubleElements()); + if (val.isMagic(JS_ELEMENTS_HOLE)) + markDenseElementsNotPacked(cx); + else + AddTypePropertyId(cx, this, JSID_VOID, val); + initDenseElement(index, val); +} + +inline void +NativeObject::setDenseElementHole(ExclusiveContext* cx, uint32_t index) +{ + MarkObjectGroupFlags(cx, this, OBJECT_FLAG_NON_PACKED); + setDenseElement(index, MagicValue(JS_ELEMENTS_HOLE)); +} + +/* static */ inline void +NativeObject::removeDenseElementForSparseIndex(ExclusiveContext* cx, + HandleNativeObject obj, uint32_t index) +{ + MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_NON_PACKED | OBJECT_FLAG_SPARSE_INDEXES); + if (obj->containsDenseElement(index)) + obj->setDenseElementUnchecked(index, MagicValue(JS_ELEMENTS_HOLE)); +} + +inline bool +NativeObject::writeToIndexWouldMarkNotPacked(uint32_t index) +{ + return getElementsHeader()->initializedLength < index; +} + +inline void +NativeObject::markDenseElementsNotPacked(ExclusiveContext* cx) +{ + MOZ_ASSERT(isNative()); + MarkObjectGroupFlags(cx, this, OBJECT_FLAG_NON_PACKED); +} + +inline void +NativeObject::ensureDenseInitializedLengthNoPackedCheck(ExclusiveContext* cx, uint32_t index, + uint32_t extra) +{ + MOZ_ASSERT(!denseElementsAreCopyOnWrite()); + MOZ_ASSERT(!denseElementsAreFrozen()); + + /* + * Ensure that the array's contents have been initialized up to index, and + * mark the elements through 'index + extra' as initialized in preparation + * for a write. + */ + MOZ_ASSERT(index + extra <= getDenseCapacity()); + uint32_t& initlen = getElementsHeader()->initializedLength; + + if (initlen < index + extra) { + size_t offset = initlen; + for (HeapSlot* sp = elements_ + initlen; + sp != elements_ + (index + extra); + sp++, offset++) + { + sp->init(this, HeapSlot::Element, offset, MagicValue(JS_ELEMENTS_HOLE)); + } + initlen = index + extra; + } +} + +inline void +NativeObject::ensureDenseInitializedLength(ExclusiveContext* cx, uint32_t index, uint32_t extra) +{ + if (writeToIndexWouldMarkNotPacked(index)) + markDenseElementsNotPacked(cx); + ensureDenseInitializedLengthNoPackedCheck(cx, index, extra); +} + +DenseElementResult +NativeObject::extendDenseElements(ExclusiveContext* cx, + uint32_t requiredCapacity, uint32_t extra) +{ + MOZ_ASSERT(!denseElementsAreCopyOnWrite()); + MOZ_ASSERT(!denseElementsAreFrozen()); + + /* + * Don't grow elements for non-extensible objects or watched objects. Dense + * elements can be added/written with no extensible or watchpoint checks as + * long as there is capacity for them. + */ + if (!nonProxyIsExtensible() || watched()) { + MOZ_ASSERT(getDenseCapacity() == 0); + return DenseElementResult::Incomplete; + } + + /* + * Don't grow elements for objects which already have sparse indexes. + * This avoids needing to count non-hole elements in willBeSparseElements + * every time a new index is added. + */ + if (isIndexed()) + return DenseElementResult::Incomplete; + + /* + * We use the extra argument also as a hint about number of non-hole + * elements to be inserted. + */ + if (requiredCapacity > MIN_SPARSE_INDEX && + willBeSparseElements(requiredCapacity, extra)) { + return DenseElementResult::Incomplete; + } + + if (!growElements(cx, requiredCapacity)) + return DenseElementResult::Failure; + + return DenseElementResult::Success; +} + +inline DenseElementResult +NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t extra) +{ + MOZ_ASSERT(isNative()); + + if (writeToIndexWouldMarkNotPacked(index)) + markDenseElementsNotPacked(cx); + + if (!maybeCopyElementsForWrite(cx)) + return DenseElementResult::Failure; + + uint32_t currentCapacity = getDenseCapacity(); + + uint32_t requiredCapacity; + if (extra == 1) { + /* Optimize for the common case. */ + if (index < currentCapacity) { + ensureDenseInitializedLengthNoPackedCheck(cx, index, 1); + return DenseElementResult::Success; + } + requiredCapacity = index + 1; + if (requiredCapacity == 0) { + /* Overflow. */ + return DenseElementResult::Incomplete; + } + } else { + requiredCapacity = index + extra; + if (requiredCapacity < index) { + /* Overflow. */ + return DenseElementResult::Incomplete; + } + if (requiredCapacity <= currentCapacity) { + ensureDenseInitializedLengthNoPackedCheck(cx, index, extra); + return DenseElementResult::Success; + } + } + + DenseElementResult result = extendDenseElements(cx, requiredCapacity, extra); + if (result != DenseElementResult::Success) + return result; + + ensureDenseInitializedLengthNoPackedCheck(cx, index, extra); + return DenseElementResult::Success; +} + +inline Value +NativeObject::getDenseOrTypedArrayElement(uint32_t idx) +{ + if (is<TypedArrayObject>()) + return as<TypedArrayObject>().getElement(idx); + return getDenseElement(idx); +} + +/* static */ inline NativeObject* +NativeObject::copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap, + HandleNativeObject templateObject) +{ + RootedShape shape(cx, templateObject->lastProperty()); + RootedObjectGroup group(cx, templateObject->group()); + MOZ_ASSERT(!templateObject->denseElementsAreCopyOnWrite()); + + JSObject* baseObj = create(cx, kind, heap, shape, group); + if (!baseObj) + return nullptr; + NativeObject* obj = &baseObj->as<NativeObject>(); + + size_t span = shape->slotSpan(); + if (span) { + uint32_t numFixed = templateObject->numFixedSlots(); + const Value* fixed = &templateObject->getSlot(0); + // Only copy elements which are registered in the shape, even if the + // number of fixed slots is larger. + if (span < numFixed) + numFixed = span; + obj->copySlotRange(0, fixed, numFixed); + + if (numFixed < span) { + uint32_t numSlots = span - numFixed; + const Value* slots = &templateObject->getSlot(numFixed); + obj->copySlotRange(numFixed, slots, numSlots); + } + } + + return obj; +} + +inline void +NativeObject::setSlotWithType(ExclusiveContext* cx, Shape* shape, + const Value& value, bool overwriting) +{ + setSlot(shape->slot(), value); + + if (overwriting) + shape->setOverwritten(); + + AddTypePropertyId(cx, this, shape->propid(), value); +} + +inline void +NativeObject::updateShapeAfterMovingGC() +{ + Shape* shape = shape_.unbarrieredGet(); + if (IsForwarded(shape)) + shape_.unsafeSet(Forwarded(shape)); +} + +/* Make an object with pregenerated shape from a NEWOBJECT bytecode. */ +static inline PlainObject* +CopyInitializerObject(JSContext* cx, HandlePlainObject baseobj, NewObjectKind newKind = GenericObject) +{ + MOZ_ASSERT(!baseobj->inDictionaryMode()); + + gc::AllocKind allocKind = gc::GetGCObjectFixedSlotsKind(baseobj->numFixedSlots()); + allocKind = gc::GetBackgroundAllocKind(allocKind); + MOZ_ASSERT_IF(baseobj->isTenured(), allocKind == baseobj->asTenured().getAllocKind()); + RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind)); + if (!obj) + return nullptr; + + if (!obj->setLastProperty(cx, baseobj->lastProperty())) + return nullptr; + + return obj; +} + +inline NativeObject* +NewNativeObjectWithGivenTaggedProto(ExclusiveContext* cx, const Class* clasp, + Handle<TaggedProto> proto, + gc::AllocKind allocKind, NewObjectKind newKind) +{ + return MaybeNativeObject(NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind, + newKind)); +} + +inline NativeObject* +NewNativeObjectWithGivenTaggedProto(ExclusiveContext* cx, const Class* clasp, + Handle<TaggedProto> proto, + NewObjectKind newKind = GenericObject) +{ + return MaybeNativeObject(NewObjectWithGivenTaggedProto(cx, clasp, proto, newKind)); +} + +inline NativeObject* +NewNativeObjectWithGivenProto(ExclusiveContext* cx, const Class* clasp, + HandleObject proto, + gc::AllocKind allocKind, NewObjectKind newKind) +{ + return MaybeNativeObject(NewObjectWithGivenProto(cx, clasp, proto, allocKind, newKind)); +} + +inline NativeObject* +NewNativeObjectWithGivenProto(ExclusiveContext* cx, const Class* clasp, + HandleObject proto, + NewObjectKind newKind = GenericObject) +{ + return MaybeNativeObject(NewObjectWithGivenProto(cx, clasp, proto, newKind)); +} + +inline NativeObject* +NewNativeObjectWithClassProto(ExclusiveContext* cx, const Class* clasp, HandleObject proto, + gc::AllocKind allocKind, + NewObjectKind newKind = GenericObject) +{ + return MaybeNativeObject(NewObjectWithClassProto(cx, clasp, proto, allocKind, newKind)); +} + +inline NativeObject* +NewNativeObjectWithClassProto(ExclusiveContext* cx, const Class* clasp, HandleObject proto, + NewObjectKind newKind = GenericObject) +{ + return MaybeNativeObject(NewObjectWithClassProto(cx, clasp, proto, newKind)); +} + +/* + * Call obj's resolve hook. + * + * cx and id are the parameters initially passed to the ongoing lookup; + * propp and recursedp are its out parameters. + * + * There are four possible outcomes: + * + * - On failure, report an error or exception and return false. + * + * - If we are already resolving a property of obj, set *recursedp = true, + * and return true. + * + * - If the resolve hook finds or defines the sought property, set propp + * appropriately, set *recursedp = false, and return true. + * + * - Otherwise no property was resolved. Set propp to nullptr and + * *recursedp = false and return true. + */ +static MOZ_ALWAYS_INLINE bool +CallResolveOp(JSContext* cx, HandleNativeObject obj, HandleId id, MutableHandleShape propp, + bool* recursedp) +{ + // Avoid recursion on (obj, id) already being resolved on cx. + AutoResolving resolving(cx, obj, id); + if (resolving.alreadyStarted()) { + // Already resolving id in obj, suppress recursion. + *recursedp = true; + return true; + } + *recursedp = false; + + bool resolved = false; + if (!obj->getClass()->getResolve()(cx, obj, id, &resolved)) + return false; + + if (!resolved) + return true; + + // Assert the mayResolve hook, if there is one, returns true for this + // property. + MOZ_ASSERT_IF(obj->getClass()->getMayResolve(), + obj->getClass()->getMayResolve()(cx->names(), id, obj)); + + if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { + MarkDenseOrTypedArrayElementFound<CanGC>(propp); + return true; + } + + MOZ_ASSERT(!obj->is<TypedArrayObject>()); + + propp.set(obj->lookup(cx, id)); + return true; +} + +static MOZ_ALWAYS_INLINE bool +ClassMayResolveId(const JSAtomState& names, const Class* clasp, jsid id, JSObject* maybeObj) +{ + MOZ_ASSERT_IF(maybeObj, maybeObj->getClass() == clasp); + + if (!clasp->getResolve()) { + // Sanity check: we should only have a mayResolve hook if we have a + // resolve hook. + MOZ_ASSERT(!clasp->getMayResolve(), "Class with mayResolve hook but no resolve hook"); + return false; + } + + if (JSMayResolveOp mayResolve = clasp->getMayResolve()) { + // Tell the analysis our mayResolve hooks won't trigger GC. + JS::AutoSuppressGCAnalysis nogc; + if (!mayResolve(names, id, maybeObj)) + return false; + } + + return true; +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +LookupOwnPropertyInline(ExclusiveContext* cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp, + bool* donep) +{ + // Check for a native dense element. + if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { + MarkDenseOrTypedArrayElementFound<allowGC>(propp); + *donep = true; + return true; + } + + // Check for a typed array element. Integer lookups always finish here + // so that integer properties on the prototype are ignored even for out + // of bounds accesses. + if (obj->template is<TypedArrayObject>()) { + uint64_t index; + if (IsTypedArrayIndex(id, &index)) { + if (index < obj->template as<TypedArrayObject>().length()) { + MarkDenseOrTypedArrayElementFound<allowGC>(propp); + } else { + propp.set(nullptr); + } + *donep = true; + return true; + } + } + + // Check for a native property. + if (Shape* shape = obj->lookup(cx, id)) { + propp.set(shape); + *donep = true; + return true; + } + + // id was not found in obj. Try obj's resolve hook, if any. + if (obj->getClass()->getResolve()) { + if (!cx->shouldBeJSContext() || !allowGC) + return false; + + bool recursed; + if (!CallResolveOp(cx->asJSContext(), + MaybeRooted<NativeObject*, allowGC>::toHandle(obj), + MaybeRooted<jsid, allowGC>::toHandle(id), + MaybeRooted<Shape*, allowGC>::toMutableHandle(propp), + &recursed)) + { + return false; + } + + if (recursed) { + propp.set(nullptr); + *donep = true; + return true; + } + + if (propp) { + *donep = true; + return true; + } + } + + propp.set(nullptr); + *donep = false; + return true; +} + +/* + * Simplified version of LookupOwnPropertyInline that doesn't call resolve + * hooks. + */ +static inline void +NativeLookupOwnPropertyNoResolve(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, + MutableHandleShape result) +{ + // Check for a native dense element. + if (JSID_IS_INT(id) && obj->containsDenseElement(JSID_TO_INT(id))) { + MarkDenseOrTypedArrayElementFound<CanGC>(result); + return; + } + + // Check for a typed array element. + if (obj->is<TypedArrayObject>()) { + uint64_t index; + if (IsTypedArrayIndex(id, &index)) { + if (index < obj->as<TypedArrayObject>().length()) + MarkDenseOrTypedArrayElementFound<CanGC>(result); + else + result.set(nullptr); + return; + } + } + + // Check for a native property. + result.set(obj->lookup(cx, id)); +} + +template <AllowGC allowGC> +static MOZ_ALWAYS_INLINE bool +LookupPropertyInline(ExclusiveContext* cx, + typename MaybeRooted<NativeObject*, allowGC>::HandleType obj, + typename MaybeRooted<jsid, allowGC>::HandleType id, + typename MaybeRooted<JSObject*, allowGC>::MutableHandleType objp, + typename MaybeRooted<Shape*, allowGC>::MutableHandleType propp) +{ + /* NB: The logic of this procedure is implicitly reflected in + * BaselineIC.cpp's |EffectlesslyLookupProperty| logic. + * If this changes, please remember to update the logic there as well. + */ + + /* Search scopes starting with obj and following the prototype link. */ + typename MaybeRooted<NativeObject*, allowGC>::RootType current(cx, obj); + + while (true) { + bool done; + if (!LookupOwnPropertyInline<allowGC>(cx, current, id, propp, &done)) + return false; + if (done) { + if (propp) + objp.set(current); + else + objp.set(nullptr); + return true; + } + + typename MaybeRooted<JSObject*, allowGC>::RootType proto(cx, current->staticPrototype()); + + if (!proto) + break; + if (!proto->isNative()) { + if (!cx->shouldBeJSContext() || !allowGC) + return false; + return LookupProperty(cx->asJSContext(), + MaybeRooted<JSObject*, allowGC>::toHandle(proto), + MaybeRooted<jsid, allowGC>::toHandle(id), + MaybeRooted<JSObject*, allowGC>::toMutableHandle(objp), + MaybeRooted<Shape*, allowGC>::toMutableHandle(propp)); + } + + current = &proto->template as<NativeObject>(); + } + + objp.set(nullptr); + propp.set(nullptr); + return true; +} + +inline bool +ThrowIfNotConstructing(JSContext *cx, const CallArgs &args, const char *builtinName) +{ + if (args.isConstructing()) + return true; + return JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_ERROR, GetErrorMessage, nullptr, + JSMSG_BUILTIN_CTOR_NO_NEW, builtinName); +} + +inline bool +IsPackedArray(JSObject* obj) +{ + return obj->is<ArrayObject>() && !obj->hasLazyGroup() && + !obj->group()->hasAllFlags(OBJECT_FLAG_NON_PACKED) && + obj->as<ArrayObject>().getDenseInitializedLength() == obj->as<ArrayObject>().length(); +} + +} // namespace js + +#endif /* vm_NativeObject_inl_h */ |