Skip to content

Conversation

@joshlf
Copy link
Member

@joshlf joshlf commented Dec 23, 2025

Add unsafe trait Project and unsafe trait Cast: Project. Cast is
implemented for any address-preserving cast, while Project generalizes
to conversions which may not preserve the address of the referent (ie,
field projections). Use these (mostly Cast, but some Project) to
unify PtrInner/Ptr casts, field projections, and SizeEq casts.
Replace a good amount of unsafe derive-generated code with uses of this
machinery.

Makes progress on #196
Closes #2856


Latest Update: v4 — Compare vs v3

📚 Full Patch History

Links show the diff between the row version and the column version.

Version v3 v2 v1 Base
v4 vs v3 vs v2 vs v1 vs Base
v3 vs v2 vs v1 vs Base
v2 vs v1 vs Base
v1 vs Base

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @joshlf, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a foundational generic framework for pointer projection and casting within the zerocopy crate. By defining Project and Cast traits, it establishes a standardized and extensible way to handle various pointer transformations, from simple address-preserving casts to complex field projections. This refactoring aims to consolidate existing, often repetitive, unsafe pointer logic into a more robust and unified system, paving the way for safer and more efficient low-level memory operations.

Highlights

  • New Generic Projection/Cast Framework: Introduced unsafe trait Project and unsafe trait Cast to provide a generic and unified mechanism for pointer conversions. Cast is specifically for address-preserving casts, while Project covers more general conversions like field projections.
  • Unification of Existing Casts: The new framework unifies various existing pointer operations, including PtrInner/Ptr casts, field projections, and SizeEq casts, under a consistent trait-based system.
  • Reduced Unsafe Boilerplate: A significant amount of unsafe, derive-generated code has been replaced by leveraging this new generic machinery, improving code maintainability and reducing potential for errors.
  • Refactored SizeEq Trait: The SizeEq trait now uses an associated type CastFrom which implements cast::Cast, allowing for more flexible and composable casting logic.
  • Updated PtrInner and Ptr Methods: Core pointer manipulation methods like cast_sized, cast, project, and transmute_unchecked have been updated to utilize the new Project and Cast traits, streamlining their implementation.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@codecov-commenter
Copy link

codecov-commenter commented Dec 23, 2025

Codecov Report

❌ Patch coverage is 71.55172% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.90%. Comparing base (b99b86d) to head (f675ef9).

Files with missing lines Patch % Lines
src/layout.rs 25.00% 30 Missing ⚠️
src/impls.rs 0.00% 2 Missing ⚠️
src/pointer/mod.rs 96.29% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2860      +/-   ##
==========================================
- Coverage   91.94%   91.90%   -0.05%     
==========================================
  Files          20       20              
  Lines        5824     5778      -46     
==========================================
- Hits         5355     5310      -45     
+ Misses        469      468       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant and well-designed refactoring of the pointer casting and projection framework by adding the Project and Cast traits. This unification simplifies the codebase and makes the casting logic more extensible and type-driven. The changes across the files are consistent with this new design.

My review focuses on the correctness and safety of these new abstractions. I've found a few places where // SAFETY: TODO comments indicate missing safety justifications for unsafe blocks. It's important to fill these in to ensure the soundness of the library. Most notably, I've identified one InvariantsEq implementation in zerocopy-derive/src/enum.rs that appears to be incorrect and could lead to unsoundness. Please address these points to finalize this excellent refactoring.

Comment on lines +943 to +944
// SAFETY: TODO
unsafe { Ptr::from_inner(ptr) }
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The safety justification for this unsafe block is missing. Please add a SAFETY comment explaining why it's sound to create a Ptr from the result of the projection.

A proper justification would likely involve reasoning about how the invariants of Ptr are upheld:

  1. The aliasing invariant I::Aliasing is preserved because field projection does not introduce new aliasing concerns.
  2. The alignment invariant is relaxed to Unaligned, which is always safe.
  3. The validity invariant I::Validity is preserved due to the T::Type: CastableFrom<T, I::Validity, I::Validity> bound.
  4. The inner PtrInner invariants (related to provenance and lifetime) are upheld by the contract of HasField::project.

Comment on lines 151 to 161
// SAFETY: TODO
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<#variant_struct_ident #ty_generics> for ___ZerocopyVariants #ty_generics #where_clause {}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

A safety justification is needed for this InvariantsEq implementation. Please add a SAFETY comment explaining why it's sound to have &___ZerocopyVariants and &___ZerocopyVariantStruct pointing to the same memory simultaneously. The justification should cover that ___ZerocopyVariants is a union containing the variant struct, and accessing it is done through controlled means within the derive logic, preventing misuse.

Comment on lines +359 to +369
// SAFETY: TODO
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This unsafe impl is missing its safety justification. Please add a SAFETY comment explaining why InvariantsEq<___ZerocopyRawEnum, Self> holds. The justification should detail how ___ZerocopyRawEnum is a layout-compatible representation of the enum Self, and therefore having references to both for the same memory region is safe under the controlled conditions of the derive macro.

Comment on lines 387 to 393
// SAFETY: TODO
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Please add a safety justification for this Immutable implementation. ___ZerocopyTag is an enum with a primitive representation and no fields, so it contains no UnsafeCells, making it Immutable. A brief SAFETY comment to this effect would be appropriate.

Suggested change
// SAFETY: TODO
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
// SAFETY: `___ZerocopyTag` is a `repr(Int)` enum with no fields, and
// thus contains no `UnsafeCell`s.
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}

@joshlf joshlf force-pushed the Gdeb4f5a282b778a82175703218739a93074d0cc4 branch from 01addfc to f291309 Compare December 23, 2025 15:29
@joshlf
Copy link
Member Author

joshlf commented Dec 23, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a generic framework for pointer projections and casts using the new Project and Cast traits. This is a significant and valuable refactoring that unifies various casting and projection mechanisms throughout the codebase, making them more composable and robust. The changes are extensive, touching many core parts of the pointer manipulation logic.

While the overall direction is excellent, I've found several critical issues related to TODOs in SAFETY comments, particularly concerning potential soundness holes with InvariantsEq implementations for enums and a missing safety justification for a new public project method. These must be addressed to ensure the soundness guarantees of zerocopy are upheld. I've also pointed out a few other places where TODOs in safety comments should be replaced with proper justifications.

Comment on lines +943 to +944
// SAFETY: TODO
unsafe { Ptr::from_inner(ptr) }
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The SAFETY comment for this unsafe block is a TODO. This is a public function, and the soundness of its unsafe usage must be thoroughly justified. The current implementation relies on T::Type: CastableFrom<T, I::Validity, I::Validity>, but it's not clear how this ensures aliasing invariants are maintained, especially since HasField::project could project to a field with different aliasing properties (e.g., a field containing an UnsafeCell inside a struct that is otherwise Immutable). Please provide a full safety justification or consider making this function unsafe.

Comment on lines +150 to +162
let invariants_eq_impl: TokenStream = parse_quote! {
// SAFETY: TODO
//
// TODO: This might not actually be sound – it's possible for there
// to be `UnsafeCell`s in some but not all enum variants.
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<#variant_struct_ident #ty_generics> for ___ZerocopyVariants #ty_generics #where_clause {}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This SAFETY comment contains a TODO and a note that this implementation might be unsound. This is a major soundness concern. If different enum variants have different UnsafeCell layouts, implementing InvariantsEq for the union of variant structs (___ZerocopyVariants) could lead to aliasing violations and undefined behavior. This must be resolved before merging. Either the implementation needs to be proven sound, or it needs to be guarded by stronger trait bounds or removed.

Comment on lines +361 to +370
let invariants_eq_impl = quote! {
// SAFETY: TODO
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<___ZerocopyRawEnum #ty_generics> for #self_ident #ty_generics #where_clause {}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This SAFETY comment is a TODO. Similar to the InvariantsEq implementation for ___ZerocopyVariants, this implementation for the raw enum wrapper could be unsound if different variants have conflicting UnsafeCell layouts. This needs a proper safety justification.

Comment on lines 390 to 393
// SAFETY: TODO
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This SAFETY comment is a TODO. While this Immutable implementation for ___ZerocopyTag seems correct because it's a C-like enum with no fields and thus cannot contain an UnsafeCell, the safety justification should be explicitly written out rather than leaving a TODO.

Suggested change
// SAFETY: TODO
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}
// SAFETY: `___ZerocopyTag` is a C-like enum with no fields, so it cannot
// contain an `UnsafeCell`.
unsafe impl ::zerocopy::Immutable for ___ZerocopyTag {
fn only_derive_is_allowed_to_implement_this_trait() {}
}

Comment on lines +404 to +415
// SAFETY: TODO
unsafe impl #generics #zerocopy_crate::HasField<(), { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(tag) }> for ___ZerocopyRawEnum #ty_generics {
fn only_derive_is_allowed_to_implement_this_trait() {}

type Type = ___ZerocopyTag;

#[inline(always)]
unsafe fn project(slf: *mut Self) -> *mut Self::Type {
slf.cast()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This SAFETY comment is a TODO. The implementation seems sound because tag is the first field of a #[repr(C)] struct, making a pointer cast valid for projection. Please add the explicit safety justification.

Suggested change
// SAFETY: TODO
unsafe impl #generics #zerocopy_crate::HasField<(), { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(tag) }> for ___ZerocopyRawEnum #ty_generics {
fn only_derive_is_allowed_to_implement_this_trait() {}
type Type = ___ZerocopyTag;
#[inline(always)]
unsafe fn project(slf: *mut Self) -> *mut Self::Type {
slf.cast()
}
}
// SAFETY: `___ZerocopyRawEnum` is a `#[repr(C)]` struct and `tag` is its
// first field, so a pointer cast is a valid projection.
unsafe impl #generics #zerocopy_crate::HasField<(), { #zerocopy_crate::STRUCT_VARIANT_ID }, { #zerocopy_crate::ident_id!(tag) }> for ___ZerocopyRawEnum #ty_generics {
fn only_derive_is_allowed_to_implement_this_trait() {}
type Type = ___ZerocopyTag;
#[inline(always)]
unsafe fn project(slf: *mut Self) -> *mut Self::Type {
slf.cast()
}
}

Add `unsafe trait Project` and `unsafe trait Cast: Project`. `Cast` is
implemented for any address-preserving cast, while `Project` generalizes
to conversions which may not preserve the address of the referent (ie,
field projections). Use these (mostly `Cast`, but some `Project`) to
unify `PtrInner`/`Ptr` casts, field projections, and `SizeEq` casts.
Replace a good amount of unsafe derive-generated code with uses of this
machinery.

Makes progress on #196
Closes #2856

gherrit-pr-id: Gdeb4f5a282b778a82175703218739a93074d0cc4
@joshlf joshlf force-pushed the Gdeb4f5a282b778a82175703218739a93074d0cc4 branch from f291309 to f675ef9 Compare December 23, 2025 15:38
//
// TODO: This might not actually be sound – it's possible for there
// to be `UnsafeCell`s in some but not all enum variants.
unsafe impl #impl_generics #zerocopy_crate::pointer::InvariantsEq<#variant_struct_ident #ty_generics> for ___ZerocopyVariants #ty_generics #where_clause {}
Copy link
Member Author

Choose a reason for hiding this comment

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

We might want to just replace BecauseInvariantsEq with BecauseImmutable and then impl Immutable for ___ZerocopyVariants and each___ZerocopyVariantStruct_Xxx. We currently require all unions to be Immutable, so this should be fine – we can just add an OuterEnum: Immutable bound to each Immutable impl block (where OuterEnum is the name of the type being derived on).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support HasField-based projection in Ptr

2 participants