/* -*- 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_h #define vm_NativeObject_h #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include #include "jsfriendapi.h" #include "jsobj.h" #include "NamespaceImports.h" #include "gc/Barrier.h" #include "gc/Heap.h" #include "gc/Marking.h" #include "js/Value.h" #include "vm/Shape.h" #include "vm/ShapedObject.h" #include "vm/String.h" #include "vm/TypeInference.h" namespace js { class Shape; class TenuringTracer; /* * To really poison a set of values, using 'magic' or 'undefined' isn't good * enough since often these will just be ignored by buggy code (see bug 629974) * in debug builds and crash in release builds. Instead, we use a safe-for-crash * pointer. */ static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* beg, Value* end) { #ifdef DEBUG for (Value* v = beg; v != end; ++v) v->setObject(*reinterpret_cast(0x48)); #endif } static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(Value* vec, size_t len) { #ifdef DEBUG Debug_SetValueRangeToCrashOnTouch(vec, vec + len); #endif } static MOZ_ALWAYS_INLINE void Debug_SetValueRangeToCrashOnTouch(GCPtrValue* vec, size_t len) { #ifdef DEBUG Debug_SetValueRangeToCrashOnTouch((Value*) vec, len); #endif } static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* vec, uint32_t len) { #ifdef DEBUG Debug_SetValueRangeToCrashOnTouch((Value*) vec, len); #endif } static MOZ_ALWAYS_INLINE void Debug_SetSlotRangeToCrashOnTouch(HeapSlot* begin, HeapSlot* end) { #ifdef DEBUG Debug_SetValueRangeToCrashOnTouch((Value*) begin, end - begin); #endif } class ArrayObject; /* * ES6 20130308 draft 8.4.2.4 ArraySetLength. * * |id| must be "length", |attrs| are the attributes to be used for the newly- * changed length property, |value| is the value for the new length, and * |result| receives an error code if the change is invalid. */ extern bool ArraySetLength(JSContext* cx, Handle obj, HandleId id, unsigned attrs, HandleValue value, ObjectOpResult& result); /* * Elements header used for native objects. The elements component of such objects * offers an efficient representation for all or some of the indexed properties * of the object, using a flat array of Values rather than a shape hierarchy * stored in the object's slots. This structure is immediately followed by an * array of elements, with the elements member in an object pointing to the * beginning of that array (the end of this structure). * See below for usage of this structure. * * The sets of properties represented by an object's elements and slots * are disjoint. The elements contain only indexed properties, while the slots * can contain both named and indexed properties; any indexes in the slots are * distinct from those in the elements. If isIndexed() is false for an object, * all indexed properties (if any) are stored in the dense elements. * * Indexes will be stored in the object's slots instead of its elements in * the following case: * - there are more than MIN_SPARSE_INDEX slots total and the load factor * (COUNT / capacity) is less than 0.25 * - a property is defined that has non-default property attributes. * * We track these pieces of metadata for dense elements: * - The length property as a uint32_t, accessible for array objects with * ArrayObject::{length,setLength}(). This is unused for non-arrays. * - The number of element slots (capacity), gettable with * getDenseCapacity(). * - The array's initialized length, accessible with * getDenseInitializedLength(). * * Holes in the array are represented by MagicValue(JS_ELEMENTS_HOLE) values. * These indicate indexes which are not dense properties of the array. The * property may, however, be held by the object's properties. * * The capacity and length of an object's elements are almost entirely * unrelated! In general the length may be greater than, less than, or equal * to the capacity. The first case occurs with |new Array(100)|. The length * is 100, but the capacity remains 0 (indices below length and above capacity * must be treated as holes) until elements between capacity and length are * set. The other two cases are common, depending upon the number of elements * in an array and the underlying allocator used for element storage. * * The only case in which the capacity and length of an object's elements are * related is when the object is an array with non-writable length. In this * case the capacity is always less than or equal to the length. This permits * JIT code to optimize away the check for non-writable length when assigning * to possibly out-of-range elements: such code already has to check for * |index < capacity|, and fallback code checks for non-writable length. * * The initialized length of an object specifies the number of elements that * have been initialized. All elements above the initialized length are * holes in the object, and the memory for all elements between the initialized * length and capacity is left uninitialized. The initialized length is some * value less than or equal to both the object's length and the object's * capacity. * * There is flexibility in exactly the value the initialized length must hold, * e.g. if an array has length 5, capacity 10, completely empty, it is valid * for the initialized length to be any value between zero and 5, as long as * the in memory values below the initialized length have been initialized with * a hole value. However, in such cases we want to keep the initialized length * as small as possible: if the object is known to have no hole values below * its initialized length, then it is "packed" and can be accessed much faster * by JIT code. * * Elements do not track property creation order, so enumerating the elements * of an object does not necessarily visit indexes in the order they were * created. */ class ObjectElements { public: enum Flags: uint32_t { // Integers written to these elements must be converted to doubles. CONVERT_DOUBLE_ELEMENTS = 0x1, // Present only if these elements correspond to an array with // non-writable length; never present for non-arrays. NONWRITABLE_ARRAY_LENGTH = 0x2, // These elements are shared with another object and must be copied // before they can be changed. A pointer to the original owner of the // elements, which is immutable, is stored immediately after the // elements data. There is one case where elements can be written to // before being copied: when setting the CONVERT_DOUBLE_ELEMENTS flag // the shared elements may change (from ints to doubles) without // making a copy first. COPY_ON_WRITE = 0x4, // For TypedArrays only: this TypedArray's storage is mapping shared // memory. This is a static property of the TypedArray, set when it // is created and never changed. SHARED_MEMORY = 0x8, // These elements are set to integrity level "frozen". FROZEN = 0x10, }; private: friend class ::JSObject; friend class ArrayObject; friend class NativeObject; friend class TenuringTracer; friend bool js::SetIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level); friend bool ArraySetLength(JSContext* cx, Handle obj, HandleId id, unsigned attrs, HandleValue value, ObjectOpResult& result); /* See Flags enum above. */ uint32_t flags; /* * Number of initialized elements. This is <= the capacity, and for arrays * is <= the length. Memory for elements above the initialized length is * uninitialized, but values between the initialized length and the proper * length are conceptually holes. */ uint32_t initializedLength; /* Number of allocated slots. */ uint32_t capacity; /* 'length' property of array objects, unused for other objects. */ uint32_t length; bool shouldConvertDoubleElements() const { return flags & CONVERT_DOUBLE_ELEMENTS; } void setShouldConvertDoubleElements() { // Note: allow isCopyOnWrite() here, see comment above. flags |= CONVERT_DOUBLE_ELEMENTS; } void clearShouldConvertDoubleElements() { MOZ_ASSERT(!isCopyOnWrite()); flags &= ~CONVERT_DOUBLE_ELEMENTS; } bool hasNonwritableArrayLength() const { return flags & NONWRITABLE_ARRAY_LENGTH; } void setNonwritableArrayLength() { MOZ_ASSERT(!isCopyOnWrite()); flags |= NONWRITABLE_ARRAY_LENGTH; } bool isCopyOnWrite() const { return flags & COPY_ON_WRITE; } void clearCopyOnWrite() { MOZ_ASSERT(isCopyOnWrite()); flags &= ~COPY_ON_WRITE; } public: constexpr ObjectElements(uint32_t capacity, uint32_t length) : flags(0), initializedLength(0), capacity(capacity), length(length) {} enum class SharedMemory { IsShared }; constexpr ObjectElements(uint32_t capacity, uint32_t length, SharedMemory shmem) : flags(SHARED_MEMORY), initializedLength(0), capacity(capacity), length(length) {} HeapSlot* elements() { return reinterpret_cast(uintptr_t(this) + sizeof(ObjectElements)); } const HeapSlot* elements() const { return reinterpret_cast(uintptr_t(this) + sizeof(ObjectElements)); } static ObjectElements * fromElements(HeapSlot* elems) { return reinterpret_cast(uintptr_t(elems) - sizeof(ObjectElements)); } bool isSharedMemory() const { return flags & SHARED_MEMORY; } GCPtrNativeObject& ownerObject() const { MOZ_ASSERT(isCopyOnWrite()); return *(GCPtrNativeObject*)(&elements()[initializedLength]); } static int offsetOfFlags() { return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements)); } static int offsetOfInitializedLength() { return int(offsetof(ObjectElements, initializedLength)) - int(sizeof(ObjectElements)); } static int offsetOfCapacity() { return int(offsetof(ObjectElements, capacity)) - int(sizeof(ObjectElements)); } static int offsetOfLength() { return int(offsetof(ObjectElements, length)) - int(sizeof(ObjectElements)); } static bool ConvertElementsToDoubles(JSContext* cx, uintptr_t elements); static bool MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj); static bool FreezeElements(ExclusiveContext* cx, HandleNativeObject obj); bool isFrozen() const { return flags & FROZEN; } void freeze() { MOZ_ASSERT(!isFrozen()); MOZ_ASSERT(!isCopyOnWrite()); flags |= FROZEN; } void markNotFrozen() { MOZ_ASSERT(isFrozen()); MOZ_ASSERT(!isCopyOnWrite()); flags &= ~FROZEN; } uint8_t elementAttributes() const { if (isFrozen()) return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY; return JSPROP_ENUMERATE; } // This is enough slots to store an object of this class. See the static // assertion below. static const size_t VALUES_PER_HEADER = 2; }; static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(ObjectElements), "ObjectElements doesn't fit in the given number of slots"); /* * Shared singletons for objects with no elements. * emptyObjectElementsShared is used only for TypedArrays, when the TA * maps shared memory. */ extern HeapSlot* const emptyObjectElements; extern HeapSlot* const emptyObjectElementsShared; struct Class; class GCMarker; class Shape; class NewObjectCache; #ifdef DEBUG static inline bool IsObjectValueInCompartment(const Value& v, JSCompartment* comp); #endif // Operations which change an object's dense elements can either succeed, fail, // or be unable to complete. The latter is used when the object's elements must // become sparse instead. The enum below is used for such operations. enum class DenseElementResult { Failure, Success, Incomplete }; enum class ShouldUpdateTypes { Update, DontUpdate }; /* * NativeObject specifies the internal implementation of a native object. * * Native objects use ShapedObject::shape_ to record property information. Two * native objects with the same shape are guaranteed to have the same number of * fixed slots. * * Native objects extend the base implementation of an object with storage for * the object's named properties and indexed elements. * * These are stored separately from one another. Objects are followed by a * variable-sized array of values for inline storage, which may be used by * either properties of native objects (fixed slots), by elements (fixed * elements), or by other data for certain kinds of objects, such as * ArrayBufferObjects and TypedArrayObjects. * * Named property storage can be split between fixed slots and a dynamically * allocated array (the slots member). For an object with N fixed slots, shapes * with slots [0..N-1] are stored in the fixed slots, and the remainder are * stored in the dynamic array. If all properties fit in the fixed slots, the * 'slots_' member is nullptr. * * Elements are indexed via the 'elements_' member. This member can point to * either the shared emptyObjectElements and emptyObjectElementsShared singletons, * into the inline value array (the address of the third value, to leave room * for a ObjectElements header;in this case numFixedSlots() is zero) or to * a dynamically allocated array. * * Slots and elements may both be non-empty. The slots may be either names or * indexes; no indexed property will be in both the slots and elements. */ class NativeObject : public ShapedObject { protected: /* Slots for object properties. */ js::HeapSlot* slots_; /* Slots for object dense elements. */ js::HeapSlot* elements_; friend class ::JSObject; private: static void staticAsserts() { static_assert(sizeof(NativeObject) == sizeof(JSObject_Slots0), "native object size must match GC thing size"); static_assert(sizeof(NativeObject) == sizeof(shadow::Object), "shadow interface must match actual implementation"); static_assert(sizeof(NativeObject) % sizeof(Value) == 0, "fixed slots after an object must be aligned"); static_assert(offsetof(NativeObject, group_) == offsetof(shadow::Object, group), "shadow type must match actual type"); static_assert(offsetof(NativeObject, slots_) == offsetof(shadow::Object, slots), "shadow slots must match actual slots"); static_assert(offsetof(NativeObject, elements_) == offsetof(shadow::Object, _1), "shadow placeholder must match actual elements"); static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX, "verify numFixedSlots() bitfield is big enough"); static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) == JSObject::MAX_BYTE_SIZE, "inconsistent maximum object size"); } public: Shape* lastProperty() const { MOZ_ASSERT(shape_); return shape_; } uint32_t propertyCount() const { return lastProperty()->entryCount(); } bool hasShapeTable() const { return lastProperty()->hasTable(); } HeapSlotArray getDenseElements() { return HeapSlotArray(elements_, !getElementsHeader()->isCopyOnWrite()); } HeapSlotArray getDenseElementsAllowCopyOnWrite() { // Backdoor allowing direct access to copy on write elements. return HeapSlotArray(elements_, true); } const Value& getDenseElement(uint32_t idx) const { MOZ_ASSERT(idx < getDenseInitializedLength()); return elements_[idx]; } bool containsDenseElement(uint32_t idx) { return idx < getDenseInitializedLength() && !elements_[idx].isMagic(JS_ELEMENTS_HOLE); } uint32_t getDenseInitializedLength() const { return getElementsHeader()->initializedLength; } uint32_t getDenseCapacity() const { return getElementsHeader()->capacity; } bool isSharedMemory() const { return getElementsHeader()->isSharedMemory(); } // Update the last property, keeping the number of allocated slots in sync // with the object's new slot span. bool setLastProperty(ExclusiveContext* cx, Shape* shape); // As for setLastProperty(), but allows the number of fixed slots to // change. This can only be used when fixed slots are being erased from the // object, and only when the object will not require dynamic slots to cover // the new properties. void setLastPropertyShrinkFixedSlots(Shape* shape); // As for setLastProperty(), but changes the class associated with the // object to a non-native one. This leaves the object with a type and shape // that are (temporarily) inconsistent. void setLastPropertyMakeNonNative(Shape* shape); // Newly-created TypedArrays that map a SharedArrayBuffer are // marked as shared by giving them an ObjectElements that has the // ObjectElements::SHARED_MEMORY flag set. void setIsSharedMemory() { MOZ_ASSERT(elements_ == emptyObjectElements); elements_ = emptyObjectElementsShared; } bool isInWholeCellBuffer() const { const gc::TenuredCell* cell = &asTenured(); gc::ArenaCellSet* cells = cell->arena()->bufferedCells; return cells && cells->hasCell(cell); } protected: #ifdef DEBUG void checkShapeConsistency(); #else void checkShapeConsistency() { } #endif static Shape* replaceWithNewEquivalentShape(ExclusiveContext* cx, HandleNativeObject obj, Shape* existingShape, Shape* newShape = nullptr, bool accessorShape = false); /* * Remove the last property of an object, provided that it is safe to do so * (the shape and previous shape do not carry conflicting information about * the object itself). */ inline void removeLastProperty(ExclusiveContext* cx); inline bool canRemoveLastProperty(); /* * Update the slot span directly for a dictionary object, and allocate * slots to cover the new span if necessary. */ bool setSlotSpan(ExclusiveContext* cx, uint32_t span); bool toDictionaryMode(ExclusiveContext* cx); private: friend class TenuringTracer; /* * Get internal pointers to the range of values starting at start and * running for length. */ void getSlotRangeUnchecked(uint32_t start, uint32_t length, HeapSlot** fixedStart, HeapSlot** fixedEnd, HeapSlot** slotsStart, HeapSlot** slotsEnd) { MOZ_ASSERT(start + length >= start); uint32_t fixed = numFixedSlots(); if (start < fixed) { if (start + length < fixed) { *fixedStart = &fixedSlots()[start]; *fixedEnd = &fixedSlots()[start + length]; *slotsStart = *slotsEnd = nullptr; } else { uint32_t localCopy = fixed - start; *fixedStart = &fixedSlots()[start]; *fixedEnd = &fixedSlots()[start + localCopy]; *slotsStart = &slots_[0]; *slotsEnd = &slots_[length - localCopy]; } } else { *fixedStart = *fixedEnd = nullptr; *slotsStart = &slots_[start - fixed]; *slotsEnd = &slots_[start - fixed + length]; } } void getSlotRange(uint32_t start, uint32_t length, HeapSlot** fixedStart, HeapSlot** fixedEnd, HeapSlot** slotsStart, HeapSlot** slotsEnd) { MOZ_ASSERT(slotInRange(start + length, SENTINEL_ALLOWED)); getSlotRangeUnchecked(start, length, fixedStart, fixedEnd, slotsStart, slotsEnd); } protected: friend class GCMarker; friend class Shape; friend class NewObjectCache; void invalidateSlotRange(uint32_t start, uint32_t length) { #ifdef DEBUG HeapSlot* fixedStart; HeapSlot* fixedEnd; HeapSlot* slotsStart; HeapSlot* slotsEnd; getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd); Debug_SetSlotRangeToCrashOnTouch(fixedStart, fixedEnd); Debug_SetSlotRangeToCrashOnTouch(slotsStart, slotsEnd); #endif /* DEBUG */ } void initializeSlotRange(uint32_t start, uint32_t count); /* * Initialize a flat array of slots to this object at a start slot. The * caller must ensure that are enough slots. */ void initSlotRange(uint32_t start, const Value* vector, uint32_t length); /* * Copy a flat array of slots to this object at a start slot. Caller must * ensure there are enough slots in this object. */ void copySlotRange(uint32_t start, const Value* vector, uint32_t length); #ifdef DEBUG enum SentinelAllowed { SENTINEL_NOT_ALLOWED, SENTINEL_ALLOWED }; /* * Check that slot is in range for the object's allocated slots. * If sentinelAllowed then slot may equal the slot capacity. */ bool slotInRange(uint32_t slot, SentinelAllowed sentinel = SENTINEL_NOT_ALLOWED) const; #endif /* * Minimum size for dynamically allocated slots in normal Objects. * ArrayObjects don't use this limit and can have a lower slot capacity, * since they normally don't have a lot of slots. */ static const uint32_t SLOT_CAPACITY_MIN = 8; HeapSlot* fixedSlots() const { return reinterpret_cast(uintptr_t(this) + sizeof(NativeObject)); } public: static MOZ_MUST_USE bool generateOwnShape(ExclusiveContext* cx, HandleNativeObject obj, Shape* newShape = nullptr) { return replaceWithNewEquivalentShape(cx, obj, obj->lastProperty(), newShape); } static MOZ_MUST_USE bool shadowingShapeChange(ExclusiveContext* cx, HandleNativeObject obj, const Shape& shape); static bool clearFlag(ExclusiveContext* cx, HandleNativeObject obj, BaseShape::Flag flag); // The maximum number of slots in an object. // |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow // int32_t (see slotsSizeMustNotOverflow). static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1; static void slotsSizeMustNotOverflow() { static_assert(NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value), "every caller of this method requires that a slot " "number (or slot count) count multiplied by " "sizeof(Value) can't overflow uint32_t (and sometimes " "int32_t, too)"); } uint32_t numFixedSlots() const { return reinterpret_cast(this)->numFixedSlots(); } uint32_t numUsedFixedSlots() const { uint32_t nslots = lastProperty()->slotSpan(getClass()); return Min(nslots, numFixedSlots()); } uint32_t numFixedSlotsForCompilation() const; uint32_t slotSpan() const { if (inDictionaryMode()) return lastProperty()->base()->slotSpan(); return lastProperty()->slotSpan(); } /* Whether a slot is at a fixed offset from this object. */ bool isFixedSlot(size_t slot) { return slot < numFixedSlots(); } /* Index into the dynamic slots array to use for a dynamic slot. */ size_t dynamicSlotIndex(size_t slot) { MOZ_ASSERT(slot >= numFixedSlots()); return slot - numFixedSlots(); } /* * Grow or shrink slots immediately before changing the slot span. * The number of allocated slots is not stored explicitly, and changes to * the slots must track changes in the slot span. */ bool growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount); void shrinkSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount); /* * This method is static because it's called from JIT code. On OOM, returns * false without leaving a pending exception on the context. */ static bool growSlotsDontReportOOM(ExclusiveContext* cx, NativeObject* obj, uint32_t newCount); bool hasDynamicSlots() const { return !!slots_; } /* Compute dynamicSlotsCount() for this object. */ uint32_t numDynamicSlots() const { return dynamicSlotsCount(numFixedSlots(), slotSpan(), getClass()); } bool empty() const { return lastProperty()->isEmptyShape(); } Shape* lookup(ExclusiveContext* cx, jsid id); Shape* lookup(ExclusiveContext* cx, PropertyName* name) { return lookup(cx, NameToId(name)); } bool contains(ExclusiveContext* cx, jsid id) { return lookup(cx, id) != nullptr; } bool contains(ExclusiveContext* cx, PropertyName* name) { return lookup(cx, name) != nullptr; } bool contains(ExclusiveContext* cx, Shape* shape) { return lookup(cx, shape->propid()) == shape; } bool containsShapeOrElement(ExclusiveContext* cx, jsid id) { if (JSID_IS_INT(id) && containsDenseElement(JSID_TO_INT(id))) return true; return contains(cx, id); } /* Contextless; can be called from other pure code. */ Shape* lookupPure(jsid id); Shape* lookupPure(PropertyName* name) { return lookupPure(NameToId(name)); } bool containsPure(jsid id) { return lookupPure(id) != nullptr; } bool containsPure(PropertyName* name) { return containsPure(NameToId(name)); } bool containsPure(Shape* shape) { return lookupPure(shape->propid()) == shape; } /* * Allocate and free an object slot. * * FIXME: bug 593129 -- slot allocation should be done by object methods * after calling object-parameter-free shape methods, avoiding coupling * logic across the object vs. shape module wall. */ static bool allocSlot(ExclusiveContext* cx, HandleNativeObject obj, uint32_t* slotp); void freeSlot(ExclusiveContext* cx, uint32_t slot); private: static Shape* getChildPropertyOnDictionary(ExclusiveContext* cx, HandleNativeObject obj, HandleShape parent, MutableHandle child); static Shape* getChildProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleShape parent, MutableHandle child); public: /* Add a property whose id is not yet in this scope. */ static Shape* addProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs, unsigned flags, bool allowDictionary = true); /* Add a data property whose id is not yet in this scope. */ static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, jsid id_, uint32_t slot, unsigned attrs); static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, HandlePropertyName name, uint32_t slot, unsigned attrs); /* Add or overwrite a property for id in this scope. */ static Shape* putProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs, unsigned flags); static inline Shape* putProperty(ExclusiveContext* cx, HandleObject obj, PropertyName* name, JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs, unsigned flags); /* Change the given property into a sibling with the same id in this scope. */ static Shape* changeProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleShape shape, unsigned attrs, JSGetterOp getter, JSSetterOp setter); /* Remove the property named by id from this object. */ static bool removeProperty(ExclusiveContext* cx, HandleNativeObject obj, jsid id); /* Clear the scope, making it empty. */ static void clear(ExclusiveContext* cx, HandleNativeObject obj); protected: /* * Internal helper that adds a shape not yet mapped by this object. * * Notes: * 1. getter and setter must be normalized based on flags (see jsscope.cpp). * 2. Checks for non-extensibility must be done by callers. */ static Shape* addPropertyInternal(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, JSGetterOp getter, JSSetterOp setter, uint32_t slot, unsigned attrs, unsigned flags, ShapeTable::Entry* entry, bool allowDictionary, const AutoKeepShapeTables& keep); static MOZ_MUST_USE bool fillInAfterSwap(JSContext* cx, HandleNativeObject obj, const Vector& values, void* priv); public: // Return true if this object has been converted from shared-immutable // prototype-rooted shape storage to dictionary-shapes in a doubly-linked // list. bool inDictionaryMode() const { return lastProperty()->inDictionary(); } const Value& getSlot(uint32_t slot) const { MOZ_ASSERT(slotInRange(slot)); uint32_t fixed = numFixedSlots(); if (slot < fixed) return fixedSlots()[slot]; return slots_[slot - fixed]; } const HeapSlot* getSlotAddressUnchecked(uint32_t slot) const { uint32_t fixed = numFixedSlots(); if (slot < fixed) return fixedSlots() + slot; return slots_ + (slot - fixed); } HeapSlot* getSlotAddressUnchecked(uint32_t slot) { uint32_t fixed = numFixedSlots(); if (slot < fixed) return fixedSlots() + slot; return slots_ + (slot - fixed); } HeapSlot* getSlotAddress(uint32_t slot) { /* * This can be used to get the address of the end of the slots for the * object, which may be necessary when fetching zero-length arrays of * slots (e.g. for callObjVarArray). */ MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); return getSlotAddressUnchecked(slot); } const HeapSlot* getSlotAddress(uint32_t slot) const { /* * This can be used to get the address of the end of the slots for the * object, which may be necessary when fetching zero-length arrays of * slots (e.g. for callObjVarArray). */ MOZ_ASSERT(slotInRange(slot, SENTINEL_ALLOWED)); return getSlotAddressUnchecked(slot); } HeapSlot& getSlotRef(uint32_t slot) { MOZ_ASSERT(slotInRange(slot)); return *getSlotAddress(slot); } const HeapSlot& getSlotRef(uint32_t slot) const { MOZ_ASSERT(slotInRange(slot)); return *getSlotAddress(slot); } void setSlot(uint32_t slot, const Value& value) { MOZ_ASSERT(slotInRange(slot)); MOZ_ASSERT(IsObjectValueInCompartment(value, compartment())); getSlotRef(slot).set(this, HeapSlot::Slot, slot, value); } void initSlot(uint32_t slot, const Value& value) { MOZ_ASSERT(getSlot(slot).isUndefined()); MOZ_ASSERT(slotInRange(slot)); MOZ_ASSERT(IsObjectValueInCompartment(value, compartment())); initSlotUnchecked(slot, value); } void initSlotUnchecked(uint32_t slot, const Value& value) { getSlotAddressUnchecked(slot)->init(this, HeapSlot::Slot, slot, value); } // MAX_FIXED_SLOTS is the biggest number of fixed slots our GC // size classes will give an object. static const uint32_t MAX_FIXED_SLOTS = 16; protected: inline bool updateSlotsForSpan(ExclusiveContext* cx, size_t oldSpan, size_t newSpan); private: void prepareElementRangeForOverwrite(size_t start, size_t end) { MOZ_ASSERT(end <= getDenseInitializedLength()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); for (size_t i = start; i < end; i++) elements_[i].destroy(); } /* * Trigger the write barrier on a range of slots that will no longer be * reachable. */ void prepareSlotRangeForOverwrite(size_t start, size_t end) { for (size_t i = start; i < end; i++) getSlotAddressUnchecked(i)->destroy(); } public: static bool rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj, uint32_t slotSpan); inline void setSlotWithType(ExclusiveContext* cx, Shape* shape, const Value& value, bool overwriting = true); inline const Value& getReservedSlot(uint32_t index) const { MOZ_ASSERT(index < JSSLOT_FREE(getClass())); return getSlot(index); } const HeapSlot& getReservedSlotRef(uint32_t index) const { MOZ_ASSERT(index < JSSLOT_FREE(getClass())); return getSlotRef(index); } HeapSlot& getReservedSlotRef(uint32_t index) { MOZ_ASSERT(index < JSSLOT_FREE(getClass())); return getSlotRef(index); } void initReservedSlot(uint32_t index, const Value& v) { MOZ_ASSERT(index < JSSLOT_FREE(getClass())); initSlot(index, v); } void setReservedSlot(uint32_t index, const Value& v) { MOZ_ASSERT(index < JSSLOT_FREE(getClass())); setSlot(index, v); } /* For slots which are known to always be fixed, due to the way they are allocated. */ HeapSlot& getFixedSlotRef(uint32_t slot) { MOZ_ASSERT(slot < numFixedSlots()); return fixedSlots()[slot]; } const Value& getFixedSlot(uint32_t slot) const { MOZ_ASSERT(slot < numFixedSlots()); return fixedSlots()[slot]; } void setFixedSlot(uint32_t slot, const Value& value) { MOZ_ASSERT(slot < numFixedSlots()); fixedSlots()[slot].set(this, HeapSlot::Slot, slot, value); } void initFixedSlot(uint32_t slot, const Value& value) { MOZ_ASSERT(slot < numFixedSlots()); fixedSlots()[slot].init(this, HeapSlot::Slot, slot, value); } /* * Get the number of dynamic slots to allocate to cover the properties in * an object with the given number of fixed slots and slot span. The slot * capacity is not stored explicitly, and the allocated size of the slot * array is kept in sync with this count. */ static uint32_t dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class* clasp); static uint32_t dynamicSlotsCount(Shape* shape) { return dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), shape->getObjectClass()); } /* Elements accessors. */ // The maximum size, in sizeof(Value), of the allocation used for an // object's dense elements. (This includes space used to store an // ObjectElements instance.) // |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow // int32_t (see elementsSizeMustNotOverflow). static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1; // The maximum number of usable dense elements in an object. static const uint32_t MAX_DENSE_ELEMENTS_COUNT = MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER; static void elementsSizeMustNotOverflow() { static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value), "every caller of this method require that an element " "count multiplied by sizeof(Value) can't overflow " "uint32_t (and sometimes int32_t ,too)"); } ObjectElements * getElementsHeader() const { return ObjectElements::fromElements(elements_); } /* Accessors for elements. */ bool ensureElements(ExclusiveContext* cx, uint32_t capacity) { MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); if (capacity > getDenseCapacity()) return growElements(cx, capacity); return true; } static bool goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqAllocated, uint32_t length, uint32_t* goodAmount); bool growElements(ExclusiveContext* cx, uint32_t newcap); void shrinkElements(ExclusiveContext* cx, uint32_t cap); void setDynamicElements(ObjectElements* header) { MOZ_ASSERT(!hasDynamicElements()); elements_ = header->elements(); MOZ_ASSERT(hasDynamicElements()); } static bool CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj); bool maybeCopyElementsForWrite(ExclusiveContext* cx) { if (denseElementsAreCopyOnWrite()) return CopyElementsForWrite(cx, this); return true; } private: inline void ensureDenseInitializedLengthNoPackedCheck(ExclusiveContext* cx, uint32_t index, uint32_t extra); // Run a post write barrier that encompasses multiple contiguous elements in a // single step. inline void elementsRangeWriteBarrierPost(uint32_t start, uint32_t count) { for (size_t i = 0; i < count; i++) { const Value& v = elements_[start + i]; if (v.isObject() && IsInsideNursery(&v.toObject())) { JS::shadow::Runtime* shadowRuntime = shadowRuntimeFromMainThread(); shadowRuntime->gcStoreBufferPtr()->putSlot(this, HeapSlot::Element, start + i, count - i); return; } } } // See the comment over setDenseElementUnchecked, this applies in the same way. void setDenseInitializedLengthUnchecked(uint32_t length) { MOZ_ASSERT(length <= getDenseCapacity()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength); getElementsHeader()->initializedLength = length; } // Use this function with care. This is done to allow sparsifying frozen // objects, but should only be called in a few places, and should be // audited carefully! void setDenseElementUnchecked(uint32_t index, const Value& val) { MOZ_ASSERT(index < getDenseInitializedLength()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); elements_[index].set(this, HeapSlot::Element, index, val); } public: void setDenseInitializedLength(uint32_t length) { MOZ_ASSERT(!denseElementsAreFrozen()); setDenseInitializedLengthUnchecked(length); } inline void ensureDenseInitializedLength(ExclusiveContext* cx, uint32_t index, uint32_t extra); void setDenseElement(uint32_t index, const Value& val) { MOZ_ASSERT(!denseElementsAreFrozen()); setDenseElementUnchecked(index, val); } void initDenseElement(uint32_t index, const Value& val) { MOZ_ASSERT(index < getDenseInitializedLength()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); elements_[index].init(this, HeapSlot::Element, index, val); } void setDenseElementMaybeConvertDouble(uint32_t index, const Value& val) { if (val.isInt32() && shouldConvertDoubleElements()) setDenseElement(index, DoubleValue(val.toInt32())); else setDenseElement(index, val); } inline void setDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val); inline void initDenseElementWithType(ExclusiveContext* cx, uint32_t index, const Value& val); inline void setDenseElementHole(ExclusiveContext* cx, uint32_t index); static inline void removeDenseElementForSparseIndex(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index); inline Value getDenseOrTypedArrayElement(uint32_t idx); void copyDenseElements(uint32_t dstStart, const Value* src, uint32_t count) { MOZ_ASSERT(dstStart + count <= getDenseCapacity()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); if (JS::shadow::Zone::asShadowZone(zone())->needsIncrementalBarrier()) { for (uint32_t i = 0; i < count; ++i) elements_[dstStart + i].set(this, HeapSlot::Element, dstStart + i, src[i]); } else { memcpy(reinterpret_cast(&elements_[dstStart]), src, count * sizeof(Value)); elementsRangeWriteBarrierPost(dstStart, count); } } void initDenseElements(uint32_t dstStart, const Value* src, uint32_t count) { MOZ_ASSERT(dstStart + count <= getDenseCapacity()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); memcpy(reinterpret_cast(&elements_[dstStart]), src, count * sizeof(Value)); elementsRangeWriteBarrierPost(dstStart, count); } void moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count) { MOZ_ASSERT(dstStart + count <= getDenseCapacity()); MOZ_ASSERT(srcStart + count <= getDenseInitializedLength()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); /* * Using memmove here would skip write barriers. Also, we need to consider * an array containing [A, B, C], in the following situation: * * 1. Incremental GC marks slot 0 of array (i.e., A), then returns to JS code. * 2. JS code moves slots 1..2 into slots 0..1, so it contains [B, C, C]. * 3. Incremental GC finishes by marking slots 1 and 2 (i.e., C). * * Since normal marking never happens on B, it is very important that the * write barrier is invoked here on B, despite the fact that it exists in * the array before and after the move. */ if (JS::shadow::Zone::asShadowZone(zone())->needsIncrementalBarrier()) { if (dstStart < srcStart) { HeapSlot* dst = elements_ + dstStart; HeapSlot* src = elements_ + srcStart; for (uint32_t i = 0; i < count; i++, dst++, src++) dst->set(this, HeapSlot::Element, dst - elements_, *src); } else { HeapSlot* dst = elements_ + dstStart + count - 1; HeapSlot* src = elements_ + srcStart + count - 1; for (uint32_t i = 0; i < count; i++, dst--, src--) dst->set(this, HeapSlot::Element, dst - elements_, *src); } } else { memmove(elements_ + dstStart, elements_ + srcStart, count * sizeof(HeapSlot)); elementsRangeWriteBarrierPost(dstStart, count); } } void moveDenseElementsNoPreBarrier(uint32_t dstStart, uint32_t srcStart, uint32_t count) { MOZ_ASSERT(!shadowZone()->needsIncrementalBarrier()); MOZ_ASSERT(dstStart + count <= getDenseCapacity()); MOZ_ASSERT(srcStart + count <= getDenseCapacity()); MOZ_ASSERT(!denseElementsAreCopyOnWrite()); MOZ_ASSERT(!denseElementsAreFrozen()); memmove(elements_ + dstStart, elements_ + srcStart, count * sizeof(HeapSlot)); elementsRangeWriteBarrierPost(dstStart, count); } inline DenseElementResult setOrExtendDenseElements(JSContext* cx, uint32_t start, const Value* vp, uint32_t count, ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update); bool shouldConvertDoubleElements() { return getElementsHeader()->shouldConvertDoubleElements(); } inline void setShouldConvertDoubleElements(); inline void clearShouldConvertDoubleElements(); bool denseElementsAreCopyOnWrite() { return getElementsHeader()->isCopyOnWrite(); } bool denseElementsAreFrozen() { return getElementsHeader()->isFrozen(); } /* Packed information for this object's elements. */ inline bool writeToIndexWouldMarkNotPacked(uint32_t index); inline void markDenseElementsNotPacked(ExclusiveContext* cx); // Ensures that the object can hold at least index + extra elements. This // returns DenseElement_Success on success, DenseElement_Failed on failure // to grow the array, or DenseElement_Incomplete when the object is too // sparse to grow (this includes the case of index + extra overflow). In // the last two cases the object is kept intact. inline DenseElementResult ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t extra); inline DenseElementResult extendDenseElements(ExclusiveContext* cx, uint32_t requiredCapacity, uint32_t extra); /* Convert a single dense element to a sparse property. */ static bool sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index); /* Convert all dense elements to sparse properties. */ static bool sparsifyDenseElements(ExclusiveContext* cx, HandleNativeObject obj); /* Small objects are dense, no matter what. */ static const uint32_t MIN_SPARSE_INDEX = 1000; /* * Element storage for an object will be sparse if fewer than 1/8 indexes * are filled in. */ static const unsigned SPARSE_DENSITY_RATIO = 8; /* * Check if after growing the object's elements will be too sparse. * newElementsHint is an estimated number of elements to be added. */ bool willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint); /* * After adding a sparse index to obj, see if it should be converted to use * dense elements. */ static DenseElementResult maybeDensifySparseElements(ExclusiveContext* cx, HandleNativeObject obj); inline HeapSlot* fixedElements() const { static_assert(2 * sizeof(Value) == sizeof(ObjectElements), "when elements are stored inline, the first two " "slots will hold the ObjectElements header"); return &fixedSlots()[2]; } #ifdef DEBUG bool canHaveNonEmptyElements(); #endif void setFixedElements() { MOZ_ASSERT(canHaveNonEmptyElements()); elements_ = fixedElements(); } inline bool hasDynamicElements() const { /* * Note: for objects with zero fixed slots this could potentially give * a spurious 'true' result, if the end of this object is exactly * aligned with the end of its arena and dynamic slots are allocated * immediately afterwards. Such cases cannot occur for dense arrays * (which have at least two fixed slots) and can only result in a leak. */ return !hasEmptyElements() && elements_ != fixedElements(); } inline bool hasFixedElements() const { return elements_ == fixedElements(); } inline bool hasEmptyElements() const { return elements_ == emptyObjectElements || elements_ == emptyObjectElementsShared; } /* * Get a pointer to the unused data in the object's allocation immediately * following this object, for use with objects which allocate a larger size * class than they need and store non-elements data inline. */ inline uint8_t* fixedData(size_t nslots) const; inline void privateWriteBarrierPre(void** oldval); void privateWriteBarrierPost(void** pprivate) { gc::Cell** cellp = reinterpret_cast(pprivate); MOZ_ASSERT(cellp); MOZ_ASSERT(*cellp); gc::StoreBuffer* storeBuffer = (*cellp)->storeBuffer(); if (storeBuffer) storeBuffer->putCell(cellp); } /* Private data accessors. */ inline void*& privateRef(uint32_t nfixed) const { /* XXX should be private, not protected! */ /* * The private pointer of an object can hold any word sized value. * Private pointers are stored immediately after the last fixed slot of * the object. */ MOZ_ASSERT(nfixed == numFixedSlots()); MOZ_ASSERT(hasPrivate()); HeapSlot* end = &fixedSlots()[nfixed]; return *reinterpret_cast(end); } bool hasPrivate() const { return getClass()->hasPrivate(); } void* getPrivate() const { return privateRef(numFixedSlots()); } void setPrivate(void* data) { void** pprivate = &privateRef(numFixedSlots()); privateWriteBarrierPre(pprivate); *pprivate = data; } void setPrivateGCThing(gc::Cell* cell) { void** pprivate = &privateRef(numFixedSlots()); privateWriteBarrierPre(pprivate); *pprivate = reinterpret_cast(cell); privateWriteBarrierPost(pprivate); } void setPrivateUnbarriered(void* data) { void** pprivate = &privateRef(numFixedSlots()); *pprivate = data; } void initPrivate(void* data) { privateRef(numFixedSlots()) = data; } /* Access private data for an object with a known number of fixed slots. */ inline void* getPrivate(uint32_t nfixed) const { return privateRef(nfixed); } static inline NativeObject* copy(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap, HandleNativeObject templateObject); void updateShapeAfterMovingGC(); void sweepDictionaryListPointer(); /* JIT Accessors */ static size_t offsetOfElements() { return offsetof(NativeObject, elements_); } static size_t offsetOfFixedElements() { return sizeof(NativeObject) + sizeof(ObjectElements); } static size_t getFixedSlotOffset(size_t slot) { return sizeof(NativeObject) + slot * sizeof(Value); } static size_t getPrivateDataOffset(size_t nfixed) { return getFixedSlotOffset(nfixed); } static size_t offsetOfSlots() { return offsetof(NativeObject, slots_); } }; // Object class for plain native objects created using '{}' object literals, // 'new Object()', 'Object.create', etc. class PlainObject : public NativeObject { public: static const js::Class class_; }; inline void NativeObject::privateWriteBarrierPre(void** oldval) { JS::shadow::Zone* shadowZone = this->shadowZoneFromAnyThread(); if (shadowZone->needsIncrementalBarrier() && *oldval && getClass()->hasTrace()) getClass()->doTrace(shadowZone->barrierTracer(), this); } #ifdef DEBUG static inline bool IsObjectValueInCompartment(const Value& v, JSCompartment* comp) { if (!v.isObject()) return true; return v.toObject().compartment() == comp; } #endif /*** Standard internal methods *******************************************************************/ /* * These functions should follow the algorithms in ES6 draft rev 29 section 9.1 * ("Ordinary Object Internal Methods"). It's an ongoing project. * * Many native objects are not "ordinary" in ES6, so these functions also have * to serve some of the special needs of Functions (9.2, 9.3, 9.4.1), Arrays * (9.4.2), Strings (9.4.3), and so on. */ extern bool NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, Handle desc, ObjectOpResult& result); extern bool NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, HandleValue value, JSGetterOp getter, JSSetterOp setter, unsigned attrs, ObjectOpResult& result); extern bool NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name, HandleValue value, GetterOp getter, SetterOp setter, unsigned attrs, ObjectOpResult& result); extern bool NativeDefineElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index, HandleValue value, JSGetterOp getter, JSSetterOp setter, unsigned attrs, ObjectOpResult& result); /* If the result out-param is omitted, throw on failure. */ extern bool NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, HandleId id, HandleValue value, JSGetterOp getter, JSSetterOp setter, unsigned attrs); extern bool NativeDefineProperty(ExclusiveContext* cx, HandleNativeObject obj, PropertyName* name, HandleValue value, JSGetterOp getter, JSSetterOp setter, unsigned attrs); extern bool NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, bool* foundp); extern bool NativeGetOwnPropertyDescriptor(JSContext* cx, HandleNativeObject obj, HandleId id, MutableHandle desc); extern bool NativeGetProperty(JSContext* cx, HandleNativeObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp); extern bool NativeGetPropertyNoGC(JSContext* cx, NativeObject* obj, const Value& receiver, jsid id, Value* vp); inline bool NativeGetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, MutableHandleValue vp) { RootedValue receiver(cx, ObjectValue(*obj)); return NativeGetProperty(cx, obj, receiver, id, vp); } bool SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result); bool SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result); /* * Indicates whether an assignment operation is qualified (`x.y = 0`) or * unqualified (`y = 0`). In strict mode, the latter is an error if no such * variable already exists. * * Used as an argument to NativeSetProperty. */ enum QualifiedBool { Unqualified = 0, Qualified = 1 }; extern bool NativeSetProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleValue v, HandleValue receiver, QualifiedBool qualified, ObjectOpResult& result); extern bool NativeSetElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v, HandleValue receiver, ObjectOpResult& result); extern bool NativeDeleteProperty(JSContext* cx, HandleNativeObject obj, HandleId id, ObjectOpResult& result); /*** SpiderMonkey nonstandard internal methods ***************************************************/ template extern bool NativeLookupOwnProperty(ExclusiveContext* cx, typename MaybeRooted::HandleType obj, typename MaybeRooted::HandleType id, typename MaybeRooted::MutableHandleType propp); /* * Get a property from `receiver`, after having already done a lookup and found * the property on a native object `obj`. * * `shape` must not be null and must not be an implicit dense property. It must * be present in obj's shape chain. */ extern bool NativeGetExistingProperty(JSContext* cx, HandleObject receiver, HandleNativeObject obj, HandleShape shape, MutableHandleValue vp); /* * */ extern bool GetPropertyForNameLookup(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp); } /* namespace js */ template <> inline bool JSObject::is() const { return isNative(); } namespace js { // Alternate to JSObject::as() that tolerates null pointers. inline NativeObject* MaybeNativeObject(JSObject* obj) { return obj ? &obj->as() : nullptr; } // Defined in NativeObject-inl.h. bool IsPackedArray(JSObject* obj); } // namespace js /*** Inline functions declared in jsobj.h that use the native declarations above *****************/ inline bool js::HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) { if (HasPropertyOp op = obj->getOpsHasProperty()) return op(cx, obj, id, foundp); return NativeHasProperty(cx, obj.as(), id, foundp); } inline bool js::GetProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, MutableHandleValue vp) { if (GetPropertyOp op = obj->getOpsGetProperty()) return op(cx, obj, receiver, id, vp); return NativeGetProperty(cx, obj.as(), receiver, id, vp); } inline bool js::GetPropertyNoGC(JSContext* cx, JSObject* obj, const Value& receiver, jsid id, Value* vp) { if (obj->getOpsGetProperty()) return false; return NativeGetPropertyNoGC(cx, &obj->as(), receiver, id, vp); } inline bool js::SetProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) { if (obj->getOpsSetProperty()) return JSObject::nonNativeSetProperty(cx, obj, id, v, receiver, result); return NativeSetProperty(cx, obj.as(), id, v, receiver, Qualified, result); } inline bool js::SetElement(JSContext* cx, HandleObject obj, uint32_t index, HandleValue v, HandleValue receiver, ObjectOpResult& result) { if (obj->getOpsSetProperty()) return JSObject::nonNativeSetElement(cx, obj, index, v, receiver, result); return NativeSetElement(cx, obj.as(), index, v, receiver, result); } #endif /* vm_NativeObject_h */