Skip to main content

ferro_babe/model/
member.rs

1use rust_asm::class_reader::{AttributeInfo, ExceptionTableEntry};
2use rust_asm::constant_pool::CpInfo;
3use rust_asm::nodes::{FieldNode, MethodNode};
4
5use super::{ByteOffset, Instruction, StackMapFrame};
6
7#[derive(Debug, Clone, Copy)]
8/// A borrowed view of a field declaration.
9pub struct Field<'a> {
10    inner: &'a FieldNode,
11}
12
13impl<'a> Field<'a> {
14    pub(crate) const fn new(inner: &'a FieldNode) -> Self {
15        Self { inner }
16    }
17
18    /// Returns the raw field access-flag bitset.
19    #[must_use]
20    pub fn access_flags(&self) -> u16 {
21        self.inner.access_flags
22    }
23
24    /// Returns the field name exactly as stored in the class file.
25    #[must_use]
26    pub fn name(&self) -> &str {
27        &self.inner.name
28    }
29
30    /// Returns the JVM field descriptor.
31    #[must_use]
32    pub fn descriptor(&self) -> &str {
33        &self.inner.descriptor
34    }
35}
36
37#[derive(Debug, Clone, Copy)]
38/// A borrowed view of a method declaration and its optional code.
39pub struct Method<'a> {
40    inner: &'a MethodNode,
41    constant_pool: &'a [CpInfo],
42}
43
44impl<'a> Method<'a> {
45    pub(crate) const fn new(inner: &'a MethodNode, constant_pool: &'a [CpInfo]) -> Self {
46        Self {
47            inner,
48            constant_pool,
49        }
50    }
51
52    /// Returns the raw method access-flag bitset.
53    #[must_use]
54    pub fn access_flags(&self) -> u16 {
55        self.inner.access_flags
56    }
57
58    /// Returns the method name exactly as stored in the class file.
59    #[must_use]
60    pub fn name(&self) -> &str {
61        &self.inner.name
62    }
63
64    /// Returns the JVM method descriptor.
65    #[must_use]
66    pub fn descriptor(&self) -> &str {
67        &self.inner.descriptor
68    }
69
70    /// Returns whether this method has a `Code` attribute.
71    ///
72    /// Abstract and native methods normally return `false`.
73    #[must_use]
74    pub fn has_code(&self) -> bool {
75        self.inner.has_code
76    }
77
78    /// Returns the `Code` attribute's declared maximum operand-stack depth.
79    ///
80    /// Returns zero for methods without code.
81    #[must_use]
82    pub fn max_stack(&self) -> u16 {
83        self.inner.max_stack
84    }
85
86    /// Returns the `Code` attribute's declared maximum local-variable count.
87    ///
88    /// Returns zero for methods without code.
89    #[must_use]
90    pub fn max_locals(&self) -> u16 {
91        self.inner.max_locals
92    }
93
94    /// Iterates over bytecode instructions in original order when code is present.
95    ///
96    /// Returns `None` for methods without a `Code` attribute. Each instruction retains its
97    /// original bytecode offset and borrows from this method.
98    pub fn instructions(&self) -> Option<impl ExactSizeIterator<Item = Instruction<'a>> + '_> {
99        self.inner.has_code.then(|| {
100            self.inner
101                .instructions
102                .insns()
103                .iter()
104                .zip(self.inner.instruction_offsets.iter().copied())
105                .map(|(instruction, offset)| Instruction::new(offset, instruction))
106        })
107    }
108
109    /// Iterates over exception-table entries in class-file order.
110    ///
111    /// Methods without code return an empty iterator.
112    pub fn exception_handlers(&self) -> impl ExactSizeIterator<Item = ExceptionHandler<'a>> + '_ {
113        self.inner
114            .exception_table
115            .iter()
116            .map(|entry| ExceptionHandler::new(entry, self.constant_pool))
117    }
118
119    /// Iterates over `StackMapTable` frames when the method has that attribute.
120    ///
121    /// The class-file format stores frames as deltas from preceding frames. The
122    /// returned views preserve that encoding and resolve object verification
123    /// types against the method's constant pool.
124    pub fn stack_map_frames(
125        &self,
126    ) -> Option<impl ExactSizeIterator<Item = StackMapFrame<'a>> + '_> {
127        let entries = self
128            .inner
129            .code_attributes
130            .iter()
131            .find_map(|attribute| match attribute {
132                AttributeInfo::StackMapTable { entries } => Some(entries),
133                _ => None,
134            })?;
135
136        Some(
137            entries
138                .iter()
139                .map(|frame| StackMapFrame::new(frame, self.constant_pool)),
140        )
141    }
142}
143
144#[derive(Debug, Clone, Copy)]
145/// One protected bytecode range and its exception handler.
146pub struct ExceptionHandler<'a> {
147    start: ByteOffset,
148    end: ByteOffset,
149    handler: ByteOffset,
150    catch_type: Option<&'a str>,
151}
152
153impl<'a> ExceptionHandler<'a> {
154    fn new(entry: &'a ExceptionTableEntry, constant_pool: &'a [CpInfo]) -> Self {
155        let catch_type = (entry.catch_type != 0)
156            .then(|| class_name(constant_pool, entry.catch_type))
157            .flatten();
158
159        Self {
160            start: ByteOffset::new(entry.start_pc),
161            end: ByteOffset::new(entry.end_pc),
162            handler: ByteOffset::new(entry.handler_pc),
163            catch_type,
164        }
165    }
166
167    /// Returns the inclusive start offset of the protected range.
168    #[must_use]
169    pub const fn start(&self) -> ByteOffset {
170        self.start
171    }
172
173    /// Returns the exclusive end offset of the protected range.
174    #[must_use]
175    pub const fn end(&self) -> ByteOffset {
176        self.end
177    }
178
179    /// Returns the bytecode offset where the handler begins.
180    #[must_use]
181    pub const fn handler(&self) -> ByteOffset {
182        self.handler
183    }
184
185    /// Returns the caught exception's internal class name.
186    ///
187    /// Returns `None` for a catch-all handler or when the referenced class could not be resolved.
188    #[must_use]
189    pub const fn catch_type(&self) -> Option<&'a str> {
190        self.catch_type
191    }
192}
193
194fn class_name(constant_pool: &[CpInfo], index: u16) -> Option<&str> {
195    let CpInfo::Class { name_index } = constant_pool.get(index as usize)? else {
196        return None;
197    };
198    let CpInfo::Utf8(name) = constant_pool.get(*name_index as usize)? else {
199        return None;
200    };
201    Some(name)
202}