use super::JumpTable;
use crate::opcode;
use bitvec::{bitvec, order::Lsb0, vec::BitVec};
use primitives::Bytes;
use std::vec::Vec;
pub(crate) fn analyze_legacy(bytecode: Bytes) -> (JumpTable, Bytes) {
let mut jumps: BitVec<u8> = bitvec![u8, Lsb0; 0; bytecode.len()];
let range = bytecode.as_ptr_range();
let start = range.start;
let mut iterator = start;
let end = range.end;
let mut prev_byte: u8 = 0;
let mut last_byte: u8 = 0;
while iterator < end {
prev_byte = last_byte;
last_byte = unsafe { *iterator };
if last_byte == opcode::JUMPDEST {
unsafe { jumps.set_unchecked(iterator.offset_from_unsigned(start), true) }
iterator = unsafe { iterator.add(1) };
} else {
let push_offset = last_byte.wrapping_sub(opcode::PUSH1);
if push_offset < 32 {
iterator = unsafe { iterator.add(push_offset as usize + 2) };
} else {
iterator = unsafe { iterator.add(1) };
}
}
}
let push_overflow = (iterator as usize) - (end as usize);
let mut padding = push_overflow;
if last_byte == opcode::STOP {
padding += is_dupn_swapn_exchange(prev_byte) as usize;
} else {
padding += 1 + is_dupn_swapn_exchange(last_byte) as usize;
}
let bytecode = if padding > 0 {
let mut padded = Vec::with_capacity(bytecode.len() + padding);
padded.extend_from_slice(&bytecode);
padded.resize(padded.len() + padding, 0);
Bytes::from(padded)
} else {
bytecode
};
(JumpTable::new(jumps), bytecode)
}
const fn is_dupn_swapn_exchange(opcode: u8) -> bool {
opcode.wrapping_sub(opcode::DUPN) < 3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bytecode_ends_with_stop_no_padding_needed() {
let bytecode = vec![
opcode::PUSH1,
0x01,
opcode::PUSH1,
0x02,
opcode::ADD,
opcode::STOP,
];
let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len());
}
#[test]
fn test_bytecode_ends_without_stop_requires_padding() {
let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH1, 0x02, opcode::ADD];
let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len() + 1);
}
#[test]
fn test_bytecode_ends_with_push16_requires_17_bytes_padding() {
let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH16];
let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len() + 17);
}
#[test]
fn test_bytecode_ends_with_push2_requires_2_bytes_padding() {
let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH2, 0x02];
let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len() + 2);
}
#[test]
fn test_bytecode_with_jumpdest_at_start() {
let bytecode = vec![opcode::JUMPDEST, opcode::PUSH1, 0x01, opcode::STOP];
let (jump_table, _) = analyze_legacy(bytecode.into());
assert!(jump_table.is_valid(0)); }
#[test]
fn test_bytecode_with_jumpdest_after_push() {
let bytecode = vec![opcode::PUSH1, 0x01, opcode::JUMPDEST, opcode::STOP];
let (jump_table, _) = analyze_legacy(bytecode.into());
assert!(jump_table.is_valid(2)); }
#[test]
fn test_bytecode_with_multiple_jumpdests() {
let bytecode = vec![
opcode::JUMPDEST,
opcode::PUSH1,
0x01,
opcode::JUMPDEST,
opcode::STOP,
];
let (jump_table, _) = analyze_legacy(bytecode.into());
assert!(jump_table.is_valid(0)); assert!(jump_table.is_valid(3)); }
#[test]
fn test_bytecode_with_max_push32() {
let bytecode = vec![opcode::PUSH32];
let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len() + 33); }
#[test]
fn test_bytecode_with_invalid_opcode() {
let bytecode = vec![0xFF, opcode::STOP]; let (jump_table, _) = analyze_legacy(bytecode.into());
assert!(!jump_table.is_valid(0)); }
#[test]
fn test_bytecode_with_sequential_pushes() {
let bytecode = vec![
opcode::PUSH1,
0x01,
opcode::PUSH2,
0x02,
0x03,
opcode::PUSH4,
0x04,
0x05,
0x06,
0x07,
opcode::STOP,
];
let (jump_table, padded_bytecode) = analyze_legacy(bytecode.clone().into());
assert_eq!(padded_bytecode.len(), bytecode.len());
assert!(!jump_table.is_valid(0)); assert!(!jump_table.is_valid(2)); assert!(!jump_table.is_valid(5)); }
#[test]
fn test_bytecode_with_jumpdest_in_push_data() {
let bytecode = vec![
opcode::PUSH2,
opcode::JUMPDEST, 0x02,
opcode::STOP,
];
let (jump_table, _) = analyze_legacy(bytecode.into());
assert!(!jump_table.is_valid(1)); }
#[test]
fn test_bytecode_ends_with_immediate_opcode_and_stop_requires_padding() {
for op in [opcode::SWAPN, opcode::DUPN, opcode::EXCHANGE] {
for bytecode in [vec![op], vec![op, opcode::STOP]] {
let (_, padded_bytecode) = analyze_legacy(bytecode.into());
assert_eq!(padded_bytecode.len(), 3);
assert_eq!(padded_bytecode[0], op);
assert_eq!(padded_bytecode[1], opcode::STOP);
assert_eq!(padded_bytecode[2], opcode::STOP);
}
}
}
}