diff options
Diffstat (limited to 'js/src/vm/JSObject.h')
-rw-r--r-- | js/src/vm/JSObject.h | 1133 |
1 files changed, 1133 insertions, 0 deletions
diff --git a/js/src/vm/JSObject.h b/js/src/vm/JSObject.h new file mode 100644 index 0000000000..bbd3d98e1c --- /dev/null +++ b/js/src/vm/JSObject.h @@ -0,0 +1,1133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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_JSObject_h +#define vm_JSObject_h + +#include "mozilla/MemoryReporting.h" + +#include "jsfriendapi.h" + +#include "js/friend/ErrorMessages.h" // JSErrNum +#include "js/GCVector.h" +#include "js/shadow/Zone.h" // JS::shadow::Zone +#include "js/Wrapper.h" +#include "vm/Shape.h" + +namespace JS { +struct ClassInfo; +} // namespace JS + +namespace js { + +using PropertyDescriptorVector = JS::GCVector<JS::PropertyDescriptor>; +class GCMarker; +class JS_PUBLIC_API GenericPrinter; +class JSONPrinter; +class Nursery; +struct AutoEnterOOMUnsafeRegion; + +namespace gc { +class RelocationOverlay; +} // namespace gc + +/****************************************************************************/ + +class GlobalObject; +class NativeObject; + +enum class IntegrityLevel { Sealed, Frozen }; + +/* + * The NewObjectKind allows an allocation site to specify the lifetime + * requirements that must be fixed at allocation time. + */ +enum NewObjectKind { + /* This is the default. Most objects are generic. */ + GenericObject, + + /* + * Objects which will not benefit from being allocated in the nursery + * (e.g. because they are known to have a long lifetime) may be allocated + * with this kind to place them immediately into the tenured generation. + */ + TenuredObject +}; + +// Forward declarations, required for later friend declarations. +bool PreventExtensions(JSContext* cx, JS::HandleObject obj, + JS::ObjectOpResult& result); +bool SetImmutablePrototype(JSContext* cx, JS::HandleObject obj, + bool* succeeded); + +} /* namespace js */ + +/* + * [SMDOC] JSObject layout + * + * A JavaScript object. + * + * This is the base class for all objects exposed to JS script (as well as some + * objects that are only accessed indirectly). Subclasses add additional fields + * and execution semantics. The runtime class of an arbitrary JSObject is + * identified by JSObject::getClass(). + * + * All objects have a non-null Shape, stored in the cell header, which describes + * the current layout and set of property keys of the object. + * + * Each Shape has a pointer to a BaseShape. The BaseShape contains the object's + * prototype object, its class, and its realm. + * + * NOTE: Some operations can change the contents of an object (including class) + * in-place so avoid assuming an object with same pointer has same class + * as before. + * - JSObject::swap() + */ +class JSObject + : public js::gc::CellWithTenuredGCPointer<js::gc::Cell, js::Shape> { + public: + // The Shape is stored in the cell header. + js::Shape* shape() const { return headerPtr(); } + + // Like shape(), but uses getAtomic to read the header word. + js::Shape* shapeMaybeForwarded() const { return headerPtrAtomic(); } + +#ifndef JS_64BIT + // Ensure fixed slots have 8-byte alignment on 32-bit platforms. + uint32_t padding_; +#endif + + private: + friend class js::GCMarker; + friend class js::GlobalObject; + friend class js::Nursery; + friend class js::gc::RelocationOverlay; + friend bool js::PreventExtensions(JSContext* cx, JS::HandleObject obj, + JS::ObjectOpResult& result); + friend bool js::SetImmutablePrototype(JSContext* cx, JS::HandleObject obj, + bool* succeeded); + + public: + const JSClass* getClass() const { return shape()->getObjectClass(); } + bool hasClass(const JSClass* c) const { return getClass() == c; } + + js::LookupPropertyOp getOpsLookupProperty() const { + return getClass()->getOpsLookupProperty(); + } + js::DefinePropertyOp getOpsDefineProperty() const { + return getClass()->getOpsDefineProperty(); + } + js::HasPropertyOp getOpsHasProperty() const { + return getClass()->getOpsHasProperty(); + } + js::GetPropertyOp getOpsGetProperty() const { + return getClass()->getOpsGetProperty(); + } + js::SetPropertyOp getOpsSetProperty() const { + return getClass()->getOpsSetProperty(); + } + js::GetOwnPropertyOp getOpsGetOwnPropertyDescriptor() const { + return getClass()->getOpsGetOwnPropertyDescriptor(); + } + js::DeletePropertyOp getOpsDeleteProperty() const { + return getClass()->getOpsDeleteProperty(); + } + js::GetElementsOp getOpsGetElements() const { + return getClass()->getOpsGetElements(); + } + JSFunToStringOp getOpsFunToString() const { + return getClass()->getOpsFunToString(); + } + + JS::Compartment* compartment() const { return shape()->compartment(); } + JS::Compartment* maybeCompartment() const { return compartment(); } + + void initShape(js::Shape* shape) { + // Note: use Cell::Zone() instead of zone() because zone() relies on the + // shape we still have to initialize. + MOZ_ASSERT(Cell::zone() == shape->zone()); + initHeaderPtr(shape); + } + void setShape(js::Shape* shape) { + MOZ_ASSERT(maybeCCWRealm() == shape->realm()); + setHeaderPtr(shape); + } + + static bool setFlag(JSContext* cx, JS::HandleObject obj, js::ObjectFlag flag); + + bool hasFlag(js::ObjectFlag flag) const { + return shape()->hasObjectFlag(flag); + } + + bool hasAnyFlag(js::ObjectFlags flags) const { + return shape()->objectFlags().hasAnyFlag(flags); + } + + // Change this object's shape for a prototype mutation. + // + // Note: the caller must ensure the object has a mutable proto, is extensible, + // etc. + static bool setProtoUnchecked(JSContext* cx, JS::HandleObject obj, + js::Handle<js::TaggedProto> proto); + + // An object is marked IsUsedAsPrototype if it is (or was) another object's + // prototype. Optimization heuristics will make use of this flag. + // + // This flag is only relevant for static prototypes. Proxy traps can return + // objects without this flag set. + // + // NOTE: it's important to call setIsUsedAsPrototype *after* initializing the + // object's properties, because that avoids unnecessary shadowing checks and + // reshaping. + // + // See: ReshapeForProtoMutation, ReshapeForShadowedProp + bool isUsedAsPrototype() const { + return hasFlag(js::ObjectFlag::IsUsedAsPrototype); + } + static bool setIsUsedAsPrototype(JSContext* cx, JS::HandleObject obj) { + return setFlag(cx, obj, js::ObjectFlag::IsUsedAsPrototype); + } + + bool useWatchtowerTestingLog() const { + return hasFlag(js::ObjectFlag::UseWatchtowerTestingLog); + } + static bool setUseWatchtowerTestingLog(JSContext* cx, JS::HandleObject obj) { + return setFlag(cx, obj, js::ObjectFlag::UseWatchtowerTestingLog); + } + + bool isGenerationCountedGlobal() const { + return hasFlag(js::ObjectFlag::GenerationCountedGlobal); + } + static bool setGenerationCountedGlobal(JSContext* cx, JS::HandleObject obj) { + return setFlag(cx, obj, js::ObjectFlag::GenerationCountedGlobal); + } + + bool hasFuseProperty() const { + return hasFlag(js::ObjectFlag::HasFuseProperty); + } + static bool setHasFuseProperty(JSContext* cx, JS::HandleObject obj) { + return setFlag(cx, obj, js::ObjectFlag::HasFuseProperty); + } + + // A "qualified" varobj is the object on which "qualified" variable + // declarations (i.e., those defined with "var") are kept. + // + // Conceptually, when a var binding is defined, it is defined on the + // innermost qualified varobj on the scope chain. + // + // Function scopes (CallObjects) are qualified varobjs, and there can be + // no other qualified varobj that is more inner for var bindings in that + // function. As such, all references to local var bindings in a function + // may be statically bound to the function scope. This is subject to + // further optimization. Unaliased bindings inside functions reside + // entirely on the frame, not in CallObjects. + // + // Global scopes are also qualified varobjs. It is possible to statically + // know, for a given script, that are no more inner qualified varobjs, so + // free variable references can be statically bound to the global. + // + // Finally, there are non-syntactic qualified varobjs used by embedders + // (e.g., Gecko and XPConnect), as they often wish to run scripts under a + // scope that captures var bindings. + inline bool isQualifiedVarObj() const; + static bool setQualifiedVarObj(JSContext* cx, JS::HandleObject obj) { + return setFlag(cx, obj, js::ObjectFlag::QualifiedVarObj); + } + + // An "unqualified" varobj is the object on which "unqualified" + // assignments (i.e., bareword assignments for which the LHS does not + // exist on the scope chain) are kept. + inline bool isUnqualifiedVarObj() const; + + // Once the "invalidated teleporting" flag is set for an object, it is never + // cleared and it may cause the JITs to insert additional guards when + // accessing properties on this object. While the flag remains clear, the + // shape teleporting optimization can be used to avoid those extra checks. + // + // The flag is set on the object if either: + // + // * Its own proto was mutated or it was on the proto chain of an object that + // had its proto mutated. + // + // * It was on the proto chain of an object that started shadowing a property + // on this object. + // + // See: + // - ReshapeForProtoMutation + // - ReshapeForShadowedProp + // - ProtoChainSupportsTeleporting + inline bool hasInvalidatedTeleporting() const; + static bool setInvalidatedTeleporting(JSContext* cx, JS::HandleObject obj) { + MOZ_ASSERT(obj->isUsedAsPrototype()); + MOZ_ASSERT(obj->hasStaticPrototype(), + "teleporting as a concept is only applicable to static " + "(not dynamically-computed) prototypes"); + return setFlag(cx, obj, js::ObjectFlag::InvalidatedTeleporting); + } + + /* + * Whether there may be "interesting symbol" properties on this object. An + * interesting symbol is a symbol for which symbol->isInterestingSymbol() + * returns true. + */ + MOZ_ALWAYS_INLINE bool maybeHasInterestingSymbolProperty() const; + + inline bool needsProxyGetSetResultValidation() const; + + /* GC support. */ + + void traceChildren(JSTracer* trc); + + void fixupAfterMovingGC() {} + + static const JS::TraceKind TraceKind = JS::TraceKind::Object; + + static constexpr size_t thingSize(js::gc::AllocKind kind); + + MOZ_ALWAYS_INLINE JS::Zone* zone() const { + MOZ_ASSERT_IF(!isTenured(), nurseryZone() == shape()->zone()); + return shape()->zone(); + } + MOZ_ALWAYS_INLINE JS::shadow::Zone* shadowZone() const { + return JS::shadow::Zone::from(zone()); + } + MOZ_ALWAYS_INLINE JS::Zone* zoneFromAnyThread() const { + MOZ_ASSERT_IF(!isTenured(), + nurseryZoneFromAnyThread() == shape()->zoneFromAnyThread()); + return shape()->zoneFromAnyThread(); + } + MOZ_ALWAYS_INLINE JS::shadow::Zone* shadowZoneFromAnyThread() const { + return JS::shadow::Zone::from(zoneFromAnyThread()); + } + static MOZ_ALWAYS_INLINE void postWriteBarrier(void* cellp, JSObject* prev, + JSObject* next) { + js::gc::PostWriteBarrierImpl<JSObject>(cellp, prev, next); + } + + /* Return the allocKind we would use if we were to tenure this object. */ + js::gc::AllocKind allocKindForTenure(const js::Nursery& nursery) const; + + bool canHaveFixedElements() const; + + size_t tenuredSizeOfThis() const { + MOZ_ASSERT(isTenured()); + return js::gc::Arena::thingSize(asTenured().getAllocKind()); + } + + void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, + JS::ClassInfo* info, + JS::RuntimeSizes* runtimeSizes); + + // We can only use addSizeOfExcludingThis on tenured objects: it assumes it + // can apply mallocSizeOf to bits and pieces of the object, whereas objects + // in the nursery may have those bits and pieces allocated in the nursery + // along with them, and are not each their own malloc blocks. + size_t sizeOfIncludingThisInNursery() const; + +#ifdef DEBUG + static void debugCheckNewObject(js::Shape* shape, js::gc::AllocKind allocKind, + js::gc::Heap heap); +#else + static void debugCheckNewObject(js::Shape* shape, js::gc::AllocKind allocKind, + js::gc::Heap heap) {} +#endif + + /* + * We permit proxies to dynamically compute their prototype if desired. + * (Not all proxies will so desire: in particular, most DOM proxies can + * track their prototype with a single, nullable JSObject*.) If a proxy + * so desires, we store (JSObject*)0x1 in the proto field of the object's + * group. + * + * We offer three ways to get an object's prototype: + * + * 1. obj->staticPrototype() returns the prototype, but it asserts if obj + * is a proxy, and the proxy has opted to dynamically compute its + * prototype using a getPrototype() handler. + * 2. obj->taggedProto() returns a TaggedProto, which can be tested to + * check if the proto is an object, nullptr, or lazily computed. + * 3. js::GetPrototype(cx, obj, &proto) computes the proto of an object. + * If obj is a proxy with dynamically-computed prototype, this code may + * perform arbitrary behavior (allocation, GC, run JS) while computing + * the proto. + */ + + js::TaggedProto taggedProto() const { return shape()->proto(); } + + bool uninlinedIsProxyObject() const; + + JSObject* staticPrototype() const { + MOZ_ASSERT(hasStaticPrototype()); + return taggedProto().toObjectOrNull(); + } + + // Normal objects and a subset of proxies have an uninteresting, static + // (albeit perhaps mutable) [[Prototype]]. For such objects the + // [[Prototype]] is just a value returned when needed for accesses, or + // modified in response to requests. These objects store the + // [[Prototype]] directly within |obj->group()|. + bool hasStaticPrototype() const { return !hasDynamicPrototype(); } + + // The remaining proxies have a [[Prototype]] requiring dynamic computation + // for every access, going through the proxy handler {get,set}Prototype and + // setImmutablePrototype methods. (Wrappers particularly use this to keep + // the wrapper/wrappee [[Prototype]]s consistent.) + bool hasDynamicPrototype() const { + bool dynamic = taggedProto().isDynamic(); + MOZ_ASSERT_IF(dynamic, uninlinedIsProxyObject()); + return dynamic; + } + + // True iff this object's [[Prototype]] is immutable. Must be called only + // on objects with a static [[Prototype]]! + inline bool staticPrototypeIsImmutable() const; + + /* + * Environment chains. + * + * The environment chain of an object is the link in the search path when + * a script does a name lookup on an environment object. For JS internal + * environment objects --- Call, LexicalEnvironment, and WithEnvironment + * --- the chain is stored in the first fixed slot of the object. For + * other environment objects, the chain goes directly to the global. + * + * In code which is not marked hasNonSyntacticScope, environment chains + * can contain only syntactic environment objects (see + * IsSyntacticEnvironment) with a global object at the root as the + * environment of the outermost non-function script. In + * hasNonSyntacticScope code, the environment of the outermost + * non-function script might not be a global object, and can have a mix of + * other objects above it before the global object is reached. + */ + + /* + * Get the enclosing environment of an object. When called on a + * non-EnvironmentObject, this will just be the global (the name + * "enclosing environment" still applies in this situation because + * non-EnvironmentObjects can be on the environment chain). + */ + inline JSObject* enclosingEnvironment() const; + + // Cross-compartment wrappers are not associated with a single realm/global, + // so these methods assert the object is not a CCW. + inline js::GlobalObject& nonCCWGlobal() const; + + JS::Realm* nonCCWRealm() const { + MOZ_ASSERT(!js::UninlinedIsCrossCompartmentWrapper(this)); + return shape()->realm(); + } + bool hasSameRealmAs(JSContext* cx) const; + + // Returns the object's realm even if the object is a CCW (be careful, in + // this case the realm is not very meaningful because wrappers are shared by + // all realms in the compartment). + JS::Realm* maybeCCWRealm() const { return shape()->realm(); } + + /* + * ES5 meta-object properties and operations. + */ + + public: + // Indicates whether a non-proxy is extensible. Don't call on proxies! + // This method really shouldn't exist -- but there are a few internal + // places that want it (JITs and the like), and it'd be a pain to mark them + // all as friends. + inline bool nonProxyIsExtensible() const; + bool uninlinedNonProxyIsExtensible() const; + + public: + /* + * Back to generic stuff. + */ + MOZ_ALWAYS_INLINE bool isCallable() const; + MOZ_ALWAYS_INLINE bool isConstructor() const; + MOZ_ALWAYS_INLINE JSNative callHook() const; + MOZ_ALWAYS_INLINE JSNative constructHook() const; + + bool isBackgroundFinalized() const; + + MOZ_ALWAYS_INLINE void finalize(JS::GCContext* gcx); + + public: + static bool nonNativeSetProperty(JSContext* cx, js::HandleObject obj, + js::HandleId id, js::HandleValue v, + js::HandleValue receiver, + JS::ObjectOpResult& result); + static bool nonNativeSetElement(JSContext* cx, js::HandleObject obj, + uint32_t index, js::HandleValue v, + js::HandleValue receiver, + JS::ObjectOpResult& result); + + static void swap(JSContext* cx, JS::HandleObject a, JS::HandleObject b, + js::AutoEnterOOMUnsafeRegion& oomUnsafe); + + /* + * In addition to the generic object interface provided by JSObject, + * specific types of objects may provide additional operations. To access, + * these addition operations, callers should use the pattern: + * + * if (obj.is<XObject>()) { + * XObject& x = obj.as<XObject>(); + * x.foo(); + * } + * + * These XObject classes form a hierarchy. For example, for a cloned block + * object, the following predicates are true: is<ClonedBlockObject>, + * is<NestedScopeObject> and is<ScopeObject>. Each of these has a + * respective class that derives and adds operations. + * + * A class XObject is defined in a vm/XObject{.h, .cpp, -inl.h} file + * triplet (along with any class YObject that derives XObject). + * + * Note that X represents a low-level representation and does not query the + * [[Class]] property of object defined by the spec: use |JS::GetBuiltinClass| + * for this. + */ + + template <class T> + inline bool is() const { + return getClass() == &T::class_; + } + + template <class T> + T& as() { + MOZ_ASSERT(this->is<T>()); + return *static_cast<T*>(this); + } + + template <class T> + const T& as() const { + MOZ_ASSERT(this->is<T>()); + return *static_cast<const T*>(this); + } + + /* + * True if either this or CheckedUnwrap(this) is an object of class T. + * (Only two objects are checked, regardless of how many wrappers there + * are.) + * + * /!\ Note: This can be true at one point, but false later for the same + * object, thanks to js::NukeCrossCompartmentWrapper and friends. + */ + template <class T> + bool canUnwrapAs(); + + /* + * Unwrap and downcast to class T. + * + * Precondition: `this->canUnwrapAs<T>()`. Note that it's not enough to + * have checked this at some point in the past; if there's any doubt as to + * whether js::Nuke* could have been called in the meantime, check again. + */ + template <class T> + T& unwrapAs(); + + /* + * Tries to unwrap and downcast to class T. Returns nullptr if (and only if) a + * wrapper with a security policy is involved. Crashes in all builds if the + * (possibly unwrapped) object is not of class T (for example, because it's a + * dead wrapper). + */ + template <class T> + inline T* maybeUnwrapAs(); + + /* + * Tries to unwrap and downcast to an object with class |clasp|. Returns + * nullptr if (and only if) a wrapper with a security policy is involved. + * Crashes in all builds if the (possibly unwrapped) object doesn't have class + * |clasp| (for example, because it's a dead wrapper). + */ + inline JSObject* maybeUnwrapAs(const JSClass* clasp); + + /* + * Tries to unwrap and downcast to class T. Returns nullptr if a wrapper with + * a security policy is involved or if the object does not have class T. + */ + template <class T> + T* maybeUnwrapIf(); + +#if defined(DEBUG) || defined(JS_JITSPEW) + void dump() const; + void dump(js::GenericPrinter& out) const; + void dump(js::JSONPrinter& json) const; + + void dumpFields(js::JSONPrinter& json) const; + void dumpStringContent(js::GenericPrinter& out) const; +#endif + + // Maximum size in bytes of a JSObject. +#ifdef JS_64BIT + static constexpr size_t MAX_BYTE_SIZE = + 3 * sizeof(void*) + 16 * sizeof(JS::Value); +#else + static constexpr size_t MAX_BYTE_SIZE = + 4 * sizeof(void*) + 16 * sizeof(JS::Value); +#endif + + protected: + // JIT Accessors. + // + // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler + // to call the method below. + friend class js::jit::MacroAssembler; + + static constexpr size_t offsetOfShape() { return offsetOfHeaderPtr(); } + + private: + JSObject(const JSObject& other) = delete; + void operator=(const JSObject& other) = delete; + + protected: + // For the allocator only, to be used with placement new. + friend class js::gc::GCRuntime; + JSObject() = default; +}; + +template <> +inline bool JSObject::is<JSObject>() const { + return true; +} + +template <typename Wrapper> +template <typename U> +MOZ_ALWAYS_INLINE JS::Handle<U*> js::RootedOperations<JSObject*, Wrapper>::as() + const { + const Wrapper& self = *static_cast<const Wrapper*>(this); + MOZ_ASSERT(self->template is<U>()); + return Handle<U*>::fromMarkedLocation( + reinterpret_cast<U* const*>(self.address())); +} + +template <typename Wrapper> +template <class U> +MOZ_ALWAYS_INLINE JS::Handle<U*> js::HandleOperations<JSObject*, Wrapper>::as() + const { + const JS::Handle<JSObject*>& self = + *static_cast<const JS::Handle<JSObject*>*>(this); + MOZ_ASSERT(self->template is<U>()); + return Handle<U*>::fromMarkedLocation( + reinterpret_cast<U* const*>(self.address())); +} + +template <class T> +bool JSObject::canUnwrapAs() { + static_assert(!std::is_convertible_v<T*, js::Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (is<T>()) { + return true; + } + JSObject* obj = js::CheckedUnwrapStatic(this); + return obj && obj->is<T>(); +} + +template <class T> +T& JSObject::unwrapAs() { + static_assert(!std::is_convertible_v<T*, js::Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (is<T>()) { + return as<T>(); + } + + // Since the caller just called canUnwrapAs<T>(), which does a + // CheckedUnwrap, this does not need to repeat the security check. + JSObject* unwrapped = js::UncheckedUnwrap(this); + MOZ_ASSERT(js::CheckedUnwrapStatic(this) == unwrapped, + "check that the security check we skipped really is redundant"); + return unwrapped->as<T>(); +} + +template <class T> +inline T* JSObject::maybeUnwrapAs() { + static_assert(!std::is_convertible_v<T*, js::Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (is<T>()) { + return &as<T>(); + } + + JSObject* unwrapped = js::CheckedUnwrapStatic(this); + if (!unwrapped) { + return nullptr; + } + + if (MOZ_LIKELY(unwrapped->is<T>())) { + return &unwrapped->as<T>(); + } + + MOZ_CRASH("Invalid object. Dead wrapper?"); +} + +inline JSObject* JSObject::maybeUnwrapAs(const JSClass* clasp) { + if (hasClass(clasp)) { + return this; + } + + JSObject* unwrapped = js::CheckedUnwrapStatic(this); + if (!unwrapped) { + return nullptr; + } + + if (MOZ_LIKELY(unwrapped->hasClass(clasp))) { + return unwrapped; + } + + MOZ_CRASH("Invalid object. Dead wrapper?"); +} + +template <class T> +T* JSObject::maybeUnwrapIf() { + static_assert(!std::is_convertible_v<T*, js::Wrapper*>, + "T can't be a Wrapper type; this function discards wrappers"); + + if (is<T>()) { + return &as<T>(); + } + + JSObject* unwrapped = js::CheckedUnwrapStatic(this); + return (unwrapped && unwrapped->is<T>()) ? &unwrapped->as<T>() : nullptr; +} + +/* + * The only sensible way to compare JSObject with == is by identity. We use + * const& instead of * as a syntactic way to assert non-null. This leads to an + * abundance of address-of operators to identity. Hence this overload. + */ +static MOZ_ALWAYS_INLINE bool operator==(const JSObject& lhs, + const JSObject& rhs) { + return &lhs == &rhs; +} + +static MOZ_ALWAYS_INLINE bool operator!=(const JSObject& lhs, + const JSObject& rhs) { + return &lhs != &rhs; +} + +// Size of the various GC thing allocation sizes used for objects. +struct JSObject_Slots0 : JSObject { + void* data[2]; +}; +struct JSObject_Slots2 : JSObject { + void* data[2]; + js::Value fslots[2]; +}; +struct JSObject_Slots4 : JSObject { + void* data[2]; + js::Value fslots[4]; +}; +struct JSObject_Slots7 : JSObject { + // Only used for extended functions which are required to have exactly seven + // fixed slots due to JIT assumptions. + void* data[2]; + js::Value fslots[7]; +}; +struct JSObject_Slots8 : JSObject { + void* data[2]; + js::Value fslots[8]; +}; +struct JSObject_Slots12 : JSObject { + void* data[2]; + js::Value fslots[12]; +}; +struct JSObject_Slots16 : JSObject { + void* data[2]; + js::Value fslots[16]; +}; + +/* static */ +constexpr size_t JSObject::thingSize(js::gc::AllocKind kind) { + MOZ_ASSERT(IsObjectAllocKind(kind)); + constexpr uint8_t objectSizes[] = { +#define EXPAND_OJBECT_SIZE(_1, _2, _3, sizedType, _4, _5, _6) sizeof(sizedType), + FOR_EACH_OBJECT_ALLOCKIND(EXPAND_OJBECT_SIZE)}; + return objectSizes[size_t(kind)]; +} + +namespace js { + +// Returns true if object may possibly use JSObject::swap. The JITs may better +// optimize objects that can never swap (and thus change their type). +// +// If ObjectMayBeSwapped is false, it is safe to guard on pointer identity to +// test immutable features of the object. For example, the target of a +// JSFunction will not change. Note: the object can still be moved by GC. +extern bool ObjectMayBeSwapped(const JSObject* obj); + +extern bool DefineFunctions(JSContext* cx, HandleObject obj, + const JSFunctionSpec* fs); + +/* ES6 draft rev 36 (2015 March 17) 7.1.1 ToPrimitive(vp[, preferredType]) */ +extern bool ToPrimitiveSlow(JSContext* cx, JSType hint, MutableHandleValue vp); + +inline bool ToPrimitive(JSContext* cx, MutableHandleValue vp) { + if (vp.isPrimitive()) { + return true; + } + return ToPrimitiveSlow(cx, JSTYPE_UNDEFINED, vp); +} + +inline bool ToPrimitive(JSContext* cx, JSType preferredType, + MutableHandleValue vp) { + if (vp.isPrimitive()) { + return true; + } + return ToPrimitiveSlow(cx, preferredType, vp); +} + +/* + * toString support. (This isn't called GetClassName because there's a macro in + * <windows.h> with that name.) + */ +MOZ_ALWAYS_INLINE const char* GetObjectClassName(JSContext* cx, + HandleObject obj); + +/* + * Prepare a |this| object to be returned to script. This includes replacing + * Windows with their corresponding WindowProxy. + * + * Helpers are also provided to first extract the |this| from specific + * types of environment. + */ +JSObject* GetThisObject(JSObject* obj); + +JSObject* GetThisObjectOfLexical(JSObject* env); + +JSObject* GetThisObjectOfWith(JSObject* env); + +} /* namespace js */ + +namespace js { + +// ES6 9.1.15 GetPrototypeFromConstructor. +extern bool GetPrototypeFromConstructor(JSContext* cx, + js::HandleObject newTarget, + JSProtoKey intrinsicDefaultProto, + js::MutableHandleObject proto); + +// https://tc39.github.io/ecma262/#sec-getprototypefromconstructor +// +// Determine which [[Prototype]] to use when creating a new object using a +// builtin constructor. +// +// This sets `proto` to `nullptr` to mean "the builtin prototype object for +// this type in the current realm", the common case. +// +// We could set it to `cx->global()->getOrCreatePrototype(protoKey)`, but +// nullptr gets a fast path in e.g. js::NewObjectWithClassProtoCommon. +// +// intrinsicDefaultProto can be JSProto_Null if there's no appropriate +// JSProtoKey enum; but we then select the wrong prototype object in a +// multi-realm corner case (see bug 1515167). +MOZ_ALWAYS_INLINE bool GetPrototypeFromBuiltinConstructor( + JSContext* cx, const CallArgs& args, JSProtoKey intrinsicDefaultProto, + js::MutableHandleObject proto) { + // We can skip the "prototype" lookup in the two common cases: + // 1. Builtin constructor called without `new`, as in `obj = Object();`. + // 2. Builtin constructor called with `new`, as in `obj = new Object();`. + // + // Cases that can't take the fast path include `new MySubclassOfObject()`, + // `new otherGlobal.Object()`, and `Reflect.construct(Object, [], Date)`. + if (!args.isConstructing() || + &args.newTarget().toObject() == &args.callee()) { + MOZ_ASSERT(args.callee().hasSameRealmAs(cx)); + proto.set(nullptr); + return true; + } + + // We're calling this constructor from a derived class, retrieve the + // actual prototype from newTarget. + RootedObject newTarget(cx, &args.newTarget().toObject()); + return GetPrototypeFromConstructor(cx, newTarget, intrinsicDefaultProto, + proto); +} + +/* ES6 draft rev 32 (2015 Feb 2) 6.2.4.5 ToPropertyDescriptor(Obj) */ +bool ToPropertyDescriptor(JSContext* cx, HandleValue descval, + bool checkAccessors, + MutableHandle<JS::PropertyDescriptor> desc); + +/* + * Throw a TypeError if desc.getter() or setter() is not + * callable. This performs exactly the checks omitted by ToPropertyDescriptor + * when checkAccessors is false. + */ +Result<> CheckPropertyDescriptorAccessors(JSContext* cx, + Handle<JS::PropertyDescriptor> desc); + +void CompletePropertyDescriptor(MutableHandle<JS::PropertyDescriptor> desc); + +/* + * Read property descriptors from props, as for Object.defineProperties. See + * ES5 15.2.3.7 steps 3-5. + */ +extern bool ReadPropertyDescriptors( + JSContext* cx, HandleObject props, bool checkAccessors, + MutableHandleIdVector ids, MutableHandle<PropertyDescriptorVector> descs); + +/* Read the name using a dynamic lookup on the scopeChain. */ +extern bool LookupName(JSContext* cx, Handle<PropertyName*> name, + HandleObject scopeChain, MutableHandleObject objp, + MutableHandleObject pobjp, PropertyResult* propp); + +extern bool LookupNameNoGC(JSContext* cx, PropertyName* name, + JSObject* scopeChain, JSObject** objp, + NativeObject** pobjp, PropertyResult* propp); + +/* + * Like LookupName except returns the global object if 'name' is not found in + * any preceding scope. + * + * Additionally, pobjp and propp are not needed by callers so they are not + * returned. + */ +extern bool LookupNameWithGlobalDefault(JSContext* cx, + Handle<PropertyName*> name, + HandleObject scopeChain, + MutableHandleObject objp); + +/* + * Like LookupName except returns the unqualified var object if 'name' is not + * found in any preceding scope. Normally the unqualified var object is the + * global. If the value for the name in the looked-up scope is an + * uninitialized lexical, an UninitializedLexicalObject is returned. + * + * Additionally, pobjp is not needed by callers so it is not returned. + */ +extern bool LookupNameUnqualified(JSContext* cx, Handle<PropertyName*> name, + HandleObject scopeChain, + MutableHandleObject objp); + +} // namespace js + +namespace js { + +/* + * Family of Pure property lookup functions. The bool return does NOT have the + * standard SpiderMonkey semantics. The return value means "can this operation + * be performed and produce a valid result without any side effects?". If any of + * these return true, then the outparam can be inspected to determine the + * result. + */ + +bool LookupPropertyPure(JSContext* cx, JSObject* obj, jsid id, + NativeObject** objp, PropertyResult* propp); + +bool LookupOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id, + PropertyResult* propp); + +bool GetPropertyPure(JSContext* cx, JSObject* obj, jsid id, Value* vp); + +bool GetOwnPropertyPure(JSContext* cx, JSObject* obj, jsid id, Value* vp, + bool* found); + +bool GetGetterPure(JSContext* cx, JSObject* obj, jsid id, JSFunction** fp); + +bool GetOwnGetterPure(JSContext* cx, JSObject* obj, jsid id, JSFunction** fp); + +bool GetOwnNativeGetterPure(JSContext* cx, JSObject* obj, jsid id, + JSNative* native); + +bool HasOwnDataPropertyPure(JSContext* cx, JSObject* obj, jsid id, + bool* result); + +/* + * Like JS::FromPropertyDescriptor, but ignore desc.object() and always set vp + * to an object on success. + * + * Use JS::FromPropertyDescriptor for getOwnPropertyDescriptor, since + * desc.object() is used to indicate whether a result was found or not. Use + * this instead for defineProperty: it would be senseless to define a "missing" + * property. + */ +extern bool FromPropertyDescriptorToObject(JSContext* cx, + Handle<JS::PropertyDescriptor> desc, + MutableHandleValue vp); + +// obj is a JSObject*, but we root it immediately up front. We do it +// that way because we need a Rooted temporary in this method anyway. +extern bool IsPrototypeOf(JSContext* cx, HandleObject protoObj, JSObject* obj, + bool* result); + +/* Wrap boolean, number or string as Boolean, Number or String object. */ +extern JSObject* PrimitiveToObject(JSContext* cx, const Value& v); +extern JSProtoKey PrimitiveToProtoKey(JSContext* cx, const Value& v); + +} /* namespace js */ + +namespace js { + +JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val, + int valIndex, HandleId key); +JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val, + int valIndex, + Handle<PropertyName*> key); +JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val, + int valIndex, HandleValue keyValue); + +MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess(JSContext* cx, + HandleValue vp, + int vpIndex, + HandleId key) { + if (vp.isObject()) { + return &vp.toObject(); + } + return js::ToObjectSlowForPropertyAccess(cx, vp, vpIndex, key); +} +MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess( + JSContext* cx, HandleValue vp, int vpIndex, Handle<PropertyName*> key) { + if (vp.isObject()) { + return &vp.toObject(); + } + return js::ToObjectSlowForPropertyAccess(cx, vp, vpIndex, key); +} +MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess( + JSContext* cx, HandleValue vp, int vpIndex, HandleValue key) { + if (vp.isObject()) { + return &vp.toObject(); + } + return js::ToObjectSlowForPropertyAccess(cx, vp, vpIndex, key); +} + +/* + * Report a TypeError: "so-and-so is not an object". + * Using NotNullObject is usually less code. + */ +extern void ReportNotObject(JSContext* cx, const Value& v); + +inline JSObject* RequireObject(JSContext* cx, HandleValue v) { + if (v.isObject()) { + return &v.toObject(); + } + ReportNotObject(cx, v); + return nullptr; +} + +/* + * Report a TypeError: "SOMETHING must be an object, got VALUE". + * Using NotNullObject is usually less code. + * + * By default this function will attempt to report the expression which computed + * the value which given as argument. This can be disabled by using + * JSDVG_IGNORE_STACK. + */ +extern void ReportNotObject(JSContext* cx, JSErrNum err, int spindex, + HandleValue v); + +inline JSObject* RequireObject(JSContext* cx, JSErrNum err, int spindex, + HandleValue v) { + if (v.isObject()) { + return &v.toObject(); + } + ReportNotObject(cx, err, spindex, v); + return nullptr; +} + +extern void ReportNotObject(JSContext* cx, JSErrNum err, HandleValue v); + +inline JSObject* RequireObject(JSContext* cx, JSErrNum err, HandleValue v) { + if (v.isObject()) { + return &v.toObject(); + } + ReportNotObject(cx, err, v); + return nullptr; +} + +/* + * Report a TypeError: "N-th argument of FUN must be an object, got VALUE". + * Using NotNullObjectArg is usually less code. + */ +extern void ReportNotObjectArg(JSContext* cx, const char* nth, const char* fun, + HandleValue v); + +inline JSObject* RequireObjectArg(JSContext* cx, const char* nth, + const char* fun, HandleValue v) { + if (v.isObject()) { + return &v.toObject(); + } + ReportNotObjectArg(cx, nth, fun, v); + return nullptr; +} + +extern bool GetFirstArgumentAsObject(JSContext* cx, const CallArgs& args, + const char* method, + MutableHandleObject objp); + +/* Helper for throwing, always returns false. */ +extern bool Throw(JSContext* cx, HandleId id, unsigned errorNumber, + const char* details = nullptr); + +/* + * ES6 rev 29 (6 Dec 2014) 7.3.13. Mark obj as non-extensible, and adjust each + * of obj's own properties' attributes appropriately: each property becomes + * non-configurable, and if level == Frozen, data properties become + * non-writable as well. + */ +extern bool SetIntegrityLevel(JSContext* cx, HandleObject obj, + IntegrityLevel level); + +inline bool FreezeObject(JSContext* cx, HandleObject obj) { + return SetIntegrityLevel(cx, obj, IntegrityLevel::Frozen); +} + +/* + * ES6 rev 29 (6 Dec 2014) 7.3.14. Code shared by Object.isSealed and + * Object.isFrozen. + */ +extern bool TestIntegrityLevel(JSContext* cx, HandleObject obj, + IntegrityLevel level, bool* resultp); + +[[nodiscard]] extern JSObject* SpeciesConstructor( + JSContext* cx, HandleObject obj, HandleObject defaultCtor, + bool (*isDefaultSpecies)(JSContext*, JSFunction*)); + +[[nodiscard]] extern JSObject* SpeciesConstructor( + JSContext* cx, HandleObject obj, JSProtoKey ctorKey, + bool (*isDefaultSpecies)(JSContext*, JSFunction*)); + +extern bool GetObjectFromIncumbentGlobal(JSContext* cx, + MutableHandleObject obj); + +#ifdef DEBUG +inline bool IsObjectValueInCompartment(const Value& v, JS::Compartment* comp) { + if (!v.isObject()) { + return true; + } + return v.toObject().compartment() == comp; +} +#endif + +/* + * A generic trace hook that calls the object's 'trace' method. + * + * If you are introducing a new JSObject subclass, MyObject, that needs a custom + * JSClassOps::trace function, it's often helpful to write `trace` as a + * non-static member function, since `this` will the correct type. In this case, + * you can use `CallTraceMethod<MyObject>` as your JSClassOps::trace value. + */ +template <typename ObjectSubclass> +void CallTraceMethod(JSTracer* trc, JSObject* obj) { + obj->as<ObjectSubclass>().trace(trc); +} + +#ifdef JS_HAS_CTYPES + +namespace ctypes { + +extern size_t SizeOfDataIfCDataObject(mozilla::MallocSizeOf mallocSizeOf, + JSObject* obj); + +} // namespace ctypes + +#endif + +#ifdef DEBUG +void AssertJSClassInvariants(const JSClass* clasp); +#endif + +} /* namespace js */ + +#endif /* vm_JSObject_h */ |