From a7f3458c273b7285e88a64990c5172f9761b20e8 Mon Sep 17 00:00:00 2001 From: Martin Habovstiak Date: Thu, 22 Dec 2022 23:35:03 +0100 Subject: [PATCH] Fix bug in `ScriptBuf::extend` for short iterators `ScriptBuf::extend` contained an optimization for short scripts that was supposed to preallocate the buffer and then fill it. By mistake it attempted to fill it from already-exhausted iterator instead of the temporary array it drained the items into. This obviously produced garbage (empty) values. This was not caught by tests because the optimization is only active for scripts with known maximum length and the test used `Instructions` which doesn't know the maximum lenght. --- bitcoin/src/blockdata/script.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/blockdata/script.rs b/bitcoin/src/blockdata/script.rs index dc3a0f91..68efcb18 100644 --- a/bitcoin/src/blockdata/script.rs +++ b/bitcoin/src/blockdata/script.rs @@ -1433,10 +1433,11 @@ impl<'a> core::iter::FromIterator> for ScriptBuf { impl<'a> Extend> for ScriptBuf { fn extend(&mut self, iter: T) where T: IntoIterator> { - let mut iter = iter.into_iter(); + let iter = iter.into_iter(); // Most of Bitcoin scripts have only a few opcodes, so we can avoid reallocations in many // cases. if iter.size_hint().1.map(|max| max < 6).unwrap_or(false) { + let mut iter = iter.fuse(); // `MaybeUninit` might be faster but we don't want to introduce more `unsafe` than // required. let mut head = [None; 5]; @@ -1445,8 +1446,10 @@ impl<'a> Extend> for ScriptBuf { total_size += instr.script_serialized_len(); *head = Some(instr); } + // Incorrect impl of `size_hint` breaks `Iterator` contract so we're free to panic. + assert!(iter.next().is_none(), "Buggy implementation of `Iterator` on {} returns invalid upper bound", core::any::type_name::()); self.reserve(total_size); - for instr in iter { + for instr in head.iter().cloned().flatten() { self.push_instruction_no_opt(instr); } } else {