@ 261106.6
current
// Copyright 2024 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_OBJECTS_CASTING_H_
#define V8_OBJECTS_CASTING_H_

/**
 * V8 Casting Guide
 * ================
 *
 * This file defines the casting infrastructure for V8's tagged objects.
 *
 * Untrusted objects (e.g. JSObject) live in the sandbox and their Map can be
 * tampered with. Trusted objects (e.g. BytecodeArray) live outside the
 * sandbox in trusted space.
 *
 *
 * 1. Release-Time Security Checks (Always-on CHECK or SBXCHECK)
 * -------------------------------------------------------------
 * These are required when type safety must be guaranteed at runtime under
 * the V8 Sandbox attacker model.
 *
 *         To      |  Untrusted        Trusted
 * From            |
 * --------------------------------------------------
 * Object          |  CheckedCast      <Impossible>*
 * TrustedObject   |  -                SbxCast
 *
 * * Note: A checked cast from a generic untrusted Object to a TrustedObject is
 *   impossible at runtime because untrusted pointers can point to arbitrary,
 *   unaligned memory, making a sound runtime type check impossible.
 *
 *
 * 2. Debug-Only Validation Checks (DCHECK-only or no-op)
 * ------------------------------------------------------
 * These are used when type safety is statically guaranteed, validated via
 * non-typesystem invariants, or when performance is critical.
 *
 *         To      |  Untrusted        Trusted
 * From            |
 * --------------------------------------------------
 * Object          |  Cast             TrustedCast
 * TrustedObject   |  -                TrustedCast
 * Uninitialized   |  UncheckedCast    -
 *
 * * Note: "Uninitialized" refers to partially-initialized objects (e.g., during
 *   deserialization or construction) where CastTraits checks would fail.
 *
 *
 * Guide to Cast Functions
 * =======================
 *
 * Cast<T>
 * -------
 * The default cast for untrusted objects. Only performs a debug-only DCHECK.
 * Since untrusted objects can be tampered with anyway, release-time checks
 * provide no additional security.
 *
 * TrustedCast<T>
 * --------------
 * Use this for trusted objects when the type is known through external means
 * but cannot be proven locally to the type system (e.g., trusted stack slots).
 * Like Cast<T>, it only performs a debug-only DCHECK.
 *
 * WARNING: Never use TrustedCast<T> on pointers loaded from untrusted/sandbox-
 * accessible fields, as an attacker can tamper with them and cause release-time
 * type confusion. Use SbxCast<T> or TryCast<T> instead.
 *
 * SbxCast<T>
 * ----------
 * Use this when a trusted pointer is obtained from an untrusted source AND the
 * TPT (Trusted Pointer Table) tag is shared among multiple types (1:N mapping).
 * It performs an always-on SBXCHECK to prevent type-confusion attacks.
 *
 * CheckedCast<T>
 * --------------
 * Performs an always-on CHECK at release time. Use this when casting untrusted
 * objects where correctness is absolutely critical.
 *
 * TryCast<T>
 * ----------
 * Performs a dynamic type check and returns a boolean. For trusted objects,
 * this is specifically used for 1:N mappings where multiple instance types
 * share the same TPT tag and you need to safely distinguish them at runtime.
 *
 * Note on 1:1 Mappings: For trusted objects with a 1:1 mapping between their
 * InstanceType and their TPT tag (like BytecodeArray), no explicit cast is
 * usually needed when reading from a field, as ReadTrustedPointerField<T>
 * already ensures the correct type.
 *
 * UncheckedCast<T>
 * ----------------
 * Performs a raw cast with zero compile-time or runtime checks. Used when the
 * object is partially initialized (so Is<T> checks would fail), or when the
 * type is already proven.
 *
 * GCSafeCast<T>
 * -------------
 * Performs a debug-only GC-aware type check. Used only within garbage
 * collection code paths.
 */

#include <type_traits>

#include "include/v8-source-location.h"
#include "src/base/logging.h"
#include "src/objects/tagged.h"
#include "src/sandbox/check.h"

namespace v8::internal {

// CastTraits<T> is a type trait that defines type checking behaviour for
// tagged object casting. The expected specialization is:
//
//     template<>
//     struct CastTraits<SomeObject> {
//       template<typename From>
//       static bool AllowFrom(Tagged<From> value) {
//         return IsSomeObject(value);
//       }
//     };
//
// or, likely, just specializations of AllowFrom for Object and HeapObject,
// under the assumption that the HeapObject implementation is the same for all
// HeapObjects and the Object implementation has additional overhead in Smi
// checks.
//
//     struct CastTraits<Object> {
//       static bool AllowFrom(Tagged<HeapObject> value) {
//         return IsSomeObject(value);
//       }
//       static bool AllowFrom(Tagged<Object> value) {
//         return IsSomeObject(value);
//       }
//     };
//
template <typename To>
struct CastTraits;

class Oddball;

template <typename T>
concept NotGCedType = !std::is_base_of_v<HeapObject, T>;

// `Is<T>(value)` checks whether `value` is a tagged object of type `T`.
template <typename T, typename U>
inline bool Is(Tagged<U> value) {
  return CastTraits<T>::AllowFrom(value);
}
template <typename T>
inline bool Is(const HeapObject* obj) {
  return Is<T>(Tagged<HeapObject>(obj));
}
template <typename T, typename U>
inline bool Is(IndirectHandle<U> value);
template <typename T, typename U>
inline bool Is(DirectHandle<U> value);
// Is<T> checks for MaybeHandles are forbidden -- you need to first convert to
// a Handle with ToHandle or ToHandleChecked.
template <typename T, typename U>
bool Is(MaybeIndirectHandle<U> value) = delete;
template <typename T, typename U>
bool Is(MaybeDirectHandle<U> value) = delete;
// C++ versions for `Is<T>` that accept pointers and references.
template <typename To, typename From>
  requires NotGCedType<To>
bool Is(const From& value) {
  return CastTraits<std::remove_const_t<To>>::AllowFrom(value);
}
template <typename To, typename From>
  requires NotGCedType<To>
bool Is(From& value) {
  return Is<To>(const_cast<const From&>(value));
}
template <typename To, typename From>
  requires NotGCedType<To>
bool Is(const From* value) {
  return value && Is<To>(*value);
}
template <typename To, typename From>
  requires NotGCedType<To>
bool Is(From* value) {
  return value && Is<To>(*static_cast<const From*>(value));
}

// `UncheckedCast<T>(value)` casts `value` to a tagged object of type `T`,
// without checking the type of value.
template <typename To, typename From>
inline Tagged<To> UncheckedCast(Tagged<From> value) {
  return Tagged<To>(value.ptr());
}
template <typename To, typename From>
inline IndirectHandle<To> UncheckedCast(IndirectHandle<From> value);
template <typename To, typename From>
inline MaybeIndirectHandle<To> UncheckedCast(MaybeIndirectHandle<From> value);
template <typename To, typename From>
inline DirectHandle<To> UncheckedCast(DirectHandle<From> value);
template <typename To, typename From>
inline MaybeDirectHandle<To> UncheckedCast(MaybeDirectHandle<From> value);

// HasCastImplementation is a concept that checks for the existence of
// Is<To>(Holder<From>) and UncheckedCast<To>(Holder<From>).
template <template <typename> class Holder, typename To, typename From>
concept HasTryCastImplementation = requires(Holder<From> value) {
  { Is<To>(value) } -> std::same_as<bool>;
  { UncheckedCast<To>(value) } -> std::same_as<Holder<To>>;
};

template <typename Derived, typename Base>
concept HasCppRefTryCastImplementation =
    NotGCedType<Derived> && requires(Base& value) {
      { Is<Derived>(value) } -> std::same_as<bool>;
    };

template <typename Derived, typename Base>
concept HasCppPointerTryCastImplementation =
    NotGCedType<Derived> && requires(Base* value) {
      { Is<Derived>(value) } -> std::same_as<bool>;
    };

// `TryCast<T>(value, &out)` casts `value` to a tagged object of type `T` and
// writes the value to `out`, returning true if the cast succeeded and false if
// it failed.
template <typename To, typename From, template <typename> class Holder>
  requires HasTryCastImplementation<Holder, To, From>
inline bool TryCast(Holder<From> value, Holder<To>* out) {
  static_assert(!is_subtype_v<To, TrustedObject> ||
                    is_subtype_v<From, Union<Smi, Oddball, TrustedObject>>,
                "Type of untrusted objects cannot be checked reliably");
  if (!Is<To>(value)) return false;
  *out = UncheckedCast<To>(value);
  return true;
}

template <typename Derived, typename Base>
  requires HasCppRefTryCastImplementation<Derived, Base>
inline bool TryCast(Base& value, Derived** out) {
  if (!Is<Derived>(value)) return false;
  *out = static_cast<Derived*>(&value);
  return true;
}

template <typename Derived, typename Base>
  requires HasCppPointerTryCastImplementation<Derived, Base>
inline bool TryCast(Base* value, Derived** out) {
  if (!Is<Derived>(value)) return false;
  *out = static_cast<Derived*>(value);
  return true;
}

#ifdef DEBUG
template <typename T>
bool GCAwareObjectTypeCheck(Tagged<Object> object, const Heap* heap);
#endif  // DEBUG

// `GCSafeCast<T>(value)` casts `object` to a tagged object of type `T` and
// should be used when the cast can be called from within a GC. The case all
// includes a debug check that `object` is either a tagged object of type `T`,
// or one of few special cases possible during GC (see GCAwareObjectTypeCheck):
// 1) `object` was already evacuated and the forwarding address refers to a
//     tagged object of type `T`.
// 2) During Scavenger, `object` is a large object.
// 3) During a conservative Scavenger, `object` is a pinned object.
template <typename T>
Tagged<T> GCSafeCast(Tagged<Object> object, const Heap* heap) {
  DCHECK(GCAwareObjectTypeCheck<T>(object, heap));
  return UncheckedCast<T>(object);
}

// Check if a value holder is null -- used for Cast DCHECKs, so that null
// handles pass the check.
template <typename T, typename U>
inline bool NullOrIs(Tagged<U> value) {
  // Raw tagged values are not allowed to be null, just check Is<T>.
  return Is<T>(value);
}
template <typename T, typename U>
inline bool NullOrIs(IndirectHandle<U> value) {
  return value.is_null() || Is<T>(value);
}
template <typename T, typename U>
inline bool NullOrIs(DirectHandle<U> value) {
  return value.is_null() || Is<T>(value);
}
template <typename T, typename U>
inline bool NullOrIs(MaybeIndirectHandle<U> value) {
  IndirectHandle<U> handle;
  return !value.ToHandle(&handle) || Is<T>(handle);
}
template <typename T, typename U>
inline bool NullOrIs(MaybeDirectHandle<U> value) {
  DirectHandle<U> handle;
  return !value.ToHandle(&handle) || Is<T>(handle);
}
template <typename Derived, typename Base>
bool NullOrIs(Base& value) {
  return Is<Derived>(value);
}
template <typename Derived, typename Base>
bool NullOrIs(Base* value) {
  return !value || Is<Derived>(*value);
}

// HasCastImplementation is a concept that checks for the existence of
// NullOrIs<To>(Holder<From>) and UncheckedCast<To>(Holder<From>).
template <template <typename> class Holder, typename To, typename From>
concept HasCastImplementation = requires(Holder<From> value) {
  { NullOrIs<To>(value) } -> std::same_as<bool>;
  { UncheckedCast<To>(value) } -> std::same_as<Holder<To>>;
};

template <typename Derived, typename Base>
concept HasCppRefCastImplementation =
    NotGCedType<Derived> && requires(Base& value) {
      { Is<Derived>(value) } -> std::same_as<bool>;
    };

template <typename Derived, typename Base>
concept HasCppPointerCastImplementation =
    NotGCedType<Derived> && requires(Base* value) {
      { Is<Derived>(value) } -> std::same_as<bool>;
    };

// `TrustedCast<T>(value)` casts `value` to a tagged object of type `T`, only
// doing a debug check that `value` is a tagged object of type `T`. Down-casts
// to trusted objects are allowed for callers which already ensured their
// correctness. Null-valued holders are allowed.
//
// WARNING: This must NEVER be used on pointers loaded from untrusted/sandbox-
// accessible fields, as an attacker can tamper with them and cause release-time
// type confusion. Use SbxCast<T> or TryCast<T> instead for those cases.
template <typename To, typename From, template <typename> class Holder>
  requires HasCastImplementation<Holder, To, From>
inline Holder<To> TrustedCast(
    Holder<From> value, SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  // Casting pointers to in-sandbox objects is allowed even with an active
  // DisallowSandboxAccess scope (as casting itself doesn't access any memory).
  // However, in debug builds the DCHECK below will need to access the Map of
  // the object, so we need to temporarily enable sandbox access. If no
  // DisallowSandboxAccess scope is active, this is a no-op.
  DCHECK_WITH_SANDBOX_ACCESS_AND_MSG_AND_LOC(
      NullOrIs<To>(value), V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  return UncheckedCast<To>(value);
}

// `SbxCast<T>(value)` casts `value` to a tagged object of trusted type `T`,
// with a sandbox-only dynamic type check ensuring that `value` is a tagged
// object of type `T`. This is required for security when the object was
// obtained from an untrusted source AND the TPT tag is shared among multiple
// types (1:N mapping). Null-valued holders are allowed.
template <typename To, typename From, template <typename> class Holder>
  requires HasCastImplementation<Holder, To, From>
inline Holder<To> SbxCast(
    Holder<From> value, SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  // Under the attacker model of the sandbox we cannot trust the type of
  // in-sandbox objects for sandbox checks as these objects can be arbitrarily
  // tampered with.
  static_assert(is_subtype_v<To, TrustedObject>);
  static_assert(is_subtype_v<From, Union<Smi, Oddball, TrustedObject>>,
                "Type of untrusted objects cannot be checked reliably");
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<To>(value),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  SBXCHECK(NullOrIs<To>(value));
  return UncheckedCast<To>(value);
}

template <typename Derived, typename Base>
  requires HasCppRefCastImplementation<Derived, Base>
inline Derived& SbxCast(Base& from,
                        SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  SBXCHECK(NullOrIs<Derived>(from));
  return static_cast<Derived&>(from);
}

template <typename Derived, typename Base>
  requires HasCppPointerCastImplementation<Derived, Base>
inline Derived* SbxCast(Base* from,
                        SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  SBXCHECK(NullOrIs<Derived>(from));
  return static_cast<Derived*>(from);
}

// `CheckedCast<T>(value)` casts `value` to a tagged object of type `T`,
// with a always-on dynamic type check ensuring that `value` is a tagged object
// of type `T`. Null-valued holders are allowed.
template <typename To, typename From, template <typename> class Holder>
  requires HasCastImplementation<Holder, To, From>
inline Holder<To> CheckedCast(
    Holder<From> value, SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<To>(value),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  CHECK(NullOrIs<To>(value));
  return UncheckedCast<To>(value);
}

template <typename Derived, typename Base>
  requires HasCppRefCastImplementation<Derived, Base>
inline Derived& CheckedCast(
    Base& from, SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  CHECK(NullOrIs<Derived>(from));
  return static_cast<Derived&>(from);
}

template <typename Derived, typename Base>
  requires HasCppPointerCastImplementation<Derived, Base>
inline Derived* CheckedCast(
    Base* from, SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  CHECK(NullOrIs<Derived>(from));
  return static_cast<Derived*>(from);
}

// `Cast<T>(value)` casts `value` to a tagged object of type `T`, with a debug
// check that `value` is a tagged object of type `T`. Null-valued holders are
// allowed. Down-casts to trusted objects are not allowed to prevent mistakes.
template <typename To, typename From, template <typename> class Holder>
  requires HasCastImplementation<Holder, To, From>
inline Holder<To> Cast(Holder<From> value,
                       SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  static_assert(
      !is_subtype_v<To, TrustedObject> || is_subtype_v<From, To>,
      "Down-casting to trusted objects is verboten. Pick one of:\n"
      "* TryCast if the use is guarded by a test\n"
      "      if (Trusted t; TryCast(obj, &t)) { t->foo(); }\n"
      "* (Checked|Sbx)Cast if the object is expected to be of a certain type\n"
      "      Tagged<Trusted> t = SbxCast<Trusted>(obj);\n"
      "* TrustedCast if correct by construction (or other deprecated usage)\n"
      "      Tagged<Trusted> t = "
      "TrustedCast<Trusted>(TrustedPointerField::ReadTrustedPointerField<"
      "kTrustedTag>(obj, ...));\n");
  return TrustedCast<To>(value, loc);
}

// TODO(leszeks): Figure out a way to make these cast to actual pointers rather
// than Tagged.
template <typename To, typename From>
  requires std::is_base_of_v<HeapObject, From>
inline Tagged<To> UncheckedCast(const From* value) {
  return UncheckedCast<To>(Tagged(value));
}
template <typename To, typename From>
  requires std::is_base_of_v<HeapObject, From>
inline Tagged<To> TrustedCast(const From* value) {
  return TrustedCast<To>(Tagged(value));
}
template <typename To, typename From>
  requires std::is_base_of_v<HeapObject, From>
inline Tagged<To> CheckedCast(const From* value) {
  return CheckedCast<To>(Tagged(value));
}
template <typename To, typename From>
  requires std::is_base_of_v<HeapObject, From>
inline Tagged<To> SbxCast(const From* value) {
  return SbxCast<To>(Tagged(value));
}
template <typename To, typename From>
  requires std::is_base_of_v<HeapObject, From>
inline Tagged<To> Cast(const From* value,
                       SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  return Cast<To>(Tagged(value), loc);
}

template <typename Derived, typename Base>
  requires HasCppRefCastImplementation<Derived, Base>
inline Derived& Cast(Base& from,
                     SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  return static_cast<Derived&>(from);
}

template <typename Derived, typename Base>
  requires HasCppPointerCastImplementation<Derived, Base>
inline Derived* Cast(Base* from,
                     SourceLocation loc = SourceLocation::CurrentIfDebug()) {
  DCHECK_WITH_MSG_AND_LOC(NullOrIs<Derived>(from),
                          V8_PRETTY_FUNCTION_VALUE_OR("Cast type check"), loc);
  return static_cast<Derived*>(from);
}

// `Is<T>(maybe_weak_value)` specialization for possible weak values and strong
// target `T`, that additionally first checks whether `maybe_weak_value` is
// actually a strong value (or a Smi, which can't be weak).
template <typename T, typename U>
inline bool Is(Tagged<MaybeWeak<U>> value) {
  // Cast from maybe weak to strong needs to be strong or smi.
  if constexpr (!is_maybe_weak_v<T>) {
    if (!value.IsStrongOrSmi()) return false;
    return CastTraits<T>::AllowFrom(Tagged<U>(value.ptr()));
  } else {
    // Dispatches to CastTraits<MaybeWeak<T>> below.
    return CastTraits<T>::AllowFrom(value);
  }
}
template <typename T, typename... U>
constexpr inline bool Is(Tagged<Union<U...>> value) {
  using UnionU = Union<U...>;
  if constexpr (is_weak_v<UnionU>) {
    return Is<T>(Tagged<Weak<HeapObject>>(value));
  } else if constexpr (is_maybe_weak_v<UnionU>) {
    // Cleared values are always ok.
    if (value.IsCleared()) return true;
    // TODO(leszeks): Skip Smi check for values that are known to not be Smi.
    if (value.IsSmi()) {
      return Is<T>(Tagged<Smi>(value.ptr()));
    }
    return Is<T>(MakeStrong(value));
  } else if constexpr (is_subtype_v<UnionU, HeapObject>) {
    return Is<T>(Tagged<HeapObject>(value));
  } else {
    static_assert(is_subtype_v<UnionU, Object>);
    return Is<T>(Tagged<Object>(value));
  }
}

// Specialization for weak cast targets, which first converts the incoming
// value to a strong reference and then checks if the cast to the strong T
// is allowed. Cleared weak references always return true.
template <typename T>
struct CastTraits<Weak<T>> {
  template <typename U>
  static bool AllowFrom(Tagged<U> value) {
    if constexpr (is_weak_v<U>) {
      return CastTraits<T>::AllowFrom(MakeStrong(value));
    }
    if constexpr (is_maybe_weak_v<U>) {
      // Cleared values are always ok.
      if (value.IsCleared()) return true;
      // TODO(leszeks): Skip Smi check for values that are known to not be Smi.
      if (value.IsSmi()) return false;
      return CastTraits<T>::AllowFrom(MakeStrong(value));
    } else {
      // Can't cast weak to non-weak.
      return false;
    }
  }
};

template <typename... T>
struct CastTraits<Union<T...>> {
  static inline bool AllowFrom(Tagged<Object> value) {
    return (Is<T>(value) || ...);
  }
  static inline bool AllowFrom(Tagged<HeapObject> value) {
    return (Is<T>(value) || ...);
  }
};

template <>
struct CastTraits<Object> {
  static inline bool AllowFrom(Tagged<Object> value) { return true; }
};
template <>
struct CastTraits<Smi> {
  static inline bool AllowFrom(Tagged<Object> value) { return value.IsSmi(); }
  static inline bool AllowFrom(Tagged<HeapObject> value) { return false; }
};
template <>
struct CastTraits<HeapObject> {
  static inline bool AllowFrom(Tagged<Object> value) {
    return value.IsHeapObject();
  }
  static inline bool AllowFrom(Tagged<HeapObject> value) { return true; }
};

}  // namespace v8::internal

#endif  // V8_OBJECTS_CASTING_H_