Skip to content

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

Draft
Adomas Bekeras (AdomasBekeras) wants to merge 1 commit into
mainfrom
u/abekeras/multitude-align-cap
Draft

test(multitude): drive alignment guards through an injectable cap#704
Adomas Bekeras (AdomasBekeras) wants to merge 1 commit 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.

`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>
@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 (edae40d) to head (90a1913).

Additional details and impacted files
@@           Coverage Diff            @@
##             main     #704    +/-   ##
========================================
  Coverage   100.0%   100.0%            
========================================
  Files         560      560            
  Lines       60859    60982   +123     
========================================
+ Hits        60859    60982   +123     
Flag Coverage Δ
linux 89.8% <100.0%> (?)
linux-arm 88.3% <100.0%> (?)
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

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.361s] (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.341s] (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)

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.

1 participant