Skip to content

Commit 3a3c18a

Browse files
authored
fix(amt): saturate the index at MAX_INDEX+1 (#2083)
When we hit the end of the AMT we'll update the "key" to where the next tree should start. Unfortunately, if we've filled in the high-end of the AMT, this key could overflow the max u64 by exactly one (i.e., it'll wrap to 0). So, instead, we saturate to the MAX_INDEX (which is `u64-1`).
1 parent 31ffd59 commit 3a3c18a

File tree

1 file changed

+38
-3
lines changed

1 file changed

+38
-3
lines changed

ipld/amt/src/iter.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ where
209209
let stack = self.stack.last_mut()?;
210210
match stack.node {
211211
Node::Leaf { vals } => {
212-
while stack.idx < vals.len() {
212+
while stack.idx < vals.len() && self.key <= MAX_INDEX {
213213
match vals[stack.idx] {
214214
Some(ref v) => {
215215
stack.idx += 1;
@@ -256,10 +256,10 @@ where
256256
}
257257
Some(&None) => {
258258
stack.idx += 1;
259-
self.key += nodes_for_height(
259+
self.key = self.key.saturating_add(nodes_for_height(
260260
self.bit_width,
261261
self.height - self.stack.len() as u32 + 1,
262-
);
262+
));
263263
}
264264
None => {
265265
self.stack.pop();
@@ -281,6 +281,41 @@ mod tests {
281281
use fvm_ipld_encoding::BytesDe;
282282
use quickcheck_macros::quickcheck;
283283

284+
// Check for regressions around checked additions when iterating over high-keys.
285+
#[test]
286+
fn check_iter_high_keys() {
287+
let db = fvm_ipld_blockstore::MemoryBlockstore::default();
288+
// Test overflowing from last node
289+
for c in 0..3 {
290+
for i in (MAX_INDEX - 10)..=MAX_INDEX {
291+
let mut amt = Amt::new_with_bit_width(&db, 8);
292+
for j in (0..c).rev() {
293+
amt.set(i - j, "foo".to_owned()).unwrap();
294+
}
295+
let mut iter = amt.iter();
296+
for j in (0..c).rev() {
297+
let (k, v) = iter.next().unwrap().unwrap();
298+
assert_eq!(k, i - j);
299+
assert_eq!(v, "foo");
300+
}
301+
302+
assert!(iter.next().is_none());
303+
}
304+
}
305+
306+
// Test overflowing from second-to-last node
307+
{
308+
let mut amt = Amt::new_with_bit_width(&db, 8);
309+
let idx = 7 * (u64::MAX / 8);
310+
amt.set(idx, "foo".to_owned()).unwrap();
311+
let mut iter = amt.iter();
312+
let (k, v) = iter.next().unwrap().unwrap();
313+
assert_eq!(k, idx);
314+
assert_eq!(v, "foo");
315+
assert!(iter.next().is_none());
316+
}
317+
}
318+
284319
#[test]
285320
fn check_iter_empty() {
286321
let db = fvm_ipld_blockstore::MemoryBlockstore::default();

0 commit comments

Comments
 (0)