Skip to content

Commit 250b66f

Browse files
committed
fix(contract): FieldMask out-of-range positions ignored, not folded (Codex P2 #441)
from_positions/with/has used & 63, which ALIASED an out-of-range position onto a valid bit (pos 64 -> bit 0) — silently corrupting the presence contract for a >64-field class (the N3 'old masks misread' failure class). Now positions >= MAX_FIELDS(64) are ignored (from_positions/with no-op; has -> false), preserving the no-panic property without misrepresenting presence. Teeth-test: pos 64 does NOT alias to bit 0, in-range bit 0 unaffected. 497 contract lib green; clippy+fmt clean. https://claude.ai/code/session_01R9AWgFa65uPnLyS2my2d2R
1 parent 5aaefd5 commit 250b66f

1 file changed

Lines changed: 42 additions & 6 deletions

File tree

crates/lance-graph-contract/src/class_view.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,27 +75,40 @@ impl FieldMask {
7575
/// Maximum addressable field positions in one `u64` mask.
7676
pub const MAX_FIELDS: u32 = 64;
7777

78-
/// Build a mask from the populated field positions.
78+
/// Build a mask from the populated field positions. Positions `>= MAX_FIELDS`
79+
/// (64) are **ignored** — NOT folded onto a valid bit. Folding (`& 63`) would
80+
/// alias position 64 onto bit 0 and silently corrupt the presence contract for
81+
/// an oversized class shape (Codex P2 on #441); ignoring keeps the no-panic
82+
/// property without misrepresenting which fields are present.
7983
pub const fn from_positions(positions: &[u8]) -> Self {
8084
let mut bits = 0u64;
8185
let mut i = 0;
8286
while i < positions.len() {
83-
bits |= 1u64 << (positions[i] as u64 & 63);
87+
if (positions[i] as u32) < Self::MAX_FIELDS {
88+
bits |= 1u64 << positions[i];
89+
}
8490
i += 1;
8591
}
8692
Self(bits)
8793
}
8894

89-
/// Set field position `n` as populated.
95+
/// Set field position `n` as populated. `n >= MAX_FIELDS` (64) is a no-op
96+
/// (NOT folded — see [`from_positions`](FieldMask::from_positions)).
9097
#[inline]
9198
pub const fn with(self, n: u8) -> Self {
92-
Self(self.0 | (1u64 << (n as u64 & 63)))
99+
if (n as u32) < Self::MAX_FIELDS {
100+
Self(self.0 | (1u64 << n))
101+
} else {
102+
self
103+
}
93104
}
94105

95-
/// Is field position `n` populated? (presence — C2)
106+
/// Is field position `n` populated? (presence — C2). `n >= MAX_FIELDS` (64) is
107+
/// always `false` — an out-of-range field is never "present" (NOT folded onto
108+
/// a valid bit).
96109
#[inline]
97110
pub const fn has(self, n: u8) -> bool {
98-
self.0 & (1u64 << (n as u64 & 63)) != 0
111+
(n as u32) < Self::MAX_FIELDS && self.0 & (1u64 << n) != 0
99112
}
100113

101114
/// Number of populated fields.
@@ -260,6 +273,29 @@ mod tests {
260273
FieldMask::EMPTY.with(1).with(1),
261274
FieldMask::from_positions(&[1])
262275
);
276+
277+
// Out-of-range positions are IGNORED, never folded onto a valid bit
278+
// (Codex P2 #441): position 64 must NOT alias to bit 0.
279+
assert_eq!(
280+
FieldMask::from_positions(&[64]),
281+
FieldMask::EMPTY,
282+
"position 64 must be ignored, not aliased to bit 0"
283+
);
284+
assert!(
285+
!FieldMask::EMPTY.with(64).has(0),
286+
"with(64) must not set bit 0"
287+
);
288+
assert!(
289+
!FieldMask::from_positions(&[0]).has(64),
290+
"has(64) must be false, not bit-0 aliased"
291+
);
292+
// In-range bit 0 unaffected by the out-of-range guard.
293+
assert!(FieldMask::from_positions(&[0, 64]).has(0));
294+
assert_eq!(
295+
FieldMask::from_positions(&[0, 64]).count(),
296+
1,
297+
"only the in-range bit 0 is set"
298+
);
263299
}
264300

265301
#[test]

0 commit comments

Comments
 (0)