/* -*- 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;
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);
  }

  [[nodiscard]] static bool reshapeForTeleporting(JSContext* cx,
                                                  JS::HandleObject obj);

  /*
   * 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);
  }

  js::gc::AllocKind allocKind() const;

  /* 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);

// 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 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);

/*
 * 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 */
