{"record":{"id":"5bb6aef7bc3c4fb3","repo":"astral-sh/ruff","slug":"index-is-bounded-by-inline-max-segments","errorCode":null,"errorMessage":"index is bounded by INLINE_MAX_SEGMENTS","messagePattern":"index is bounded by INLINE_MAX_SEGMENTS","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ty_python_core/src/member.rs","lineNumber":947,"sourceCode":"\nstruct SmallSegmentsInfoIterator {\n    segments: SmallSegments,\n    index: usize,\n    next_offset: TextSize,\n}\n\nimpl Iterator for SmallSegmentsInfoIterator {\n    type Item = SegmentInfo;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        let count = self.segments.len();\n        if self.index >= count {\n            return None;\n        }\n\n        // Extract the relative offset and kind for the current segment\n        let shift = INLINE_COUNT_BITS\n            + (u32::try_from(self.index).expect(\"index is bounded by INLINE_MAX_SEGMENTS\")\n                * INLINE_SEGMENT_BITS);\n        let segment_data = (self.segments.0 >> shift) & INLINE_SEGMENT_MASK;\n        let kind = (segment_data & INLINE_KIND_MASK) as u8;\n        let relative_offset = ((segment_data >> INLINE_KIND_BITS) & INLINE_PREV_LEN_MASK) as u32;\n\n        // Update the running absolute offset\n        self.next_offset += TextSize::new(relative_offset);\n\n        let kind = match kind {\n            0 => SegmentKind::Attribute,\n            1 => SegmentKind::IntSubscript,\n            2 => SegmentKind::StringSubscript,\n            3 => SegmentKind::BytesSubscript,\n            _ => panic!(\"Invalid SegmentKind bits\"),\n        };\n\n        self.index += 1;\n        Some(SegmentInfo::new(kind, self.next_offset))","sourceCodeStart":929,"sourceCodeEnd":965,"githubUrl":"https://github.com/astral-sh/ruff/blob/fca5c7cf2cd2f8338f18b9485f936b8e9f07bd0b/crates/ty_python_core/src/member.rs#L929-L965","documentation":"SmallSegmentsInfoIterator decodes segments back out of the packed u64. Its .expect(\"index is bounded by INLINE_MAX_SEGMENTS\") asserts the stored segment count never exceeds 7, which construction in try_from_slice guarantees; the loop also stops at the stored count.","triggerScenarios":"Iterating a SmallSegments whose packed count field is corrupted or was produced outside try_from_slice. Well-formed values cannot exceed 7 segments, so this fires only on internal bugs.","commonSituations":"Property/fuzz tests mutating the packed u64; refactors that change the count encoding without updating the iterator's decoding shifts.","solutions":["Treat any occurrence as a bug: minimize the repro and report it upstream","When altering the inline layout, update count encoding and iterator decoding together","Add a pack-then-iterate round-trip test for 1..=7 segments"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// round-trip invariant test: decode exactly as many segments as were packed\nfor n in 1..=INLINE_MAX_SEGMENTS { assert_eq!(SmallSegments::try_from_slice(&segs[..n]).unwrap().iter().count(), n); }","typeGuard":null,"tryCatchPattern":"let out = std::panic::catch_unwind(|| small.iter().collect::<Vec<_>>()); // isolate panics in fuzzing","preventionTips":["Never hand-construct the packed u64","Change count encoding and iterator shifts in the same commit","Add property tests that pack then iterate for all valid counts"],"tags":["internal","invariant","panic","bit-packing","member-access"],"backgroundTag":"internal-invariant-violation","analyzedSha":"fca5c7cf2cd2f8338f18b9485f936b8e9f07bd0b","analyzedAt":"2026-08-20T16:33:49.445Z","contentChangedAt":"2026-08-20T16:33:49.445Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}