revm_bytecode/legacy/
analysis.rs

1use super::JumpTable;
2use crate::opcode;
3use bitvec::{bitvec, order::Lsb0, vec::BitVec};
4use primitives::Bytes;
5use std::vec::Vec;
6
7/// Analyzes the bytecode for use in [`LegacyAnalyzedBytecode`](crate::LegacyAnalyzedBytecode).
8///
9/// See [`LegacyAnalyzedBytecode`](crate::LegacyAnalyzedBytecode) for more details.
10///
11/// Prefer using [`LegacyAnalyzedBytecode::analyze`](crate::LegacyAnalyzedBytecode::analyze) instead.
12pub fn analyze_legacy(bytecode: Bytes) -> (JumpTable, Bytes) {
13    if bytecode.is_empty() {
14        return (JumpTable::default(), Bytes::from_static(&[opcode::STOP]));
15    }
16
17    let mut jumps: BitVec<u8> = bitvec![u8, Lsb0; 0; bytecode.len()];
18    let range = bytecode.as_ptr_range();
19    let start = range.start;
20    let mut iterator = start;
21    let end = range.end;
22    let mut opcode = 0;
23
24    while iterator < end {
25        opcode = unsafe { *iterator };
26        if opcode == opcode::JUMPDEST {
27            // SAFETY: Jumps are max length of the code
28            unsafe { jumps.set_unchecked(iterator.offset_from(start) as usize, true) }
29            iterator = unsafe { iterator.add(1) };
30        } else {
31            let push_offset = opcode.wrapping_sub(opcode::PUSH1);
32            if push_offset < 32 {
33                // SAFETY: Iterator access range is checked in the while loop
34                iterator = unsafe { iterator.add(push_offset as usize + 2) };
35            } else {
36                // SAFETY: Iterator access range is checked in the while loop
37                iterator = unsafe { iterator.add(1) };
38            }
39        }
40    }
41
42    let padding = (iterator as usize) - (end as usize) + (opcode != opcode::STOP) as usize;
43    let bytecode = if padding > 0 {
44        let mut padded = Vec::with_capacity(bytecode.len() + padding);
45        padded.extend_from_slice(&bytecode);
46        padded.resize(padded.len() + padding, 0);
47        Bytes::from(padded)
48    } else {
49        bytecode
50    };
51
52    (JumpTable::new(jumps), bytecode)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn test_bytecode_ends_with_stop_no_padding_needed() {
61        let bytecode = vec![
62            opcode::PUSH1,
63            0x01,
64            opcode::PUSH1,
65            0x02,
66            opcode::ADD,
67            opcode::STOP,
68        ];
69        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
70        assert_eq!(padded_bytecode.len(), bytecode.len());
71    }
72
73    #[test]
74    fn test_bytecode_ends_without_stop_requires_padding() {
75        let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH1, 0x02, opcode::ADD];
76        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
77        assert_eq!(padded_bytecode.len(), bytecode.len() + 1);
78    }
79
80    #[test]
81    fn test_bytecode_ends_with_push16_requires_17_bytes_padding() {
82        let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH16];
83        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
84        assert_eq!(padded_bytecode.len(), bytecode.len() + 17);
85    }
86
87    #[test]
88    fn test_bytecode_ends_with_push2_requires_2_bytes_padding() {
89        let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH2, 0x02];
90        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
91        assert_eq!(padded_bytecode.len(), bytecode.len() + 2);
92    }
93
94    #[test]
95    fn test_empty_bytecode_requires_stop() {
96        let bytecode = vec![];
97        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
98        assert_eq!(padded_bytecode.len(), 1); // Just STOP
99    }
100
101    #[test]
102    fn test_bytecode_with_jumpdest_at_start() {
103        let bytecode = vec![opcode::JUMPDEST, opcode::PUSH1, 0x01, opcode::STOP];
104        let (jump_table, _) = analyze_legacy(bytecode.clone().into());
105        assert!(jump_table.is_valid(0)); // First byte should be a valid jumpdest
106    }
107
108    #[test]
109    fn test_bytecode_with_jumpdest_after_push() {
110        let bytecode = vec![opcode::PUSH1, 0x01, opcode::JUMPDEST, opcode::STOP];
111        let (jump_table, _) = analyze_legacy(bytecode.clone().into());
112        assert!(jump_table.is_valid(2)); // JUMPDEST should be at position 2
113    }
114
115    #[test]
116    fn test_bytecode_with_multiple_jumpdests() {
117        let bytecode = vec![
118            opcode::JUMPDEST,
119            opcode::PUSH1,
120            0x01,
121            opcode::JUMPDEST,
122            opcode::STOP,
123        ];
124        let (jump_table, _) = analyze_legacy(bytecode.clone().into());
125        assert!(jump_table.is_valid(0)); // First JUMPDEST
126        assert!(jump_table.is_valid(3)); // Second JUMPDEST
127    }
128
129    #[test]
130    fn test_bytecode_with_max_push32() {
131        let bytecode = vec![opcode::PUSH32];
132        let (_, padded_bytecode) = analyze_legacy(bytecode.clone().into());
133        assert_eq!(padded_bytecode.len(), bytecode.len() + 33); // PUSH32 + 32 bytes + STOP
134    }
135
136    #[test]
137    fn test_bytecode_with_invalid_opcode() {
138        let bytecode = vec![0xFF, opcode::STOP]; // 0xFF is an invalid opcode
139        let (jump_table, _) = analyze_legacy(bytecode.clone().into());
140        assert!(!jump_table.is_valid(0)); // Invalid opcode should not be a jumpdest
141    }
142
143    #[test]
144    fn test_bytecode_with_sequential_pushes() {
145        let bytecode = vec![
146            opcode::PUSH1,
147            0x01,
148            opcode::PUSH2,
149            0x02,
150            0x03,
151            opcode::PUSH4,
152            0x04,
153            0x05,
154            0x06,
155            0x07,
156            opcode::STOP,
157        ];
158        let (jump_table, padded_bytecode) = analyze_legacy(bytecode.clone().into());
159        assert_eq!(padded_bytecode.len(), bytecode.len());
160        assert!(!jump_table.is_valid(0)); // PUSH1
161        assert!(!jump_table.is_valid(2)); // PUSH2
162        assert!(!jump_table.is_valid(5)); // PUSH4
163    }
164
165    #[test]
166    fn test_bytecode_with_jumpdest_in_push_data() {
167        let bytecode = vec![
168            opcode::PUSH2,
169            opcode::JUMPDEST, // This should not be treated as a JUMPDEST
170            0x02,
171            opcode::STOP,
172        ];
173        let (jump_table, _) = analyze_legacy(bytecode.clone().into());
174        assert!(!jump_table.is_valid(1)); // JUMPDEST in push data should not be valid
175    }
176}