/* -*- 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; 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 { 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 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(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& x = obj.as(); * x.foo(); * } * * These XObject classes form a hierarchy. For example, for a cloned block * object, the following predicates are true: is, * is and is. 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 inline bool is() const { return getClass() == &T::class_; } template T& as() { MOZ_ASSERT(this->is()); return *static_cast(this); } template const T& as() const { MOZ_ASSERT(this->is()); return *static_cast(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 bool canUnwrapAs(); /* * Unwrap and downcast to class T. * * Precondition: `this->canUnwrapAs()`. 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 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 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 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() const { return true; } template template MOZ_ALWAYS_INLINE JS::Handle js::RootedOperations::as() const { const Wrapper& self = *static_cast(this); MOZ_ASSERT(self->template is()); return Handle::fromMarkedLocation( reinterpret_cast(self.address())); } template template MOZ_ALWAYS_INLINE JS::Handle js::HandleOperations::as() const { const JS::Handle& self = *static_cast*>(this); MOZ_ASSERT(self->template is()); return Handle::fromMarkedLocation( reinterpret_cast(self.address())); } template bool JSObject::canUnwrapAs() { static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); if (is()) { return true; } JSObject* obj = js::CheckedUnwrapStatic(this); return obj && obj->is(); } template T& JSObject::unwrapAs() { static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); if (is()) { return as(); } // Since the caller just called canUnwrapAs(), 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(); } template inline T* JSObject::maybeUnwrapAs() { static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); if (is()) { return &as(); } JSObject* unwrapped = js::CheckedUnwrapStatic(this); if (!unwrapped) { return nullptr; } if (MOZ_LIKELY(unwrapped->is())) { return &unwrapped->as(); } 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 T* JSObject::maybeUnwrapIf() { static_assert(!std::is_convertible_v, "T can't be a Wrapper type; this function discards wrappers"); if (is()) { return &as(); } JSObject* unwrapped = js::CheckedUnwrapStatic(this); return (unwrapped && unwrapped->is()) ? &unwrapped->as() : 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 * 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 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 desc); void CompletePropertyDescriptor(MutableHandle 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 descs); /* Read the name using a dynamic lookup on the scopeChain. */ extern bool LookupName(JSContext* cx, Handle 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 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 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 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 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 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` as your JSClassOps::trace value. */ template void CallTraceMethod(JSTracer* trc, JSObject* obj) { obj->as().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 */