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.
This commit is contained in:
Martin Habovstiak 2022-12-22 23:35:03 +01:00
parent 7fee6239e1
commit a7f3458c27
1 changed files with 5 additions and 2 deletions

View File

@ -1433,10 +1433,11 @@ impl<'a> core::iter::FromIterator<Instruction<'a>> for ScriptBuf {
impl<'a> Extend<Instruction<'a>> for ScriptBuf { impl<'a> Extend<Instruction<'a>> for ScriptBuf {
fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item = Instruction<'a>> { fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item = Instruction<'a>> {
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 // Most of Bitcoin scripts have only a few opcodes, so we can avoid reallocations in many
// cases. // cases.
if iter.size_hint().1.map(|max| max < 6).unwrap_or(false) { 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 // `MaybeUninit` might be faster but we don't want to introduce more `unsafe` than
// required. // required.
let mut head = [None; 5]; let mut head = [None; 5];
@ -1445,8 +1446,10 @@ impl<'a> Extend<Instruction<'a>> for ScriptBuf {
total_size += instr.script_serialized_len(); total_size += instr.script_serialized_len();
*head = Some(instr); *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::<T::IntoIter>());
self.reserve(total_size); self.reserve(total_size);
for instr in iter { for instr in head.iter().cloned().flatten() {
self.push_instruction_no_opt(instr); self.push_instruction_no_opt(instr);
} }
} else { } else {