Skip to main content

ferro_babe/model/
instruction.rs

1use rust_asm::insn::{Insn, LdcValue, MemberRef};
2
3use super::ConstantPoolIndex;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6/// A byte offset within a method's `Code` attribute.
7pub struct ByteOffset(u16);
8
9impl ByteOffset {
10    pub(crate) const fn new(offset: u16) -> Self {
11        Self(offset)
12    }
13
14    /// Returns the raw byte offset.
15    #[must_use]
16    pub const fn get(self) -> u16 {
17        self.0
18    }
19}
20
21#[derive(Debug, Clone, Copy)]
22/// A borrowed JVM instruction paired with its original bytecode offset.
23pub struct Instruction<'a> {
24    offset: ByteOffset,
25    instruction: &'a Insn,
26}
27
28impl<'a> Instruction<'a> {
29    pub(crate) const fn new(offset: u16, instruction: &'a Insn) -> Self {
30        Self {
31            offset: ByteOffset::new(offset),
32            instruction,
33        }
34    }
35
36    /// Returns the instruction's offset within its containing method's code array.
37    #[must_use]
38    pub const fn offset(&self) -> ByteOffset {
39        self.offset
40    }
41
42    /// Returns the raw JVM opcode byte.
43    #[must_use]
44    pub fn opcode(&self) -> u8 {
45        opcode_of(self.instruction)
46    }
47
48    /// Returns this instruction's decoded operand while preserving raw indices and offsets.
49    ///
50    /// Branch targets are calculated relative to [`Self::offset`]. Switch target slices contain
51    /// relative offsets; their associated `base_offset` identifies the instruction from which to
52    /// calculate an absolute target.
53    #[must_use]
54    pub fn operand(&self) -> InstructionOperand<'a> {
55        let offset = i32::from(self.offset.get());
56
57        match self.instruction {
58            Insn::Simple(_) => InstructionOperand::None,
59            Insn::Int(node) => InstructionOperand::Immediate(node.operand),
60            Insn::Var(node) => InstructionOperand::Local(node.var_index),
61            Insn::Type(node) => {
62                InstructionOperand::ConstantPool(ConstantPoolIndex::new(node.type_index))
63            }
64            Insn::Field(node) => InstructionOperand::Member(MemberReference::from(&node.field_ref)),
65            Insn::Method(node) => {
66                InstructionOperand::Member(MemberReference::from(&node.method_ref))
67            }
68            Insn::InvokeInterface(node) => InstructionOperand::InvokeInterface {
69                method: ConstantPoolIndex::new(node.method_index),
70                count: node.count,
71            },
72            Insn::InvokeDynamic(node) => InstructionOperand::InvokeDynamic {
73                call_site: ConstantPoolIndex::new(node.method_index),
74            },
75            Insn::Jump(node) => InstructionOperand::Branch {
76                relative: node.offset,
77                target: offset + node.offset,
78            },
79            Insn::Ldc(node) => InstructionOperand::Ldc(LdcValueRef::from(&node.value)),
80            Insn::Iinc(node) => InstructionOperand::Increment {
81                local: node.var_index,
82                amount: node.increment,
83            },
84            Insn::TableSwitch(node) => InstructionOperand::TableSwitch {
85                default: SwitchTarget::new(node.default_offset, offset + node.default_offset),
86                low: node.low,
87                high: node.high,
88                targets: &node.offsets,
89                base_offset: self.offset,
90            },
91            Insn::LookupSwitch(node) => InstructionOperand::LookupSwitch {
92                default: SwitchTarget::new(node.default_offset, offset + node.default_offset),
93                pairs: &node.pairs,
94                base_offset: self.offset,
95            },
96            Insn::MultiANewArray(node) => InstructionOperand::MultiArray {
97                class: ConstantPoolIndex::new(node.type_index),
98                dimensions: node.dimensions,
99            },
100        }
101    }
102}
103
104#[derive(Debug, Clone, Copy)]
105/// A decoded JVM instruction operand.
106///
107/// Variants expose the operand representation used by the class file. In particular, indices are
108/// not rewritten into source-level names and switch targets retain their relative offsets.
109pub enum InstructionOperand<'a> {
110    /// An instruction with no explicit operand.
111    None,
112    /// A signed immediate operand.
113    Immediate(i32),
114    /// A local-variable slot index.
115    Local(u16),
116    /// A direct constant-pool index.
117    ConstantPool(ConstantPoolIndex),
118    /// A field or method reference.
119    Member(MemberReference<'a>),
120    /// An `invokeinterface` method index and its encoded argument count.
121    InvokeInterface {
122        /// Index of the interface method reference.
123        method: ConstantPoolIndex,
124        /// Encoded argument count byte.
125        count: u8,
126    },
127    /// An `invokedynamic` call-site index.
128    InvokeDynamic {
129        /// Index of the invokedynamic constant-pool entry.
130        call_site: ConstantPoolIndex,
131    },
132    /// A branch displacement and its calculated target offset.
133    Branch {
134        /// Signed displacement encoded by the instruction.
135        relative: i32,
136        /// Offset obtained by adding `relative` to the instruction offset.
137        target: i32,
138    },
139    /// A value loaded by an `ldc`, `ldc_w`, or `ldc2_w` instruction.
140    Ldc(LdcValueRef<'a>),
141    /// A local-variable increment.
142    Increment {
143        /// Local-variable slot index.
144        local: u16,
145        /// Signed increment amount.
146        amount: i16,
147    },
148    /// A `tableswitch` operand.
149    TableSwitch {
150        /// Default target displacement and absolute target.
151        default: SwitchTarget,
152        /// Lowest matching key.
153        low: i32,
154        /// Highest matching key.
155        high: i32,
156        /// Relative target offsets in key order.
157        targets: &'a [i32],
158        /// Offset of the `tableswitch` instruction.
159        base_offset: ByteOffset,
160    },
161    /// A `lookupswitch` operand.
162    LookupSwitch {
163        /// Default target displacement and absolute target.
164        default: SwitchTarget,
165        /// Match-key and relative-target pairs in class-file order.
166        pairs: &'a [(i32, i32)],
167        /// Offset of the `lookupswitch` instruction.
168        base_offset: ByteOffset,
169    },
170    /// A `multianewarray` class reference and dimension count.
171    MultiArray {
172        /// Index of the array class entry.
173        class: ConstantPoolIndex,
174        /// Number of dimensions to allocate.
175        dimensions: u8,
176    },
177}
178
179#[derive(Debug, Clone, Copy)]
180/// A field or method reference used by an instruction.
181pub enum MemberReference<'a> {
182    /// A reference preserved as a constant-pool index.
183    ConstantPool(ConstantPoolIndex),
184    /// A reference already resolved into its JVM symbolic components.
185    Symbolic {
186        /// Declaring class or interface internal name.
187        owner: &'a str,
188        /// Member name.
189        name: &'a str,
190        /// JVM field or method descriptor.
191        descriptor: &'a str,
192    },
193}
194
195impl<'a> From<&'a MemberRef> for MemberReference<'a> {
196    fn from(value: &'a MemberRef) -> Self {
197        match value {
198            MemberRef::Index(index) => Self::ConstantPool(ConstantPoolIndex::new(*index)),
199            MemberRef::Symbolic {
200                owner,
201                name,
202                descriptor,
203            } => Self::Symbolic {
204                owner,
205                name,
206                descriptor,
207            },
208        }
209    }
210}
211
212#[derive(Debug, Clone, Copy)]
213/// A value loaded by an LDC-family instruction.
214pub enum LdcValueRef<'a> {
215    /// A constant-pool entry reference.
216    ConstantPool(ConstantPoolIndex),
217    /// A resolved string literal.
218    String(&'a str),
219    /// A type literal whose exact descriptor is not retained by this view.
220    TypeDescriptor,
221    /// A 32-bit integer literal.
222    Integer(i32),
223    /// A 32-bit floating-point literal.
224    Float(f32),
225    /// A 64-bit integer literal.
226    Long(i64),
227    /// A 64-bit floating-point literal.
228    Double(f64),
229}
230
231impl<'a> From<&'a LdcValue> for LdcValueRef<'a> {
232    fn from(value: &'a LdcValue) -> Self {
233        match value {
234            LdcValue::Index(index) => Self::ConstantPool(ConstantPoolIndex::new(*index)),
235            LdcValue::String(value) => Self::String(value),
236            LdcValue::Type(_) => Self::TypeDescriptor,
237            LdcValue::Int(value) => Self::Integer(*value),
238            LdcValue::Float(value) => Self::Float(*value),
239            LdcValue::Long(value) => Self::Long(*value),
240            LdcValue::Double(value) => Self::Double(*value),
241        }
242    }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq)]
246/// A switch or branch target represented as both relative and absolute offsets.
247pub struct SwitchTarget {
248    relative: i32,
249    target: i32,
250}
251
252impl SwitchTarget {
253    const fn new(relative: i32, target: i32) -> Self {
254        Self { relative, target }
255    }
256
257    /// Returns the signed displacement encoded in the class file.
258    #[must_use]
259    pub const fn relative(self) -> i32 {
260        self.relative
261    }
262
263    /// Returns the target offset calculated from the enclosing switch instruction.
264    #[must_use]
265    pub const fn target(self) -> i32 {
266        self.target
267    }
268}
269
270fn opcode_of(instruction: &Insn) -> u8 {
271    match instruction {
272        Insn::Simple(node) => node.opcode,
273        Insn::Int(node) => node.insn.opcode,
274        Insn::Var(node) => node.insn.opcode,
275        Insn::Type(node) => node.insn.opcode,
276        Insn::Field(node) => node.insn.opcode,
277        Insn::Method(node) => node.insn.opcode,
278        Insn::InvokeInterface(node) => node.insn.opcode,
279        Insn::InvokeDynamic(node) => node.insn.opcode,
280        Insn::Jump(node) => node.insn.opcode,
281        Insn::Ldc(node) => node.insn.opcode,
282        Insn::Iinc(node) => node.insn.opcode,
283        Insn::TableSwitch(node) => node.insn.opcode,
284        Insn::LookupSwitch(node) => node.insn.opcode,
285        Insn::MultiANewArray(node) => node.insn.opcode,
286    }
287}