Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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;
class WithEnvironmentObject;
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);
}
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 environment 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;
// Non-syntactic with-environment objects can be made qualified varobjs after
// construction. All other qualified varobjs are directly marked as such when
// allocating the object.
static inline bool setQualifiedVarObj(
JSContext* cx, JS::Handle<js::WithEnvironmentObject*> obj);
// An "unqualified" varobj is the object on which "unqualified"
// assignments (i.e., bareword assignments for which the LHS does not
// exist on the environment 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);
} /* namespace js */
namespace js {
// ES6 9.1.15 GetPrototypeFromConstructor.
extern bool GetPrototypeFromConstructor(JSContext* cx,
js::HandleObject newTarget,
JSProtoKey intrinsicDefaultProto,
js::MutableHandleObject proto);
//
// 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
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 envChain. */
extern bool LookupName(JSContext* cx, Handle<PropertyName*> name,
HandleObject envChain, MutableHandleObject objp,
MutableHandleObject pobjp, PropertyResult* propp);
extern bool LookupNameNoGC(JSContext* cx, PropertyName* name,
JSObject* envChain, 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 JSObject* LookupNameWithGlobalDefault(JSContext* cx,
Handle<PropertyName*> name,
HandleObject envChain);
/*
* 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, a RuntimeLexicalErrorObject is returned.
*
* Additionally, pobjp is not needed by callers so it is not returned.
*/
extern JSObject* LookupNameUnqualified(JSContext* cx,
Handle<PropertyName*> name,
HandleObject envChain);
} // 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 GetObjectFromHostDefinedData(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 */