What changed, and why it matters
This commit only adds two new unit tests to verify that two decoder types allocate memory in correctly-sized batches. It does not change any production code, fix a bug, or alter behavior. There is no security-relevant change.
No action required; this is a test-only change with no security implications.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds test-only code in consensus_encoding/src/decode/decoders.rs. It introduces byte_vec_decoder_reserves_in_batches and vec_decoder_reserves_in_batches to exercise the reserve logic of ByteVecDecoder and VecDecoder. No implementation code is modified. The commit message explicitly states the purpose is to ‘kill mutants’ (improve mutation testing coverage).
Changed components
consensus_encoding/src/decode/decoders.rs (tests only)Inspect captured patch +95 / −0
diff --git a/consensus_encoding/src/decode/decoders.rs b/consensus_encoding/src/decode/decoders.rs
index 6eca939b..0e9d0a07 100644
--- a/consensus_encoding/src/decode/decoders.rs
+++ b/consensus_encoding/src/decode/decoders.rs
@@ -1174,6 +1174,52 @@ mod tests {
decode_byte_vec_multi_byte_length_prefix, [0xff; 256], two_fifty_six_bytes_encoded();
}
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn byte_vec_decoder_reserves_in_batches() {
+ // A small number of extra bytes so we extend exactly by the remainder
+ // instead of another full batch.
+ let tail_length: usize = 11;
+
+ let total_len = MAX_VECTOR_ALLOCATE + tail_length;
+ let total_len_le = u32::try_from(total_len).expect("total_len fits u32").to_le_bytes();
+ let mut decoder = ByteVecDecoder::new();
+
+ let mut prefix = vec![0xFE]; // total_len_le is a compact size of four bytes.
+ prefix.extend_from_slice(&total_len_le);
+ prefix.push(0xAA);
+ let mut prefix_slice = prefix.as_slice();
+ decoder.push_bytes(&mut prefix_slice).expect("length plus first element");
+ assert!(prefix_slice.is_empty());
+
+ assert_eq!(decoder.buffer.capacity(), MAX_VECTOR_ALLOCATE);
+ assert_eq!(decoder.buffer.len(), 1);
+ assert_eq!(decoder.buffer[0], 0xAA);
+
+ let fill = vec![0xBB; MAX_VECTOR_ALLOCATE - 1];
+ let mut fill_slice = fill.as_slice();
+ decoder.push_bytes(&mut fill_slice).expect("fills to batch boundary, full capacity");
+ assert!(fill_slice.is_empty());
+
+ assert_eq!(decoder.buffer.capacity(), MAX_VECTOR_ALLOCATE);
+ assert_eq!(decoder.buffer.len(), MAX_VECTOR_ALLOCATE);
+ assert_eq!(decoder.buffer[MAX_VECTOR_ALLOCATE - 1], 0xBB);
+
+ let mut tail = vec![0xCC];
+ tail.extend([0xDD].repeat(tail_length - 1));
+ let mut tail_slice = tail.as_slice();
+ decoder.push_bytes(&mut tail_slice).expect("fills the remaining bytes");
+ assert!(tail_slice.is_empty());
+
+ assert_eq!(decoder.buffer.capacity(), MAX_VECTOR_ALLOCATE + tail_length);
+ assert_eq!(decoder.buffer.len(), total_len);
+ assert_eq!(decoder.buffer[MAX_VECTOR_ALLOCATE], 0xCC);
+
+ let result = decoder.end().unwrap();
+ assert_eq!(result.len(), total_len);
+ assert_eq!(result[total_len - 1 ], 0xDD);
+ }
+
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Inner(u32);
@@ -1283,6 +1329,55 @@ mod tests {
assert_eq!(got, want);
}
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn vec_decoder_reserves_in_batches() {
+ // A small number of extra elements so we extend exactly by the remainder
+ // instead of another full batch.
+ let tail_length: usize = 11;
+
+ let element_size = core::mem::size_of::<Inner>();
+ let batch_length = MAX_VECTOR_ALLOCATE / element_size;
+ assert!(batch_length > 1);
+ let total_len = batch_length + tail_length;
+ let total_len_le = u32::try_from(total_len).expect("total_len fits u32").to_le_bytes();
+ let mut decoder = Test::decoder();
+
+ let mut prefix = vec![0xFE]; // total_len_le is a compact size of four bytes.
+ prefix.extend_from_slice(&total_len_le);
+ prefix.extend_from_slice(&0xAA_u32.to_le_bytes());
+ let mut prefix_slice = prefix.as_slice();
+ decoder.push_bytes(&mut prefix_slice).expect("length plus first element");
+ assert!(prefix_slice.is_empty());
+
+ assert_eq!(decoder.0.buffer.capacity(), batch_length);
+ assert_eq!(decoder.0.buffer.len(), 1);
+ assert_eq!(decoder.0.buffer[0], Inner(0xAA));
+
+ let fill = 0xBB_u32.to_le_bytes().repeat(batch_length - 1);
+ let mut fill_slice = fill.as_slice();
+ decoder.push_bytes(&mut fill_slice).expect("fills to batch boundary, full capacity");
+ assert!(fill_slice.is_empty());
+
+ assert_eq!(decoder.0.buffer.capacity(), batch_length);
+ assert_eq!(decoder.0.buffer.len(), batch_length);
+ assert_eq!(decoder.0.buffer[batch_length - 1], Inner(0xBB));
+
+ let mut tail = 0xCC_u32.to_le_bytes().to_vec();
+ tail.extend(0xDD_u32.to_le_bytes().repeat(tail_length - 1));
+ let mut tail_slice = tail.as_slice();
+ decoder.push_bytes(&mut tail_slice).expect("fills the remaining bytes");
+ assert!(tail_slice.is_empty());
+
+ assert_eq!(decoder.0.buffer.capacity(), batch_length + tail_length);
+ assert_eq!(decoder.0.buffer.len(), total_len);
+ assert_eq!(decoder.0.buffer[batch_length], Inner(0xCC));
+
+ let Test(result) = decoder.end().unwrap();
+ assert_eq!(result.len(), total_len);
+ assert_eq!(result[total_len - 1 ], Inner(0xDD));
+ }
+
#[cfg(feature = "alloc")]
fn two_fifty_six_elements() -> Test {
Test(iter::repeat(Inner(0xDEAD_BEEF)).take(256).collect())
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.