Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion oscars/src/collectors/mark_sweep_branded/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,12 @@ impl<'a, T: Trace + ?Sized> GcRefMut<'a, T> {

impl<T: Trace + ?Sized> Finalize for GcRefCell<T> {}

unsafe impl<T: Trace + ?Sized> Trace for GcRefCell<T> {
unsafe impl<T: Trace + ?Sized> Trace for GcRefCell<T>
where
T::StaticId: Sized,
{
// GcRefCell branded by T's lifetime, map to the static form.
type StaticId = GcRefCell<T::StaticId>;
unsafe fn trace(&self, tracer: &mut Tracer) {
let val = unsafe { &*self.inner.as_ptr() };
unsafe {
Expand Down
7 changes: 6 additions & 1 deletion oscars/src/collectors/mark_sweep_branded/ephemeron.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@ impl<'id, K: Trace + ?Sized, V: Trace> Copy for Ephemeron<'id, K, V> {}

impl<'id, K: Trace + ?Sized, V: Trace> Finalize for Ephemeron<'id, K, V> {}

unsafe impl<'id, K: Trace + ?Sized, V: Trace> Trace for Ephemeron<'id, K, V> {
unsafe impl<'id, K: Trace + ?Sized, V: Trace> Trace for Ephemeron<'id, K, V>
where
K::StaticId: Sized,
{
// Ephemeron<'id, K, V> -> Ephemeron<'static, K::StaticId, V::StaticId>
type StaticId = Ephemeron<'static, K::StaticId, V::StaticId>;
// Ephemerons do not mark their key; liveness of the key is determined
// by the GC independently. The value is marked via the GC's ephemeron
// fixpoint phase in `Collector::collect`.
Expand Down
22 changes: 18 additions & 4 deletions oscars/src/collectors/mark_sweep_branded/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,16 @@ impl<'gc, T: Trace + ?Sized + 'gc> Gc<'gc, T> {
}
}

/// Returns `true` if the inner value is of type `U`.
///
/// Uses `TypeId` via `U::StaticId`, sound even when `U` carries a branded
/// lifetime because `StaticId` is the lifetime erased proxy defined on the
/// `Trace` trait. This avoids the `T: 'static` restriction while still
/// giving us a stable, unique identity guarantee.
#[inline]
pub fn is<U: Trace + ?Sized + 'gc>(&self) -> bool {
let actual_type_name = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_name };
actual_type_name == core::any::type_name::<U>()
pub fn is<U: Trace + ?Sized>(&self) -> bool {
let actual_type_id = unsafe { (*self.ptr.as_ptr().as_ptr()).0.type_id };
actual_type_id == core::any::TypeId::of::<U::StaticId>()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, why can't we use TypeId::of::<U>() here?

@shruti2522 shruti2522 Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replced StaticId with dtonlay's typeid crate, everything compiles, certainly an improvement on the earlier approach.
It gives us a TypeId equivalent that works without requiring 'static, so U no longer needs the StaticId proxy at all

}
Comment thread
shruti2522 marked this conversation as resolved.

#[inline]
Expand Down Expand Up @@ -149,7 +155,15 @@ impl<'gc, T: Trace + ?Sized + 'gc> Deref for Gc<'gc, T> {
}

impl<T: Trace + ?Sized> Finalize for Gc<'_, T> {}
unsafe impl<T: Trace + ?Sized> Trace for Gc<'_, T> {
unsafe impl<'gc, T: Trace + ?Sized + 'gc> Trace for Gc<'gc, T>
where
T::StaticId: Sized,
{
// The StaticId of Gc<'gc, T> is Gc<'static, T::StaticId>.
// This maps any branded Gc to a fully 'static form, giving a unique TypeId
// per pointee type regardless of which 'gc brand is in use.
type StaticId = Gc<'static, T::StaticId>;

unsafe fn trace(&self, tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {
tracer.mark(self);
}
Expand Down
18 changes: 13 additions & 5 deletions oscars/src/collectors/mark_sweep_branded/gc_box.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! The heap header wrapping every GC-managed value.

use core::any::TypeId;
use core::cell::Cell;
use core::ptr::NonNull;

Expand Down Expand Up @@ -32,8 +33,13 @@ pub struct GcBox<T: ?Sized> {
pub(crate) drop_fn: DropFn,
/// Allocation ID used to validate weak pointers.
pub(crate) alloc_id: usize,
/// Type name of the underlying value
pub(crate) type_name: &'static str,
/// Unique identifier for the concrete type `T`.
///
/// Stored as `TypeId::of::<T::StaticId>()`, we use the `StaticId` proxy
/// type so that branded lifetimes (eg. `'gc`) don't require `T: 'static`.
/// Two values whose erased types share the same `StaticId` produce the
/// same `TypeId`, which is exactly what we want for sound downcasting.
pub(crate) type_id: TypeId,
/// The user value.
pub(crate) value: T,
}
Expand All @@ -42,15 +48,17 @@ impl<T: ?Sized> GcBox<T> {
pub(crate) const FREED_ALLOC_ID: usize = usize::MAX;
}

impl<T> GcBox<T> {
/// Create a [`GcBox`] for `value`, `color` starts as [`GcColor::White`]
impl<T: Trace> GcBox<T> {
/// Create a [`GcBox`] for `value`, `color` starts as [`GcColor::White`].
///
/// Requires `T: Trace` to access `T::StaticId` for the `TypeId`.
pub(crate) fn new(value: T, trace_fn: TraceFn, drop_fn: DropFn, alloc_id: usize) -> Self {
Self {
color: Cell::new(GcColor::White),
trace_fn,
drop_fn,
alloc_id,
type_name: core::any::type_name::<T>(),
type_id: TypeId::of::<T::StaticId>(),
value,
}
}
Expand Down
6 changes: 6 additions & 0 deletions oscars/src/collectors/mark_sweep_branded/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ pub struct Collector {
pub(crate) ephemerons: RefCell<Vec<EphemeronEntry>>,
}

impl Default for Collector {
fn default() -> Self {
Self::new()
}
}

impl Collector {
pub fn new() -> Self {
Self {
Expand Down
2 changes: 2 additions & 0 deletions oscars/src/collectors/mark_sweep_branded/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ struct JsObject {
}

unsafe impl crate::collectors::mark_sweep_branded::Trace for JsObject {
// JsObject contains no GC pointers and no lifetimes, so its static proxy is itself.
type StaticId = JsObject;
unsafe fn trace(&self, _tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {}
}
impl crate::collectors::mark_sweep_branded::Finalize for JsObject {}
Expand Down
2 changes: 2 additions & 0 deletions oscars/src/collectors/mark_sweep_branded/tests/uaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use core::cell::Cell;
struct DetectDrop<'a>(&'a Cell<bool>);

unsafe impl<'a> Trace for DetectDrop<'a> {
// DetectDrop<'a> borrows a local `Cell`, the static proxy is DetectDrop<'static>.
type StaticId = DetectDrop<'static>;
unsafe fn trace(&self, _tracer: &mut crate::collectors::mark_sweep_branded::trace::Tracer) {}
}

Expand Down
Loading