Skip to content

debug_assert-guarded immediate ranges are compiled out in release — an out-of-range imm12 mis-encodes silently; 6 sites remain after #1071 fixed the one the census reached #1072

Description

@avrabe

The class

debug_assert! is compiled out in release. An encoder that guards an immediate's range with debug_assert! and then encodes it will, in a release build, shift overflow bits into neighbouring instruction fields — a silently wrong instruction, not a panic.

#1017's acceptance census hit exactly this on aarch64:

encoder.rs:410  debug_assert!(imm12 < 0x1000, "sub imm12 out of range")

A ≥512-slot local frame (≥4096 bytes) panicked in debug and would have mis-encoded SP in release. Fixed in #1071 with a loud selector decline on frame_size > 0xFFF, which covers the prologue sub and epilogue add together and transitively bounds the ldr/str slot offsets (an offset inside a ≤4095-byte frame is ≤4095).

It was latent only because it was unreachable: those modules declined at module level before ever reaching the encoder. Widening acceptance made it live. That is the general property worth recording — increasing reach converts latent encoder defects into reachable ones, and a backend that accepts 1.6% of real input has most of its encoder untested by real programs.

The residual, measured

git grep -n 'debug_assert' -- 'crates/synth-backend*/src/*encoder*.rs'

aarch64 — outside the #1071 frame guard:

encoder.rs:470  debug_assert!(imm12 < 0x1000, "cmp imm12 out of range")
encoder.rs:484  debug_assert!(shift <= 4,     "uxtw extend-shift out of range")

Neither is bounded by frame_size. Whether either is reachable with an out-of-range value is the question to answer — the honest answer may be "no, and here is the upstream invariant that guarantees it", which is a fine result if it is demonstrated rather than asserted.

ARM — four sites that assert an upstream invariant:

arm_encoder.rs:193   "selector enforces the CMP imm8 range"
arm_encoder.rs:194   "selector enforces the LDR imm12 range"
arm_encoder.rs:3932  (same CMP claim)
arm_encoder.rs:3933  (same LDR claim)

These are a different shape — they claim a caller guarantees the range. That claim may well be true. But "the selector enforces X" is precisely the form #946 found false for writes_sp, where a function claimed exhaustiveness while 175 of 222 variants fell to a wildcard. An unverified enforcement claim guarding a release-invisible check is worth confirming rather than trusting.

RISC-V has 0 debug_assert in its encoder, so it is not affected.

What would close this

For each of the 6 remaining sites, one of:

  1. A demonstrated upstream guard — show the value cannot exceed the range, ideally with a red-first test that trips the guard rather than the assert.
  2. Promote to a real checkErr, or a loud decline, so release behaves like debug. The encoder is already Ok-or-Err in the Thumb-2 path for exactly this reason (opt: repair + re-enable the optimized linear-memory path (root-cause the MemLoad ADD-register corruption) #180/arm encoder: panics (debug_assert) on PC/R15 operand instead of returning Err — fuzz-found, pre-existing #185: "verify bytes not IR", 16-bit reg-forms corrupt R8–R12).
  3. A stated reason it cannot be reached, recorded next to the assert.

The cheap mechanical version: a test that builds in release and drives each encoder with an out-of-range immediate, asserting an Err rather than a corrupted encoding. That fails today wherever the guard is only a debug_assert.

Why not fold this into #1071

#1071 fixed the instance its census reached, with a red-first test and a two-direction check. The remaining five sites were not reached by that corpus, so fixing them there would be unmeasured speculation. Filing separately so the class gets its own measurement rather than riding on an unrelated PR.

Refs #1017

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions