summaryrefslogtreecommitdiffstats
path: root/js/src/vm/NativeObject-inl.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/NativeObject-inl.h')
-rw-r--r--js/src/vm/NativeObject-inl.h612
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 */