Skip to main content

ferro_babe/model/
member.rs

1use rust_asm::class_reader::ExceptionTableEntry;
2use rust_asm::constant_pool::CpInfo;
3use rust_asm::nodes::{FieldNode, MethodNode};
4
5use super::{ByteOffset, Instruction};
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
120#[derive(Debug, Clone, Copy)]
121/// One protected bytecode range and its exception handler.
122pub struct ExceptionHandler<'a> {
123    start: ByteOffset,
124    end: ByteOffset,
125    handler: ByteOffset,
126    catch_type: Option<&'a str>,
127}
128
129impl<'a> ExceptionHandler<'a> {
130    fn new(entry: &'a ExceptionTableEntry, constant_pool: &'a [CpInfo]) -> Self {
131        let catch_type = (entry.catch_type != 0)
132            .then(|| class_name(constant_pool, entry.catch_type))
133            .flatten();
134
135        Self {
136            start: ByteOffset::new(entry.start_pc),
137            end: ByteOffset::new(entry.end_pc),
138            handler: ByteOffset::new(entry.handler_pc),
139            catch_type,
140        }
141    }
142
143    /// Returns the inclusive start offset of the protected range.
144    #[must_use]
145    pub const fn start(&self) -> ByteOffset {
146        self.start
147    }
148
149    /// Returns the exclusive end offset of the protected range.
150    #[must_use]
151    pub const fn end(&self) -> ByteOffset {
152        self.end
153    }
154
155    /// Returns the bytecode offset where the handler begins.
156    #[must_use]
157    pub const fn handler(&self) -> ByteOffset {
158        self.handler
159    }
160
161    /// Returns the caught exception's internal class name.
162    ///
163    /// Returns `None` for a catch-all handler or when the referenced class could not be resolved.
164    #[must_use]
165    pub const fn catch_type(&self) -> Option<&'a str> {
166        self.catch_type
167    }
168}
169
170fn class_name(constant_pool: &[CpInfo], index: u16) -> Option<&str> {
171    let CpInfo::Class { name_index } = constant_pool.get(index as usize)? else {
172        return None;
173    };
174    let CpInfo::Utf8(name) = constant_pool.get(*name_index as usize)? else {
175        return None;
176    };
177    Some(name)
178}