etk_asm/
disasm.rs

1//! A simple disassembler from the EVM Toolkit.
2//!
3//! Converts a stream of bytes into an iterator of [`Op<[u8]>'].
4//!
5//! See the documentation for [`Disassembler`] for more information.
6mod error {
7    use snafu::{Backtrace, Snafu};
8
9    use super::Offset;
10
11    /// Errors that may arise during disassembly.
12    #[derive(Debug, Snafu)]
13    #[snafu(context(suffix(false)), visibility(pub(super)))]
14    #[non_exhaustive]
15    pub enum Error {
16        /// The input is incomplete.
17        #[non_exhaustive]
18        Truncated {
19            /// The remaining bytes, with their location.
20            remaining: Offset<Vec<u8>>,
21
22            /// The location of the error.
23            backtrace: Backtrace,
24        },
25    }
26}
27
28use etk_ops::shanghai::Op;
29
30pub use self::error::Error;
31
32use snafu::ensure;
33
34use std::collections::VecDeque;
35use std::fmt;
36use std::io::{self, Write};
37
38/// An item with its location within a stream of bytes.
39#[derive(Debug, Clone, Copy, Eq, PartialEq)]
40pub struct Offset<T> {
41    /// The location within a stream of bytes of a particular item.
42    pub offset: usize,
43
44    /// The item, which was located at `offset.
45    pub item: T,
46}
47
48impl<T> Offset<T> {
49    /// Create a new instance of `Offset`.
50    pub const fn new(offset: usize, item: T) -> Self {
51        Self { offset, item }
52    }
53}
54
55impl<T> fmt::Display for Offset<T>
56where
57    T: fmt::Display,
58{
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        write!(f, "{: >4x}:   {}", self.offset, self.item)
61    }
62}
63
64/// A [`std::iter::Iterator`] over the [`Op<[u8]>`] produced by disassembling
65/// a stream of bytes.
66#[derive(Debug)]
67pub struct Iter<'a> {
68    disassembler: &'a mut Disassembler,
69}
70
71impl<'a> Iterator for Iter<'a> {
72    type Item = Offset<Op<[u8]>>;
73
74    fn next(&mut self) -> Option<Self::Item> {
75        let buffer = &mut self.disassembler.buffer;
76        let front = *buffer.front()?;
77        let specifier = Op::<()>::from(front);
78        let len = specifier.size();
79        if buffer.len() < len {
80            return None;
81        }
82
83        let remaining = buffer.split_off(len);
84        let mut instruction = std::mem::replace(&mut self.disassembler.buffer, remaining);
85        let instruction = instruction.make_contiguous();
86
87        let item = Op::from_slice(instruction).ok()?;
88        let offset = self.disassembler.offset;
89        self.disassembler.offset += len;
90        Some(Offset::new(offset, item))
91    }
92}
93
94/// A simple disassembler that converts a stream of bytes into an iterator over
95/// the disassembled [`Op<[u8]>`].
96///
97/// ## Example
98/// ```rust
99/// use etk_ops::shanghai::{Op, GetPc, Stop};
100/// use etk_asm::disasm::Disassembler;
101/// # use etk_asm::disasm::Offset;
102///
103/// use std::io::Write;
104///
105/// let input = [0x58, 0x00];
106///
107/// let mut dasm = Disassembler::new();
108/// dasm.write_all(&input).unwrap();
109///
110/// let actual: Vec<_> = dasm.ops().collect();
111///
112/// dasm.finish().unwrap();
113///
114/// # let expected = [Offset::new(0, GetPc.into()), Offset::new(1, Stop.into())];
115/// # assert_eq!(expected, actual.as_slice());
116/// ```
117#[derive(Debug, Default)]
118pub struct Disassembler {
119    buffer: VecDeque<u8>,
120    offset: usize,
121}
122
123impl Write for Disassembler {
124    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
125        self.buffer.reserve(buf.len());
126        self.buffer.extend(buf);
127        Ok(buf.len())
128    }
129
130    fn flush(&mut self) -> io::Result<()> {
131        Ok(())
132    }
133}
134
135impl Disassembler {
136    /// Create a new instance of `Disassembler`.
137    pub fn new() -> Self {
138        Default::default()
139    }
140
141    /// Get an iterator over the disassembled [`Op<[u8]>`].
142    pub fn ops(&mut self) -> Iter {
143        Iter { disassembler: self }
144    }
145
146    /// Indicate that there are no further bytes to write. Returns any errors
147    /// collected.
148    pub fn finish(self) -> Result<(), Error> {
149        ensure!(
150            self.buffer.is_empty(),
151            error::Truncated {
152                remaining: Offset::new(self.offset, self.buffer.into()),
153            }
154        );
155        Ok(())
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use etk_ops::shanghai::*;
162
163    use hex_literal::hex;
164
165    use super::*;
166
167    #[test]
168    fn empty() {
169        let mut dasm = Disassembler::new();
170        assert!(dasm.ops().next().is_none());
171        dasm.finish().unwrap();
172    }
173
174    #[test]
175    fn stop() {
176        let input = hex!("00");
177        let expected = [Offset::new(0, Op::from(Stop))];
178
179        let mut dasm = Disassembler::new();
180        dasm.write_all(&input).unwrap();
181
182        let actual: Vec<_> = dasm.ops().collect();
183
184        assert_eq!(expected, actual.as_slice());
185        dasm.finish().unwrap();
186    }
187
188    #[test]
189    fn partial_push5() {
190        let input = hex!("6401020304");
191        let expected = [Offset::new(0, Op::from(Push5(hex!("0102030406"))))];
192
193        let mut dasm = Disassembler::new();
194        dasm.write_all(&input).unwrap();
195        assert!(dasm.ops().next().is_none());
196
197        dasm.write_all(&hex!("06")).unwrap();
198
199        let actual: Vec<_> = dasm.ops().collect();
200
201        assert_eq!(expected, actual.as_slice());
202        dasm.finish().unwrap();
203    }
204
205    #[test]
206    fn push5() {
207        let input = hex!("640102030405");
208        let expected = [Offset::new(0, Op::from(Push5(hex!("0102030405"))))];
209
210        let mut dasm = Disassembler::new();
211        dasm.write_all(&input).unwrap();
212
213        let actual: Vec<_> = dasm.ops().collect();
214
215        assert_eq!(expected, actual.as_slice());
216        dasm.finish().unwrap();
217    }
218}