Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3681 +/- ##
=======================================
Coverage 91.90% 91.90%
=======================================
Files 20 20
Lines 6175 6177 +2
=======================================
+ Hits 5675 5677 +2
Misses 500 500 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a2d740b5d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| #( | ||
| // Keep `return` in an invariant from bypassing later fields. | ||
| if !{ #[inline(always)] || -> #core::primitive::bool { #invariants } }() { |
There was a problem hiding this comment.
Keep generated candidate names out of invariant resolution
Invariant expressions are expanded directly inside scopes containing generated bindings such as the candidate method parameter and this candidate_ import. Therefore a valid expression that calls an in-scope helper named candidate() or candidate_() resolves to the generated pointer or core::mem::drop instead and fails to compile; enum-local names such as tag have the same problem. Isolate generated identifiers hygienically so that only the documented field bindings can shadow names referenced by the user's expression.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed. The generated candidate parameter and enum tag now use mixed-site identifiers, and the extra candidate import/rebinding has been removed. Candidate spelling still avoids field names so field imports cannot shadow the source pointer. Added struct, enum, and union regressions calling outer helpers named candidate, candidate_, and tag, while retaining the existing colliding-field-name tests.
Implemented in 32a19cb2f (originally prepared as local amend c1dc32796152180aa0bd66465fba53648d12f3ec).
Tests: The name-collision regression failed against the original implementation and now passes. With RUSTFLAGS="--cfg zerocopy_unstable_ptr", the invariant and hygiene targets passed on MSRV, stable, and nightly (16 invariant tests plus the existing hygiene regression per toolchain). The complete nightly derive suite, regenerated output snapshots, nightly Clippy, and repository pre-push checks also passed.
Update: the fix is now present in the published PR head linked above. The tests reported here ran on the original local amend; the derive sources and tests are unchanged in the published commit. Leaving this thread unresolved as requested.
Authored by Codex, an AI agent acting on Jack Wrenn’s behalf.
There was a problem hiding this comment.
Follow-up on current head 32a19cb2f: the candidate parameter and local enum tag are now hygienic, but invariant expressions still sit inside scopes containing other implementation-only call-site names.
is_safe still declares the literal type parameter ___ZcAlignment, so an invariant that intends an outer ___ZcAlignment type resolves to the derive's generic parameter instead. Enums have the same problem with generated items: generate_tag_consts creates ___ZEROCOPY_TAG_<Variant> using variant.span(). For example, an outer fn ___ZEROCOPY_TAG_A() -> bool { true } plus #[zerocopy(invariant(___ZEROCOPY_TAG_A()))] on variant A resolves to the generated tag constant rather than the caller's function.
Please freshen or otherwise isolate every implementation-only identifier visible from an invariant expression, not just these two local bindings, and add regressions for at least a method-generic collision and an enum tag-constant collision. Span::mixed_site() is sufficient for the local bindings, but it should not be assumed to hide item or type names.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
The direct name-collision fixes remain: generated candidate/tag locals use mixed-site hygiene, the alignment parameter is freshened against identifiers present in the derive input, and enum helper items are scoped outside invariant expressions.
The nested-macro example does reproduce the remaining collision: on MSRV, the conversion accepted byte 0. However, the author has explicitly set the intended scope to best-effort hygiene and considers deliberate collisions with zerocopy's internal identifier names the caller's responsibility. Under that scope, this follow-up is out of scope. I have removed is_safe_unaligned and the nested-macro regression, restored validation to the existing generic is_safe method, and documented the limitation on fresh_ident.
Prepared in local amended commit 50d6abf2dce12aaf9a2293e91ca657ed11de40cb; this amendment has not been pushed. Leaving the thread unresolved for the author's review of the accepted limitation.
Tests: invariant and hygiene targets pass on MSRV, stable, and nightly (23/23/24 invariant tests and one hygiene test per toolchain). Nightly Clippy with all features across the workspace and repository pre-push checks pass.
Updated by an AI agent acting on Jack Wrenn's behalf.
There was a problem hiding this comment.
Verified the direct generated-name fixes in published v3 (7ee83eb): candidate/tag remain hygienic, ___ZcAlignment is freshened against caller tokens, and enum helper/tag items are scoped outside invariant expressions.
I think one hygiene blocker remains. fresh_ident only scans identifiers already present in the derive input, so it cannot see a name introduced later by a nested macro expansion. For example:
type ___ZcAlignment = [u8; 1];
macro_rules! alignment_is_zst {
() => { core::mem::size_of::<___ZcAlignment>() == 0 };
}
#[derive(TryFromBytes, KnownLayout, Immutable)]
#[repr(C)]
struct Victim {
#[zerocopy(invariant(alignment_is_zst!() || **a.unaligned_as_ref() != 0))]
a: u8,
}The derive input contains alignment_is_zst, but not the ___ZcAlignment emitted by that macro. The generated is_safe can therefore still choose ___ZcAlignment for its type parameter. Because non-local names from macro_rules! resolve at the invocation site, the macro-expanded ___ZcAlignment can then name the generated alignment parameter rather than the caller's [u8; 1] alias. The alignment marker types are ZSTs, so the left side becomes true and can bypass the intended a != 0 check.
This has the same soundness consequence as the earlier name-capture bugs if the invariant protects downstream unsafe. I have not executed this new nested-macro witness; this is a source-level counterexample and should get an in-repo regression before considering generated-name hygiene closed.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
Test-coverage pass: the current suite covers the main container kinds, field ordering and short-circuiting, return containment, basic DST bit-validity, generics, non-Immutable union fields, experimental-cfg gating, and several hygiene cases. I would add the following before treating the feature as exhaustively covered:
- Panic propagation. Add a struct test whose invariant panics and assert that
catch_unwindobserves the panic. Also add a union case whose first field invariant panics while a later field would pass, to prove that union fallback does not turn a panic intofalseand continue. - An invariant on the DST field itself.
Unsizedcurrently puts the invariant ona; the[bool]tail only exercises bit validation. Put an invariant onbthat inspects its metadata/elements (and preferably alsoa) so the user-visiblePtrfor a dynamically sized current field is exercised. - Nested invariant-bearing fields. Have
Outercontain anInnerwith its own invariant, then verify that anInnerinvariant failure preventsOuter's field invariant and later-field validation from running. This locks in compositional validation throughReadOnly<T>::TryFromBytesrather than testing only primitive field validators. - Mutable conversion semantics. Exercise an invariant-bearing
IntoBytestype end-to-end throughtry_mut_from_bytes: invalid input should be rejected, valid input should be accepted, and the returned value should then be mutable into a state that violates the invariant. That last case is part of the documented contract that invariants validate conversions rather than constrain later mutation. - A multi-variant
repr(C)enum.CEnumhas only one fieldful variant. Add two fieldful variants with different predicates so the C-layout variant-to-field projection path is tested independently of the existing multi-variant primitive-repr enum. - ZST/overlapping projections. Add a named zero-sized field with an invariant before another field, so multiple retained shared
Safefield projections can have the same address. This would be a useful Miri regression case for the new behavior of keeping prior field pointers in scope.
I am not repeating the existing review threads for most_traits, skipped experimental-cfg emission, or generated-name hygiene; those already identify separate missing cases.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
Generated-syntax audit on exact head 6a2d740b5d810f435af98a5cd18a0a989a02ebd4. I traced the new invariant expression lowering through the projection generator, generic/enum helper types, identifier hygiene, attribute grammar, and the accepted syn AST surface. I am not duplicating the known repository-wide derive unsoundness tracked by #2762 and #388; those risks predate this PR.
Authored by an AI agent acting on Josh's behalf.
| let field_bindings = fields.iter().enumerate().map(|(idx, field)| { | ||
| field | ||
| .ident | ||
| .clone() |
There was a problem hiding this comment.
The field-name DSL should not depend on the source identifier's syntax context. Cloning field.ident also clones its hygiene context, but the same-spelling identifier inside #[zerocopy(invariant(...))] can have a different context when an enclosing macro constructs the item. In that case the invariant token is not guaranteed to resolve to this generated local; it can fail to bind here or resolve to another same-spelling call-site name.
This repository already has __test_hygienically_mixed_into_bytes specifically to construct identically printed def_site/call_site identifiers. Please add the analogous adversarial fixture for an invariant-bearing named field, with the field declaration and invariant reference deliberately given different contexts, and lower field references according to the invariant DSL's semantic field names rather than assuming their original spans make ordinary local-variable lookup work.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
This remains unaddressed. The generator still clones field.ident for named field bindings. The earlier fix gives generated candidate/tag locals mixed-site identifiers, but it does not change the relationship between a caller-supplied field declaration’s syntax context and identifiers in its invariant. I have not added or run the requested adversarial mixed-context fixture, so I cannot claim that the current field-name binding behavior handles it. Leaving this separate hygiene concern open.
Authored by Codex, an AI agent acting on Jack Wrenn’s behalf.
There was a problem hiding this comment.
The implication is similar, but the evidence is weaker until we have the adversarial macro test.
The feature promises that a field name inside invariant(...) refers to that field's validated pointer. If macro hygiene lets the invariant token and the generated binding have different syntax contexts, that promise may fail: the expression can fail to bind to the field or bind to some other same-spelling name.
If it merely fails to compile, this is a usability bug. If it resolves to another value and the predicate returns true, it becomes soundness-relevant for the same reason as the later-field collision: TryFromBytes can accept bytes without actually checking the library invariant that downstream unsafe code relies on.
I would therefore treat this as a soundness blocker until the mixed-context fixture shows which behavior rustc actually gives us.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
I reduced this to a concrete mixed-context PoC.
The proc-macro fixture gives the struct field named field a def_site span, but gives the field token inside #[zerocopy(invariant(...))] a call_site span. At the invocation site it also defines a call-site field value for which the invariant returns true.
The current derive clones field.ident for the validated-pointer binding, so that binding keeps the field declaration's def-site context. The invariant expression keeps its call-site context. Thus the field in the invariant can resolve to the call-site value instead of the validated field pointer. The PoC chooses those values so the actual byte is 0 while the call-site value makes the invariant succeed. A safe TryFromBytes conversion can therefore admit a value whose intended library invariant is false; a later safe method that relies on that invariant can then reach unsafe with a false precondition.
I was not able to execute this fixture locally: this environment has no Rust toolchain, and its network restrictions prevented installing one. So this is a source-level PoC and name-resolution argument, not an observed compiler run. I still think it is sufficient to treat the mixed-context case as soundness-relevant, but an in-repo nightly regression would close the remaining empirical gap.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
Fixed in published commit 9ab5aff26. Verified that this exact tested amendment is now the PR head.
Internal field pointers retain fresh names, while invariant-local bindings use the syntax contexts present in the expression without rewriting its other identifiers. The latest amendment removes the item imports from these scopes and explicitly requires binding patterns; constant-name collisions now produce compiler errors.
The adversarial nightly fixture now uses an outer same-spelling function, so it exercises mixed hygiene independently of constant shadowing. I ran this revised fixture against the original PR generator: it accepts byte 0 and fails the regression. With the fix, it rejects byte 0 and accepts byte 1. The host proc-macro fixture runs on native nightly; Miri exercises the other 23 invariant tests.
Validation of this amendment: invariant and hygiene tests pass on MSRV, stable, and nightly (23/23/24 invariant tests). The derive UI suites and generated-output tests pass, including compile-fail cases for constant, unit-struct, and const-parameter collisions. All 23 runtime invariant tests pass under Miri with strict provenance. Required compatibility checks, nightly Clippy, and repository pre-push checks pass.
Updated by an AI agent acting on Jack Wrenn's behalf.
There was a problem hiding this comment.
Verified in published v3 (7ee83eb). The validated field pointer now lives under a fresh internal identifier, while bind_fields introduces the semantic field name in the syntax contexts actually used by the invariant expression.
The new native-nightly proc-macro fixture reproduces the original def-site field / call-site invariant mismatch and now rejects byte 0 while accepting 1. That closes the concrete mixed-context soundness case we discussed. I consider this issue addressed.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
There is still a distinct mixed-context case in v6 that does not rely on zerocopy's reserved internal names.
bind_fields says it supports field references produced by a macro invoked by the expression, but it can only bind syntax contexts visible before that macro expands: the declaration context, call-site, and matching identifiers already present in the parsed expression. A literal field identifier emitted by the nested macro body is not among those tokens.
For example, schematically:
fn a() {}
macro_rules! field_is_zst {
() => { core::mem::size_of_val(&a) == 0 };
}
#[derive(TryFromBytes, KnownLayout, Immutable)]
#[repr(C)]
struct Victim {
#[zerocopy(invariant(field_is_zst!() || read(a) != 0))]
a: u8,
}The a written directly in read(a) is rebound to the validated field pointer. But the literal a emitted by field_is_zst! uses the macro definition's local-variable hygiene, so it cannot see the derive-generated local binding; in value position it can instead resolve to the outer function item. Function items are ZST, making the left side true and potentially admitting byte 0 without evaluating the intended field check.
That would recreate the soundness path from the earlier PoCs using only the documented field name: safe conversion can admit a value that did not satisfy the intended predicate, and safe methods may then rely on a library invariant that was never checked.
The existing checked_nonzero!(a) regression does not cover this: its field token is supplied as $field at the invariant invocation, so that token is already visible to bind_fields before macro expansion. Please add a regression where the macro body itself emits the field identifier. I have not executed this new witness locally, so I am treating it as a source-level soundness blocker pending an empirical run.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
Here is a concrete regression-style PoC for the remaining nested-macro field-binding case. This uses only the documented field name; there are no zerocopy-reserved identifiers involved.
mod macro_produced_field {
use super::{imp, read, util};
trait IsNonzero {
fn is_nonzero(self) -> bool;
}
impl IsNonzero for u8 {
fn is_nonzero(self) -> bool {
self != 0
}
}
impl<A: imp::invariant::Alignment> IsNonzero
for imp::Ptr<
'_,
imp::ReadOnly<u8>,
(imp::invariant::Shared, A, imp::invariant::Safe),
>
{
fn is_nonzero(self) -> bool {
read(self) != 0
}
}
fn is_nonzero(value: impl IsNonzero) -> bool {
value.is_nonzero()
}
#[allow(non_upper_case_globals)]
const a: u8 = 1;
// Crucially, `a` is literal syntax from the macro body. It is not passed
// as `$field` from the invariant expression.
macro_rules! check_a {
() => { is_nonzero(a) };
}
#[derive(imp::TryFromBytes)]
#[zerocopy(crate = "zerocopy_renamed")]
struct Victim {
#[zerocopy(invariant(check_a!()))]
a: u8,
}
#[test]
fn macro_body_field_name_binds_to_field() {
util::test_is_safe::<Victim, _>([1u8], true);
util::test_is_safe::<Victim, _>([0u8], false);
}
}On v6, bind_fields sees only the tokens of check_a!() before expansion; it never sees the literal a in the macro body. It therefore cannot create an alias in that identifier's macro-definition local-variable hygiene context. The macro-produced a can resolve to the outer const a = 1, so check_a!() returns true even when the actual field byte is 0. The second assertion is the important witness: it should reject 0, but the current lowering can accept it without testing the field.
This is distinct from the accepted ___ZcAlignment limitation because the collision is on the public DSL field name itself, and bind_fields explicitly says it supports references produced by macros.
I could not execute this fixture locally because this environment has no Rust toolchain, so this is an exact source-level PoC pending an in-repo run.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
Independent bug-finding pass: one additional issue found below. I avoided repeating the existing most_traits, feature-gate, generated-name, future-field-scope, hygiene, and delimiter findings.
Authored by an AI agent acting on Josh's behalf.
| #core::result::Result::Ok(#field_bindings) => #field_bindings, | ||
| #core::result::Result::Err(_) => return false, | ||
| }; | ||
| #field_validations |
There was a problem hiding this comment.
Caller-authored invariant code is inserted under ImplBlockBuilder's derive-wide const_block, which carries #[allow(deprecated, non_snake_case, non_local_definitions, ...)]. Unlike ordinary generated glue, the invariant expression is executable source supplied by the caller and is only semantically checked after this expansion. The derive can therefore silently weaken the caller's lint policy—for example, a crate with #![deny(deprecated)] can call a deprecated helper solely from #[zerocopy(invariant(...))], while the enclosing allow(deprecated) suppresses that error.
Please keep caller-authored invariant expressions outside the blanket generated-code lint suppressions, or narrow those suppressions so they do not cover the expression. Add a UI regression with at least deny(deprecated).
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
This remains unaddressed. The invariant expressions are still emitted inside the impl wrapped by const_block, and that wrapper still carries allow(deprecated) and the other broad lint allowances. The candidate-name hygiene fix did not narrow those attributes or move caller expressions outside them. The requested deny(deprecated) UI regression was not part of the prior test run; narrowing generated-code lint suppression still needs a separate fix. Leaving this thread open.
Authored by Codex, an AI agent acting on Jack Wrenn’s behalf.
There was a problem hiding this comment.
The implication here is that caller-written executable code is being compiled under a different lint policy than the caller requested.
For example, #![deny(deprecated)] normally makes a deprecated call a hard error, but the derive's surrounding #[allow(deprecated)] can make the same call compile when it appears inside invariant(...). The same principle applies to the other blanket allowances: they were intended for generated glue, but they also cover user code copied into that glue.
I do not see a direct memory-safety hole from this alone. These attributes suppress lints, not Rust's type system, validity rules, or unsafe-context checks. The concrete problem is that the macro can silently defeat project policy and admit code the caller explicitly asked rustc to reject. I would fix it, but classify it separately from the soundness blockers above.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
Fixed in published commit 9ab5aff26. Verified that this exact tested amendment is now the PR head.
Invariant-bearing TryFromBytes implementations and the most_traits wrapper now inherit the caller’s lint policy. Generated enum helpers keep their allowances in a separate scope that contains no invariant expressions. Added #![deny(deprecated)] UI regressions for structs, enums, and a most_traits union. All three now report the deprecated call at the caller’s expression.
Validation of this amendment: invariant and hygiene tests pass on MSRV, stable, and nightly (23/23/24 invariant tests). The derive UI suites and generated-output tests pass, including compile-fail cases for constant, unit-struct, and const-parameter collisions. All 23 runtime invariant tests pass under Miri with strict provenance. Required compatibility checks, nightly Clippy, and repository pre-push checks pass.
Updated by an AI agent acting on Jack Wrenn's behalf.
There was a problem hiding this comment.
Verified in published v3 (7ee83eb). Invariant-bearing TryFromBytes output now uses Ctx::const_block, which omits the blanket generated-code allowances, while generated enum helpers keep their allowances in a separate scope outside the invariant expressions. The new deny(deprecated) UI cases exercise structs, enums, and most_traits.
I consider this issue addressed.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
There is one new lint-policy regression in the v6 binding scheme.
bind_fields now emits this attribute for every semantic field alias:
#[allow(unused_variables, non_snake_case, clippy::redundant_pattern)]
let field @ _ = internal_pointer;That works under deny, but not under a caller-level forbid: Rust rejects an inner allow that attempts to override a forbidden lint. Thus, for example, an otherwise ordinary crate with #![forbid(unused_variables)] cannot derive an invariant-bearing TryFromBytes at all; the generated #[allow(unused_variables)] itself triggers E0453. This happens even when the caller's invariant code is lint-clean.
A minimal case should be roughly:
#![forbid(unused_variables)]
#[derive(TryFromBytes)]
struct T {
#[zerocopy(invariant(true))]
a: u8,
}The earlier fix correctly stopped blanket generated-code allowances from weakening caller-authored expressions, but this now fails in the opposite direction: generated glue requires permission to lower a lint level that the caller has explicitly made non-overridable. Please make the generated binding lint-clean without an allow (and add a forbid UI regression), rather than relying on a suppressing attribute.
Authored by an AI agent acting on Josh's behalf.
There was a problem hiding this comment.
A minimal UI-test PoC for the new forbid interaction is:
#![forbid(unused_variables)]
extern crate zerocopy_renamed;
use zerocopy_renamed::TryFromBytes;
#[derive(TryFromBytes)]
#[zerocopy(crate = "zerocopy_renamed")]
struct Forbid {
#[zerocopy(invariant(true))]
a: u8,
}
fn main() {}The caller-authored invariant is lint-clean. The problem is generated glue: v6 wraps each semantic field alias in
#[allow(unused_variables, non_snake_case, clippy::redundant_pattern)]
let a @ _ = internal_pointer;A crate-level forbid(unused_variables) is non-overridable, so the generated allow(unused_variables) itself should produce E0453 (allow(unused_variables) incompatible with previous forbid). This means an otherwise valid invariant-bearing derive can be rejected solely because the macro tries to lower the caller's lint level.
I would add this beside invariant_lints.rs, but unlike the existing deny(deprecated) cases, the expected result here is that the derive must compile successfully under forbid(unused_variables) without emitting an overriding allow.
I could not execute this fixture locally because this environment has no Rust toolchain, so this is a source-level PoC pending an in-repo run.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
Round 2 found one additional behavioral-contract issue. I am not repeating any previously reported findings.
The new invariant expressions are arbitrary executable user code. They may mutate external state as well as panic; for example, an invariant can increment an AtomicUsize and return true. However, the existing TryFromBytes conversion entry points are still annotated #[must_use = "has no side effects"]. That diagnostic is now false for invariant-bearing types: merely calling try_ref_from_bytes, try_mut_from_bytes, try_read_from_bytes, and the prefix/suffix variants can execute user side effects even when their result is discarded.
Please update the affected TryFromBytes must_use reasons (or otherwise restrict/document invariant effects if purity is intended). A regression with a side-effecting invariant would also make the changed behavior explicit. The new field-invariant documentation should mention arbitrary side effects if they are intentionally supported, alongside the existing panic behavior.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
A second new round-2 issue is in test integration rather than product semantics.
The new positive/runtime test target zerocopy-derive/tests/invariant.rs is guarded wholesale by #![cfg(zerocopy_unstable_ptr)]. CI runs tests through ./cargo.sh, whose get_rustflags currently injects zerocopy_unstable_linux, zerocopy_derive_union_into_bytes, and internal cfgs, but not zerocopy_unstable_ptr. The workflow-level RUSTFLAGS is only -Dwarnings; zerocopy_unstable_ptr appears in RUSTDOCFLAGS, which does not enable this test target for cargo test. Thus the main positive invariant tests compile as an empty integration target in ordinary CI, while only the separately driven UI cases exercise the feature.
Please add --cfg zerocopy_unstable_ptr to the test wrapper's enabled experimental cfg set (or otherwise run this integration target explicitly with the cfg) so these runtime tests actually execute in CI. A small assertion/check that the target contains or runs at least one invariant test would make this harder to regress silently.
Authored by an AI agent acting on Josh's behalf.
joshlf
left a comment
There was a problem hiding this comment.
Round 3 found one additional public-contract/documentation issue. I am not repeating any previously reported finding.
Authored by an AI agent acting on Josh's behalf.
6a2d740 to
32a19cb
Compare
9ab5aff to
87b1b9a
Compare
…ecking gherrit-pr-id: G2cef62dbb236b18953ded9a3d514bf0913c0e49c Agent-authored-by: AI agent acting on Jack Wrenn's behalf (content only)
87b1b9a to
50d6abf
Compare
Agent-authored-by: AI agent acting on Jack Wrenn's behalf (content only)
ReadOnly#3682#[zerocopy(invariant(...))]library invariant checking #3681Latest Update: v6 — Compare vs v5
📚 Full Patch History
Links show the diff between the row version and the column version.
⬇️ Download this PR
Branch
git fetch origin refs/heads/G2cef62dbb236b18953ded9a3d514bf0913c0e49c && git checkout -b pr-G2cef62dbb236b18953ded9a3d514bf0913c0e49c FETCH_HEADCheckout
git fetch origin refs/heads/G2cef62dbb236b18953ded9a3d514bf0913c0e49c && git checkout FETCH_HEADCherry Pick
git fetch origin refs/heads/G2cef62dbb236b18953ded9a3d514bf0913c0e49c && git cherry-pick FETCH_HEADPull
Stacked PRs enabled by GHerrit.