{"id":"4c442eb1c995916a","repo":"rust-lang/rust","slug":"range-should-be-nonempty","errorCode":null,"errorMessage":"range should be nonempty","messagePattern":"range should be nonempty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs","lineNumber":686,"sourceCode":"    /// `InitMask::range_as_init_chunks(...).collect::<Vec<_>>()`.\n    pub fn prepare_copy(&self, range: AllocRange) -> InitCopy {\n        // Since we are copying `size` bytes from `src` to `dest + i * size` (`for i in 0..repeat`),\n        // a naive initialization mask copying algorithm would repeatedly have to read the initialization mask from\n        // the source and write it to the destination. Even if we optimized the memory accesses,\n        // we'd be doing all of this `repeat` times.\n        // Therefore we precompute a compressed version of the initialization mask of the source value and\n        // then write it back `repeat` times without computing any more information from the source.\n\n        // A precomputed cache for ranges of initialized / uninitialized bits\n        // 0000010010001110 will become\n        // `[5, 1, 2, 1, 3, 3, 1]`,\n        // where each element toggles the state.\n\n        let mut ranges = smallvec::SmallVec::<[u64; 1]>::new();\n\n        let mut chunks = self.range_as_init_chunks(range).peekable();\n\n        let initial = chunks.peek().expect(\"range should be nonempty\").is_init();\n\n        // Here we rely on `range_as_init_chunks` to yield alternating init/uninit chunks.\n        for chunk in chunks {\n            let len = chunk.range().end.bytes() - chunk.range().start.bytes();\n            ranges.push(len);\n        }\n\n        InitCopy { ranges, initial }\n    }\n\n    /// Applies multiple instances of the run-length encoding to the initialization mask.\n    pub fn apply_copy(&mut self, defined: InitCopy, range: AllocRange, repeat: u64) {\n        // An optimization where we can just overwrite an entire range of initialization bits if\n        // they are going to be uniformly `1` or `0`. If this happens to be a full-range overwrite,\n        // we won't need materialized blocks either.\n        if defined.ranges.len() <= 1 {\n            let start = range.start;\n            let end = range.start + range.size * repeat; // `Size` operations","sourceCodeStart":668,"sourceCodeEnd":704,"githubUrl":"https://github.com/rust-lang/rust/blob/22057b88b091743bc0fd8d592a9264f0a6951403/compiler/rustc_middle/src/mir/interpret/allocation/init_mask.rs#L668-L704","documentation":"`.expect(\"range should be nonempty\")` in `InitMask::prepare_copy` fires when `range_as_init_chunks(range)` yields zero chunks, i.e. the supplied `AllocRange` covers no bytes. `prepare_copy` builds a run-length encoding of initialization bits to replay during repeated `mem_copy_repeatedly` operations; an empty source has no first chunk to peek at, so the initialization state is undefined. Callers are contractually required to pass a nonempty range.","triggerScenarios":"Calling `InitMask::prepare_copy(AllocRange { start, size: Size::ZERO })`, or any caller path that computes a copy range whose `end - start == 0` bytes (e.g. a zero-sized memcpy/repeat with `repeat > 0` but `size == 0`, or an off-by-one that produced an empty sub-range).","commonSituations":"Const-eval / Miri bugs handling ZST copies or zero-sized array initializers; MIR optimizations that fold a copy down to zero bytes but still route it through `prepare_copy`; custom allocators in interpreted code returning size-0 blocks; regressions after changes to `mem_copy_repeatedly` repeat/size handling.","solutions":["Guard the caller: skip `prepare_copy` (and `apply_copy`) when `range.size.bytes() == 0`, since copying zero bytes is a no-op.","Audit `mem_copy_repeatedly` and its callers to ensure `size > 0` is enforced before precomputing the init mask.","Reproduce under Miri with `MIRIFLAGS=-Zmiri-track-raw-pointers` to find the producing instruction, then fix the upstream size computation.","Add a debug_assert at the caller boundary documenting the nonempty precondition."],"exampleFix":"// before\nlet copy = mask.prepare_copy(AllocRange::from(start..end)); // end == start → panic\n\n// after\nif end > start {\n    let copy = mask.prepare_copy(AllocRange::from(start..end));\n    mask.apply_copy(copy, dest_range, repeat);\n}","handlingStrategy":"validation","validationCode":"// prepare_copy panics when the requested range is empty (no init chunks).\n// Reject zero-length / backwards ranges at the boundary.\nfn nonempty_range(range: AllocRange) -> Option<AllocRange> {\n    if range.start.bytes() < range.end.bytes() {\n        Some(range)\n    } else {\n        None\n    }\n}\n\nlet copy = match nonempty_range(range) {\n    Some(r) => mask.prepare_copy(r),\n    None => InitCopy { ranges: Default::default(), initial: true }, // nothing to copy\n};","typeGuard":null,"tryCatchPattern":"let copy = std::panic::catch_unwind(|| mask.prepare_copy(range));\nmatch copy {\n    Ok(c) => /* use c */,\n    Err(_) => /* treat as empty copy, log the offending range */,\n}","preventionTips":["Guard every allocation-range operation with a start < end check; the init-mask code assumes nonempty ranges throughout.","Track byte sizes as Size/Bytes newtypes and reject zero at construction rather than at use.","When copying between allocations, short-circuit on equal start/end before touching the mask.","In interpreters/translators that build ranges programmatically, assert non-emptiness once at the source rather than at each sink."],"tags":["rustc","mir","const-eval","interpreter","init-mask","assertion"],"analyzedSha":"22057b88b091743bc0fd8d592a9264f0a6951403","analyzedAt":"2026-08-03T08:09:25.915Z","schemaVersion":2}