Skip to content

test(multitude): drive alignment guards through an injectable cap - #704

Open
Adomas Bekeras (AdomasBekeras) wants to merge 2 commits into
mainfrom
u/abekeras/multitude-align-cap
Open

test(multitude): drive alignment guards through an injectable cap#704
Adomas Bekeras (AdomasBekeras) wants to merge 2 commits into
mainfrom
u/abekeras/multitude-align-cap

Conversation

@AdomasBekeras

Copy link
Copy Markdown
Contributor

The bug

ADO 7707893: cargo test fails on a clean checkout.

The arena refuses allocations whose alignment reaches a cap. CHUNK_ALIGN is 64 KiB, and the smart-pointer cap is half of it, 32 KiB, because a smart pointer recovers its chunk header by masking the value pointer's offset within its chunk tile — a value aligned that far can land outside the first tile, where the mask finds a different chunk's header.

To test the rejection, the tests had to instantiate a type aligned at or above the cap. So they declared #[repr(align(32768))], #[repr(align(65536))] and #[repr(align(131072))] types. Some codegen backends cap type alignment at 8192 and refuse to compile such a type at all. The library built fine; three test binaries (arena, audit_repro, pin_support) and one doctest failed codegen.

Why the previous fix didn't hold

#501 gated the tests behind #[cfg(not(utc_backend))] (since renamed align_capped_backend), with the flag set by an out-of-tree CI pipeline. Three problems:

  • Nothing in this repo sets it, so the default build was the broken one. A contributor cloning the repo hits the failure; the gate only helps a build that already knows to opt in.
  • It's all-or-nothing. A backend that sets it loses the guard coverage entirely, on a live memory-safety check.
  • It missed the doctest, because rustdoc ignores RUSTFLAGS. Setting RUSTFLAGS would also have clobbered .cargo/config.toml's -C target-cpu=x86-64-v3.

This change

The test needs the type's alignment and the cap to meet. The old approach raised the alignment to the cap. This one lowers the cap to an alignment every backend compiles.

Arena gets a #[cfg(test)] alignment cap that the guards read:

#[cfg(not(test))]
fn chunk_align_cap(&self) -> usize { CHUNK_ALIGN }

#[cfg(test)]
fn chunk_align_cap(&self) -> usize { self.align_cap.get() }

Tests call capped_arena(), which sets the cap to 8192, and use shared helper types aligned to 4096 and 8192. Both boundaries stay reachable, and the production 2:1 ratio between the chunk cap and the smart-pointer cap is preserved, so each test still exercises the cap its entry point actually consults.

The cfg(test) field and the whole knob disappear from production builds — Arena's layout is unchanged.

Along the way:

  • Three duplicate MAX_SMART_PTR_ALIGN constants collapse into one accessor. All nine guard sites now route through rejects_smart_ptr_align / rejects_chunk_align.
  • ~46 over-alignment tests move from tests/ into #[cfg(test)] modules in src/ (arena/align_guard_tests.rs, bytemuck.rs, zerocopy.rs) so they can reach the knob.
  • The align_capped_backend cfg is deleted from the workspace Cargo.toml.
  • Net −48 lines.

Coverage

Nothing was dropped. Two additions beyond parity:

  • One test asserts the error is specifically is_alignment_too_large(), which nothing outside the deleted doctest checked before.
  • try_alloc_slice_fill_iter's guard had no over-alignment test at all; it does now.

Mutation-checked by hand: forcing rejects_smart_ptr_align to false fails 39 tests, forcing rejects_chunk_align to false fails 9.

Things worth a reviewer's attention

The guards are no longer const { }. They were if const { align_of::<T>() >= MAX_SMART_PTR_ALIGN }, folded at compile time by construction. They are now ordinary comparisons against an #[inline(always)] accessor. In release under cfg(not(test)) that accessor returns a literal and align_of::<T>() is a constant, so LLVM folds it; debug builds pay a compare. The guarantee is gone, the behaviour isn't. Restoring the guarantee would need a macro expanding to the const { } form under cfg(not(test)) — happy to add it if you'd rather have the certainty.

buffer_freezable still reads the real cap. It's used inside const { } on the Vec hot path, so making it cap-aware would put a runtime branch there. The consequence is that a capped arena is not a faithful model for Vec/String growth and freeze tests — documented on capped_arena() and set_align_cap(), and set_align_cap now asserts the cap can only be lowered. A new lib test asserts the arena's default caps equal CHUNK_ALIGN and max_smart_ptr_align(), so the two sources can't drift apart silently.

One over-aligned type survives. non_freezable_overaligned_vec_grows_via_oversized_path in tests/arena.rs still declares #[repr(align(32768))]. It's the one place where the alignment is the subject — it's what makes the element non-freezable — and it compiles because try_reserve never materialises the layout. That's an emergent property, not a guarantee, so there's now a comment naming it as the one fragile declaration left, to make a future failure diagnosable.

Verification

  • cargo test -p multitude --all-features passes on both the alignment-capped backend and an LLVM-backend toolchain.
  • cargo clippy -p multitude --all-features --all-targets -- -D warnings clean.
  • Formatted with the pinned nightly.
  • cargo spellcheck could not be run — the binary is broken in my environment (missing DLL). Please let CI cover it.

Not fixed here

A clean-checkout cargo build --workspace on the internal toolchain also fails in zeroize 1.9.0 (reached via fetch*rustlsaws-lc-rs) with codegen not yet implemented for Terminator_InlineAsm. Unrelated to alignment and not fixable in this repo. Worth tracking separately.

@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (7fc5cd5) to head (a0217cc).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #704    +/-   ##
========================================
  Coverage   100.0%   100.0%            
========================================
  Files         587      587            
  Lines       62997    63121   +124     
========================================
+ Hits        62997    63121   +124     
Flag Coverage Δ
linux 88.3% <100.0%> (-11.7%) ⬇️
linux-arm 88.3% <100.0%> (-11.7%) ⬇️
scheduled ?
windows 89.8% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 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.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

multitude

     Cloning origin/main
    Building multitude v0.9.0 (current)
       Built [   9.038s] (current)
     Parsing multitude v0.9.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-multitude-0_9_0-default-1502d9aa811debfb/target/doc/multitude.json
(supported formats are v55, v56, v57)

rallocator

     Cloning origin/main
    Building rallocator v0.1.0 (current)
       Built [   3.146s] (current)
     Parsing rallocator v0.1.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-rallocator-0_1_0-default-f875cd4edfafef03/target/doc/rallocator.json
(supported formats are v55, v56, v57)

@AdomasBekeras
Adomas Bekeras (AdomasBekeras) marked this pull request as ready for review August 31, 2026 09:09
Copilot AI lite review requested due to automatic review settings August 31, 2026 09:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes multitude’s clean-checkout cargo test failures on codegen backends that cap type alignment (e.g., 8192) by making the arena’s alignment-rejection caps injectable under cfg(test), then rewriting over-alignment tests to exercise the same guard boundaries using smaller, backend-portable alignments.

Changes:

  • Add a test-only alignment cap knob on Arena and route all alignment guards through rejects_smart_ptr_align / rejects_chunk_align.
  • Move/replace over-alignment coverage from crates/multitude/tests/* into unit tests in src/ (including a new arena/align_guard_tests.rs) using capped_arena() and shared aligned helper types.
  • Remove the align_capped_backend cfg wiring from the workspace.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/multitude/tests/zerocopy_integration.rs Removes backend-gated over-alignment integration tests now covered by in-crate unit tests.
crates/multitude/tests/pin_support.rs Removes the over-alignment test that depended on very large repr(align) values.
crates/multitude/tests/bytemuck_integration.rs Removes backend-gated over-alignment integration tests now covered by in-crate unit tests.
crates/multitude/tests/audit_repro.rs Removes backend-gated over-alignment regression coverage now covered by unit tests.
crates/multitude/tests/arena.rs Removes large-alignment test types/cases and documents the one remaining >8192 aligned type.
crates/multitude/src/zerocopy.rs Adds cfg(test) unit tests that drive alignment guards using capped_arena() helpers.
crates/multitude/src/tests_support.rs Adds shared test-only cap constants, capped_arena(), and aligned helper types for guard testing.
crates/multitude/src/error.rs Updates AllocError::is_alignment_too_large docs to avoid non-portable doctest alignment examples.
crates/multitude/src/bytemuck.rs Adds cfg(test) unit tests that drive alignment guards using capped_arena() helpers.
crates/multitude/src/arena/mod.rs Adds test-only alignment cap storage plus shared *_align_cap / rejects_* helpers.
crates/multitude/src/arena/alloc_value.rs Replaces duplicated const caps with arena-based guard helpers for sized smart-pointer paths.
crates/multitude/src/arena/alloc_unsized.rs Routes unsized/DST smart-pointer alignment checks through arena guard helpers.
crates/multitude/src/arena/alloc_slice_ref.rs Routes simple-reference slice alignment checks through arena chunk-cap helper.
crates/multitude/src/arena/alloc_slice_box.rs Routes boxed-slice alignment checks through arena smart-pointer-cap helper.
crates/multitude/src/arena/alloc_slice_arc.rs Routes arc/rc-slice alignment checks through arena smart-pointer-cap helper.
crates/multitude/src/arena/align_guard_tests.rs New unit-test suite asserting each entry point rejects alignments at/above the relevant cap.
crates/multitude/src/allocator_impl.rs Uses arena-derived smart-pointer cap for allocator alignment rejection + test updates.
Cargo.toml Removes align_capped_backend from the workspace check-cfg list.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +72 to +74
assert!(align_of::<SmartPtrOverAligned>() == TEST_SMART_PTR_ALIGN);
assert!(align_of::<SmartPtrOverAlignedDrop>() == TEST_SMART_PTR_ALIGN);
assert!(align_of::<ChunkOverAligned>() == TEST_CHUNK_ALIGN);
Comment on lines +46 to +48
/// Aligned exactly at the smart-pointer cap: rejected by every
/// smart-pointer entry point, accepted by the simple-reference paths.
#[derive(Clone, Copy, Debug)]
Comment on lines +593 to +597
assert!(cap.is_power_of_two(), "alignment cap must be a power of two");
assert!(
cap <= CHUNK_ALIGN,
"the cap may only be lowered: raising it would let the guards accept alignments no chunk can satisfy"
);
Comment thread crates/multitude/src/error.rs Outdated
/// Such a request can never
/// succeed, regardless of how much memory is available.
/// Such a request can never succeed, regardless of how much memory is
/// available. The arena rejects any value whose alignment reaches half

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖: Non-blocking — is_alignment_too_large presents the smart-pointer cap as universal. Document both boundaries: smart-pointer and single-value allocations reject at 32 KiB; simple-reference slices reject at 64 KiB.

Comment thread crates/multitude/src/zerocopy.rs Outdated
#[test]
fn try_alloc_ref_over_aligned_returns_err() {
let arena = capped_arena();
arena.zerocopy().try_alloc::<ChunkOverAligned>().unwrap_err();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖: Non-blocking — these scalar zerocopy tests use the chunk-cap fixture, so they cannot detect a guard loosened from 4 KiB to 8 KiB. Use SmartPtrOverAligned in try_alloc_ref_over_aligned_returns_err and alloc_ref_panics_on_over_aligned, matching the bytemuck tests.


#[cfg(feature = "zerocopy")]
// SAFETY: a `u8` plus alignment padding; every byte pattern is valid.
unsafe impl zerocopy::TryFromBytes for $ty {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖: Non-blocking — these manual zerocopy implementations bypass the traits' derive-only seal and hidden internals. Replace the zerocopy arms of impl_zero_valid! with feature-gated derives on the fixture types. The current u8 fixtures are bit-valid, but derives prevent this generic macro from silently asserting validity for a future niche-bearing type.

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.

[Copilot speaking]

Derive the bytemuck and zerocopy marker traits instead of implementing their internals

The new shared alignment fixtures manually implement bytemuck::Zeroable, zerocopy::TryFromBytes, and zerocopy::FromZeros. The zerocopy implementations define TryFromBytes::is_bit_valid and call hidden only_derive_is_allowed_to_implement_this_trait methods that its public documentation explicitly reserves for derive-generated code, even though this crate enables both dependencies' derive features and declares the fixtures in this module.

Reproducible reasoning: crates/multitude/Cargo.toml enables bytemuck and zerocopy with their derive features, and Cargo.lock selects zerocopy 0.8.52. SmartPtrOverAligned and ChunkOverAligned are repr(C) wrappers containing only a u8, so the supported derives can prove their marker contracts directly. Zerocopy's documented contract directs implementers to derive TryFromBytes, permits callers to trust a successful validity result, and warns that hidden internals can change in otherwise compatible releases in ways that affect soundness. FromZeros extends TryFromBytes, and deriving FromZeros emits both implementations after checking the fields. Although the handwritten true validity result is reasonable for these fixtures, the macro unnecessarily assumes ownership of unsafe contracts and bypasses the dependencies' supported compile-time analysis.

Consequence: A routine zerocopy dependency update can change hidden trait machinery or validity requirements while these handwritten implementations either stop compiling or continue with an outdated proof of bit validity, potentially making the test-only code unsound and blocking dependency updates.

Recommended action: Apply conditional derives to both fixture structs, using cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable)) and cfg_attr(feature = "zerocopy", derive(zerocopy::FromZeros)), then remove impl_zero_valid and its handwritten marker implementations. This uses the dependencies' supported compile-time-checked path; deriving zerocopy's FromZeros also generates the required TryFromBytes implementation.

References:

Impacted locations:

  • crates/multitude/src/tests_support.rs:77-105
  • crates/multitude/src/tests_support.rs:87-100

@martin-kolinek martin-kolinek left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖: Approved.

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.

[Copilot speaking]

Published 12 findings. One finding follows up on an existing discussion thread.

See diagnostics
Diagnostic Value
Cache Miss

Comment on lines +69 to +70
// `repr(align)` takes a literal, so the helper types cannot name the cap
// constants directly. Keep them in step.

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.

[Copilot speaking]

Replace the colloquial maintenance instruction with an explicit statement

The comment explaining the compile-time alignment assertions ends with “Keep them in step.” This idiom leaves both the antecedent of “them” and the precise maintenance requirement implicit, even though the following assertions enforce a specific relationship between the literal type alignments and the cap constants.

Reproducible reasoning: The English prose guideline requires professional language and explicitly asks authors to avoid colloquialisms. “Keep them in step” is an idiomatic expression rather than a direct description of the invariant. Reading the following assertions shows that the intended instruction is to keep the literal repr(align) values synchronized with TEST_SMART_PTR_ALIGN and TEST_CHUNK_ALIGN, so the comment can state that purpose directly.

Consequence: A maintainer, especially one unfamiliar with the idiom, may need to inspect and reverse-engineer the assertions to determine which values must remain synchronized when changing the test cap.

Recommended action: Replace the final sentence with direct wording such as: “The assertions below keep the literal type alignments synchronized with the cap constants.”

Impacted locations:

  • crates/multitude/src/tests_support.rs:69-70

Comment on lines +454 to +455
#[cfg(test)]
mod tests {

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.

[Copilot speaking]

Run the relocated bytemuck and zerocopy tests in default test builds

The relocated bytemuck and zerocopy alignment tests are nested in source modules that remain included only under their Cargo features. The Arena accessors and test fixture marker implementations they use are likewise feature-gated. Because Rust evaluates these enclosing gates before discovering the nested #[cfg(test)] modules, an ordinary cargo test build excludes the relocated coverage even though the optional crates are available as dev-dependencies.

Reproducible reasoning: A unit-test module can run only when every enclosing module and API it uses is compiled. crates/multitude/src/lib.rs excludes the bytemuck and zerocopy modules unless their features are active, crates/multitude/src/arena/mod.rs similarly excludes the corresponding Arena accessors, and crates/multitude/src/tests_support.rs excludes the required marker implementations. The inner #[cfg(test)] attributes therefore cannot make these tests participate in a default test build. This conflicts with the repository convention of making feature-dependent code available under cfg(any(test, feature = "...")) when dev-dependencies supply the optional crates.

Consequence: Regressions in the bytemuck or zerocopy alignment adapters can pass the ordinary test build; the relocated coverage runs only when callers explicitly enable the optional features or use all features.

Recommended action: Include the bytemuck and zerocopy module declarations and their Arena accessors under cfg(any(test, feature = "...")), while retaining feature-only doc(cfg(...)) annotations for published API documentation. Make the test-only fixture marker implementations available unconditionally within tests_support.rs, or use the matching any(test, feature = "...") conditions. Keep both optional crates available as non-optional dev-dependencies and enable zerocopy's derive feature for the dev-dependency as for the regular dependency.

References:

  • crates/multitude/Cargo.toml feature and dev-dependency declarations

Impacted locations:

  • crates/multitude/src/bytemuck.rs:454-455
  • crates/multitude/src/zerocopy.rs:451-452
  • crates/multitude/src/tests_support.rs:83-99
  • crates/multitude/src/lib.rs:518-529
  • crates/multitude/tests/bytemuck_integration.rs:151-230
  • crates/multitude/tests/zerocopy_integration.rs:124-196
  • crates/multitude/src/bytemuck.rs:455-536
  • crates/multitude/src/zerocopy.rs:452-533
  • crates/multitude/src/lib.rs:518-529
  • crates/multitude/src/arena/mod.rs:516-542
  • crates/multitude/Cargo.toml:64-100

#[test]
fn try_alloc_slice_box_over_aligned_returns_err() {
let arena = capped_arena();
arena.bytemuck().try_alloc_slice_box::<SmartPtrOverAligned>(4).unwrap_err();

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.

[Copilot speaking]

Explain the non-empty slice length used by the adapter guard tests

The new bytemuck and zerocopy slice rejection tests repeatedly use the element count 4, although the tested condition depends on element alignment and only needs a non-empty slice to select the initialization path. The test modules do not say whether that count is significant or arbitrary.

Reproducible reasoning: Each affected test changes the allocation family and aligned fixture while keeping the count fixed. The guard executes from align_of::<T>() before allocation size can affect the result, so no assertion depends on four elements. A reader extending the matrix must otherwise inspect the guard implementations to learn that any ordinary non-empty count is equivalent for this purpose.

Consequence: Future cases can preserve or change the repeated count inconsistently, and reviewers must reverse engineer whether slice length participates in the boundary being tested.

Recommended action: Use the simplest non-empty witness consistently, such as one element, and document once near the shared adapter matrix that slice length is intentionally minimal because only element alignment is under test. Introduce a named constant only if the length later gains semantics beyond being non-empty.

Impacted locations:

  • crates/multitude/src/bytemuck.rs:481-541
  • crates/multitude/src/zerocopy.rs:470-530

Comment on lines +30 to +31
/// Smart-pointer alignment cap under [`TEST_CHUNK_ALIGN`]. Half the chunk
/// cap, as in production.

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.

[Copilot speaking]

Condense the new alignment-related documentation summaries

Several new item documentation blocks combine their summary and detailed rationale in one opening paragraph, so their rendered first paragraphs exceed the repository's 80-character summary limit. This affects TEST_SMART_PTR_ALIGN, SmartPtrOverAlignedDrop, ChunkOverAligned, the test-only align_cap field, and set_align_cap.

Reproducible reasoning: Rustdoc joins adjacent documentation-comment lines into one paragraph; wrapping the source therefore does not create a short rendered summary. Each affected item can state its role in one sentence, while details about production geometry, test-only thresholds, and needs_drop branching belong in a later paragraph. The change improves generated-documentation scanning without removing the rationale.

Consequence: Readers scanning item documentation receive wrapped implementation detail before a concise statement of each item's role.

Recommended action: Give each affected item a rendered opening sentence of at most 80 characters, add a blank documentation line, and move the remaining explanation into later paragraphs. Keep the substantive distinction between production geometry and synthetic test thresholds while shortening the summaries.

Impacted locations:

  • crates/multitude/src/tests_support.rs:30-31
  • crates/multitude/src/tests_support.rs:52-53
  • crates/multitude/src/tests_support.rs:63-64
  • crates/multitude/src/arena/mod.rs:158-160
  • crates/multitude/src/arena/mod.rs:583-585

#[test]
#[should_panic(expected = "the cap may only be lowered")]
fn set_align_cap_rejects_raising_the_cap() {
Arena::new().set_align_cap(CHUNK_ALIGN * 2);

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.

[Copilot speaking]

Explain why the raising-cap test preserves power-of-two alignment

The new set_align_cap_rejects_raising_the_cap test passes CHUNK_ALIGN * 2, but it does not document that this particular relationship is required to reach the intended upper-bound assertion. set_align_cap first rejects values that are not powers of two, so an apparently equivalent above-cap value can exercise a different validation branch.

Reproducible reasoning: The test expects the panic text from the cap <= CHUNK_ALIGN assertion. The preceding assertion requires cap.is_power_of_two(). Multiplying the power-of-two production cap by another power of two preserves that prerequisite while exceeding the production cap. This dependency is a non-obvious reason for the chosen literal multiplier and should be recorded where the value is selected.

Consequence: A maintainer can replace the expression with a simpler-looking above-cap value such as an incremented cap and unintentionally turn this into a test of the power-of-two check, weakening coverage of the lower-only constraint.

Recommended action: Add an inline comment explaining that the test input must remain a power of two while exceeding the production cap so execution reaches the lower-only assertion. Describe the relationship without restating the literal value.

Impacted locations:

  • crates/multitude/src/arena/align_guard_tests.rs:34

Comment on lines +3781 to 3787
//
// This is the last type in the crate aligned above 8192, which some
// codegen backends refuse to compile (see the arena's alignment-guard
// unit tests for why the others were removed). It compiles only because
// `try_reserve` never materializes the layout. If this file ever fails to
// build with an alignment error, this declaration is the cause.
#[test]

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.

[Copilot speaking]

Explain the remaining high-alignment reserve test accurately and locally

The new comment above non_freezable_overaligned_vec_grows_via_oversized_path describes removed declarations, calls this the crate's last type above a backend threshold, gives an absolute diagnosis for any future alignment build failure, and says try_reserve never materializes the layout. The durable local fact is narrower: the high alignment makes Over non-freezable and selects the oversized reserve path; the code computes its size and alignment and reserves aligned raw storage, but it never constructs an Over value or an over-aligned stack temporary.

Reproducible reasoning: Vec::try_grow_to uses size_of::<T>(), align_of::<T>(), and the non-freezable reservation path, so layout information is part of the operation. The test avoids push and leaves the vector empty, which means no typed Over value is created on the stack or written into the reserved storage. That distinction explains the intended portability behavior without maintaining a repository-wide census, narrating removed tests, or claiming every possible future alignment diagnostic has one cause.

Consequence: The current comment can become stale when other fixtures are added or moved and can send a maintainer toward allocation-layout construction even though the test deliberately preserves it and avoids only typed value materialization.

Recommended action: Replace the historical census and absolute troubleshooting claim with a present-state comment: the alignment intentionally makes the element non-freezable and drives oversized reservation; the path still computes and reserves aligned storage but does not construct an Over value or over-aligned stack temporary. Keep any backend caveat scoped to that typed-materialization constraint.

References:

  • crates/multitude/src/vec/mod.rs (Vec::try_grow_to)

Impacted locations:

  • crates/multitude/tests/arena.rs:3777-3801
  • crates/multitude/src/vec/mod.rs:129-183

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.

[Copilot speaking]

Remove the now-empty Helpers section marker

The change removes the only helper type declarations beneath the three-line Helpers separator in coverage_arena_gaps, leaving the separator immediately followed by ordinary tests. The marker therefore no longer describes any content in the module.

Reproducible reasoning: Before this change, HalfChunkAlign and ChunkAlign appeared directly beneath the marker. The pull request deletes both declarations but retains the visual chapter heading. Reading the resulting module shows that the first item after the marker is try_alloc_simple_ref_returns_mutable_reference, so there is no helper section to identify. This also conflicts with the code-ordering guidance to avoid section-separator comments and instead make organization clear through code structure.

Consequence: Readers are directed to expect shared helper definitions and must scan onward to discover that the advertised section is empty, adding noise and making the module's organization misleading.

Recommended action: Delete the Helpers label and its surrounding separator lines. No replacement heading is needed because the remaining items are tests in the existing test module.

Impacted locations:

  • crates/multitude/tests/arena.rs:2236-2238

Comment on lines +564 to +569
/// Exclusive upper bound on the alignment a chunk can satisfy.
///
/// Production builds always report [`CHUNK_ALIGN`]. Tests lower it
/// with [`Self::set_align_cap`] so the rejection guards can be driven
/// by types whose alignment every codegen backend accepts — a type
/// aligned to the real cap does not compile everywhere.

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.

[Copilot speaking]

Define one authoritative alignment-policy model and use it consistently

The change uses the word "cap" for three different concepts whose relationships are not defined in one place: immutable production geometry (CHUNK_ALIGN and the first-tile masking invariant), test-only synthetic rejection thresholds lowered by set_align_cap, and the API-specific limits selected by each allocation family. The resulting documentation is internally inconsistent. Escape-capable Box, Rc, and Arc owners use the lower smart-pointer threshold; arena-bound Alloc<[T]> slices use the full chunk threshold; scalar Alloc<T> currently uses the lower threshold even though the design says arena-lifetime allocations are not subject to the smart-pointer cap; and all of these failures share AllocError::is_alignment_too_large. Local comments also describe synthetic thresholds as physical geometry and call Alloc<[T]> results plain references.

Reproducible reasoning: Production chunks remain aligned to CHUNK_ALIGN, and smart-pointer destruction recovers a chunk header by masking the payload address. The new cfg(test) field does not change that geometry: it only changes the values read by rejects_smart_ptr_align and rejects_chunk_align. Call-site inspection shows that smart owners and scalar Alloc<T> call the lower-threshold predicate, while arena-bound slice helpers call the full-chunk predicate; buffer_freezable remains a constant expression over the production threshold. The public error classifier therefore represents whichever limit applies to the selected API rather than one universal numeric boundary. Because no authoritative model names these layers and maps owner families to their policy, the helper rustdoc, test overview, public error docs, and design document each generalize from a different subset of the behavior. The scalar Alloc<T> call site additionally exposes a real unresolved contract question: its lower threshold is established behavior, but the nearby masking explanation applies to escape-capable owners and the design explicitly describes arena-lifetime allocations differently.

Consequence: Maintainers can use a synthetic test threshold in geometry-dependent code, apply the wrong guard to a new allocation entry point, or change the scalar Alloc<T> limit based on contradictory documentation. Callers can also infer a universal half-chunk limit from the shared error classifier even when an arena-bound slice API accepts alignments up to the full chunk threshold.

Recommended action: Create an affected-scope implementation section that defines production geometry, synthetic test thresholds, and API alignment policy as separate concepts. Include a table mapping scalar Alloc<T>, arena-bound Alloc<[T]>, and escape-capable owners to their selected threshold and rationale; explicitly decide and document the scalar Alloc<T> policy without presuming whether code or documentation should change. Record why the portable cases are crate unit tests and why buffer_freezable remains production-constant-based. Link this model from the existing design sections, then align the public error documentation and local comments with it: describe the shared classifier generically, use the established Alloc terminology, scope masking and chunk-capacity claims to production geometry, and describe the test override in present-state terms.

References:

  • crates/multitude/docs/DESIGN.md, "Thin smart pointers: the alignment/masking trick", "Fragile invariants", and "Testing and verification"
  • crates/multitude/docs/DESIGN.md, "Thin smart pointers: the alignment/masking trick" and "Sharp edges for users"
  • crates/multitude/docs/DESIGN.md, "Thin smart pointers: the alignment/masking trick" and "The four allocation styles"
  • crates/multitude/docs/DESIGN.md, "The four allocation styles"
  • crates/multitude/docs/DESIGN.md, "The four allocation styles", "Thin smart pointers: the alignment/masking trick", and "Sharp edges for users"
  • crates/multitude/docs/DESIGN.md, "Thin smart pointers: the alignment/masking trick"

Impacted locations:

  • crates/multitude/docs/DESIGN.md
  • crates/multitude/src/tests_support.rs:21-42
  • crates/multitude/src/arena/align_guard_tests.rs:4-28
  • crates/multitude/src/arena/mod.rs:554-626
  • crates/multitude/tests/arena.rs:3777-3801
  • crates/multitude/src/error.rs:93-102
  • crates/multitude/src/bytemuck.rs:91-129
  • crates/multitude/src/zerocopy.rs:88-126
  • crates/multitude/src/arena/align_guard_tests.rs:4-15
  • crates/multitude/src/arena/align_guard_tests.rs:21-23
  • crates/multitude/src/arena/align_guard_tests.rs:134-156
  • crates/multitude/src/arena/alloc_value.rs:794-800
  • crates/multitude/src/arena/mod.rs:601-616
  • crates/multitude/src/arena/alloc_value.rs:916-919
  • crates/multitude/src/arena/mod.rs:563-626

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.

[Copilot speaking]

Separate guard semantics from entry-point routing in the portable tests

The portable alignment tests are maintained as flat, hand-written API examples, although the change introduces two distinct verification responsibilities: proving the centralized predicates' comparison semantics and proving that every independently implemented allocation route calls the correct predicate before any fast return. The current suite tests only equality at both exclusive thresholds, omits distinct Box-copy, arena-bound clone, and DST guard implementations, no longer preserves typed-empty rejection coverage for several slice paths, and duplicates the bytemuck and zerocopy adapter matrix closely enough that the zerocopy scalar cases already use the wrong boundary fixture.

Reproducible reasoning: rejects_smart_ptr_align and rejects_chunk_align implement align >= threshold; all existing portable rejection fixtures are exactly equal to the injected thresholds, so changing either predicate to equality would preserve the suite. Call-site inspection then reveals independently breakable routes: Box copy calls check_slice_box_layout separately from Box fill, arena-bound clone calls reject_over_aligned separately from copy and fill, Box DST has its own runtime-layout guard, and Arc/Rc DST share another guard. These sites are not reached with the lowered cap. The slice guards execute before their zero-length fast returns, but relocated copy tests use non-empty inputs and the clone acceptance test uses a fixture below that call site's threshold, so those tests cannot detect a disconnected guard or reordered empty return. Finally, bytemuck and zerocopy scalar adapters both forward to Arena::try_alloc_with, yet only the bytemuck copy uses the lower-threshold fixture; the duplicated matrices therefore fail to encode their shared routing contract structurally. These are consequences of the same test organization rather than independent API defects.

Consequence: A predicate can regress from >= to ==, a distinct allocation route can stop consulting the injected threshold, an empty slice can bypass validation, or one ecosystem adapter can drift to the wrong boundary while all portable tests that currently exercise the affected layer remain green.

Recommended action: Organize the portable coverage in two layers. First, add focused truth-table tests for both centralized predicates at equality and strictly above the injected threshold, without duplicating the full API matrix. Second, define an explicit routing matrix that maps every distinct guard implementation to its expected threshold fixture and input shape. Include typed-empty cases where validation must precede a fast return, cover Box copy and arena-bound clone separately, exercise both DST guard implementations with matching sized layouts under the dst feature, and generate the equivalent bytemuck and zerocopy cases from one shared mapping after correcting the zerocopy scalar fixture to SmartPtrOverAligned. This finding concerns semantic coverage when the relevant modules are compiled; feature-selection coverage remains a separate concern.

References:

  • crates/multitude/src/arena/alloc_value.rs
  • crates/multitude/docs/DESIGN.md, "Thin smart pointers: the alignment/masking trick" and "Safety invariants"
  • crates/multitude/src/arena/align_guard_tests.rs
  • crates/multitude/tests/dst.rs

Impacted locations:

  • crates/multitude/src/bytemuck.rs:451-543
  • crates/multitude/src/zerocopy.rs:451-532
  • crates/multitude/src/arena/align_guard_tests.rs:13-15
  • crates/multitude/src/arena/align_guard_tests.rs:54-156
  • crates/multitude/src/arena/mod.rs:609-626
  • crates/multitude/src/arena/align_guard_tests.rs
  • crates/multitude/src/arena/alloc_slice_box.rs:231-232
  • crates/multitude/src/arena/alloc_slice_ref.rs:374-375
  • crates/multitude/src/arena/alloc_unsized.rs:390-392
  • crates/multitude/src/arena/alloc_unsized.rs:479-481


#[cfg(feature = "zerocopy")]
// SAFETY: a `u8` plus alignment padding; every byte pattern is valid.
unsafe impl zerocopy::TryFromBytes for $ty {

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.

[Copilot speaking]

Derive the bytemuck and zerocopy marker traits instead of implementing their internals

The new shared alignment fixtures manually implement bytemuck::Zeroable, zerocopy::TryFromBytes, and zerocopy::FromZeros. The zerocopy implementations define TryFromBytes::is_bit_valid and call hidden only_derive_is_allowed_to_implement_this_trait methods that its public documentation explicitly reserves for derive-generated code, even though this crate enables both dependencies' derive features and declares the fixtures in this module.

Reproducible reasoning: crates/multitude/Cargo.toml enables bytemuck and zerocopy with their derive features, and Cargo.lock selects zerocopy 0.8.52. SmartPtrOverAligned and ChunkOverAligned are repr(C) wrappers containing only a u8, so the supported derives can prove their marker contracts directly. Zerocopy's documented contract directs implementers to derive TryFromBytes, permits callers to trust a successful validity result, and warns that hidden internals can change in otherwise compatible releases in ways that affect soundness. FromZeros extends TryFromBytes, and deriving FromZeros emits both implementations after checking the fields. Although the handwritten true validity result is reasonable for these fixtures, the macro unnecessarily assumes ownership of unsafe contracts and bypasses the dependencies' supported compile-time analysis.

Consequence: A routine zerocopy dependency update can change hidden trait machinery or validity requirements while these handwritten implementations either stop compiling or continue with an outdated proof of bit validity, potentially making the test-only code unsound and blocking dependency updates.

Recommended action: Apply conditional derives to both fixture structs, using cfg_attr(feature = "bytemuck", derive(bytemuck::Zeroable)) and cfg_attr(feature = "zerocopy", derive(zerocopy::FromZeros)), then remove impl_zero_valid and its handwritten marker implementations. This uses the dependencies' supported compile-time-checked path; deriving zerocopy's FromZeros also generates the required TryFromBytes implementation.

References:

Impacted locations:

  • crates/multitude/src/tests_support.rs:77-105
  • crates/multitude/src/tests_support.rs:87-100

@sandersaares

Copy link
Copy Markdown
Member

Thanks for doing this. I was worried about our previous attempts to solve the problem, good to see a new attempt. Third time is the charm!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new test modules include unqualified align_of usage and a misleading set_align_cap panic message/expectation that should be corrected for clarity and consistency.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

Previously missed (2) — in code that hasn't changed since the last review.

crates/multitude/src/arena/align_guard_tests.rs:32

  • This #[should_panic(expected = ...)] string should match Arena::set_align_cap’s panic message; if that message is updated to describe the cap <= CHUNK_ALIGN constraint, update this expected substring as well.
    crates/multitude/src/arena/mod.rs:597
  • The assertion currently checks cap <= CHUNK_ALIGN, but the panic message says the cap “may only be lowered” and explains a different failure mode. Updating the message to describe the actual constraint (cap must not exceed CHUNK_ALIGN) makes failures easier to diagnose.

crates/multitude/src/tests_support.rs:74

  • These assertions call align_of::<...>() without importing it in this module. Qualify the calls (or add an import within the changed region) so the const check doesn’t depend on an unshown use.
    assert!(align_of::<SmartPtrOverAligned>() == TEST_SMART_PTR_ALIGN);
    assert!(align_of::<SmartPtrOverAlignedDrop>() == TEST_SMART_PTR_ALIGN);
    assert!(align_of::<ChunkOverAligned>() == TEST_CHUNK_ALIGN);
  • Files reviewed: 18/18 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +17 to +19
use crate::Arena;
use crate::internal::constants::{CHUNK_ALIGN, max_smart_ptr_align};
use crate::tests_support::{ChunkOverAligned, SmartPtrOverAligned, SmartPtrOverAlignedDrop, TEST_CHUNK_ALIGN, capped_arena};
ADOMAS BEKERAS (from Dev Box) and others added 2 commits September 2, 2026 11:23
`cargo test` failed on a clean checkout. The arena rejects allocations
aligned at or above a cap: `CHUNK_ALIGN` is 64 KiB and the smart-pointer
cap is half of it, 32 KiB. Testing those guards meant declaring types with
`#[repr(align(32768))]` and larger, which some codegen backends refuse to
compile at all — the library built fine, but the `arena`, `audit_repro` and
`pin_support` test binaries and one doctest failed codegen.

The previous workaround gated those tests behind a cfg that nothing in-tree
sets, so the default build was the broken one, and it dropped the coverage
wholesale on any backend that set it.

Lower the cap to reach a legal alignment instead of raising a type's
alignment to reach the cap. `Arena` gains a `cfg(test)` alignment cap that
the guards read; tests set it to 8192 and drive both boundaries with 4096-
and 8192-aligned types, which every backend accepts. The affected tests move
in-crate as unit tests so they can reach it.

Also collapses three duplicate `MAX_SMART_PTR_ALIGN` definitions into one
accessor and removes the cfg entirely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The zerocopy scalar tests used the chunk-cap fixture, but `try_alloc` and
`alloc` route through the smart-pointer guard. They could not detect that
guard loosening from 4 KiB to 8 KiB. Use the smart-pointer fixture, as the
bytemuck tests already did.

Every other fixture sits exactly at its threshold, so narrowing either
predicate from `>=` to `==` left the suite green. Add tests that lower the
cap further and drive the same fixtures from strictly above it, plus the
accepting counterpart below.

`set_align_cap` accepted 1, which makes the derived smart-pointer cap 0 and
rejects every alignment; assert a lower bound.

Also documents both alignment boundaries on `is_alignment_too_large`
instead of only the smart-pointer one, and drops a section marker left
empty by the test migration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

There are a couple of concrete correctness issues in the new/updated test modules (unqualified align_of usage and one misleading helper-type doc comment) that should be addressed before merging.

Review details

Suppressed comments (3)

crates/multitude/src/tests_support.rs:75

  • The const assertions use align_of without importing it; qualify these calls with core::mem::align_of (or import align_of) so the assertions are unambiguous.
const _: () = {
    assert!(align_of::<SmartPtrOverAligned>() == TEST_SMART_PTR_ALIGN);
    assert!(align_of::<SmartPtrOverAlignedDrop>() == TEST_SMART_PTR_ALIGN);
    assert!(align_of::<ChunkOverAligned>() == TEST_CHUNK_ALIGN);
};

crates/multitude/src/tests_support.rs:47

  • The doc comment says this type is "accepted by the simple-reference paths", but the scalar &mut T entry points (e.g. try_alloc::<T>()) are described elsewhere as using the smart-pointer cap. Consider clarifying that the "accepted" behavior refers specifically to the simple-reference slice entry points, which use the chunk cap.
/// Aligned exactly at the smart-pointer cap: rejected by every
/// smart-pointer entry point, accepted by the simple-reference paths.

crates/multitude/src/arena/align_guard_tests.rs:19

  • This module uses align_of::<T>() in multiple tests, but doesn’t import or qualify align_of. Add use core::mem::align_of; (or fully qualify each call) to keep the tests self-contained.
use crate::Arena;
use crate::internal::constants::{CHUNK_ALIGN, max_smart_ptr_align};
use crate::tests_support::{ChunkOverAligned, SmartPtrOverAligned, SmartPtrOverAlignedDrop, TEST_CHUNK_ALIGN, capped_arena};
  • Files reviewed: 18/18 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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.

4 participants