Skip to main content

ferro_babe/model/
class.rs

1use rust_asm::nodes::ClassNode;
2
3use super::{ConstantPoolIndex, ConstantRef, Field, Method};
4use crate::Diagnostic;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7/// The minor and major version numbers stored in a class-file header.
8pub struct ClassVersion {
9    minor: u16,
10    major: u16,
11}
12
13impl ClassVersion {
14    pub(crate) const fn new(minor: u16, major: u16) -> Self {
15        Self { minor, major }
16    }
17
18    /// Returns the class-file minor version.
19    #[must_use]
20    pub fn minor(self) -> u16 {
21        self.minor
22    }
23
24    /// Returns the class-file major version.
25    #[must_use]
26    pub fn major(self) -> u16 {
27        self.major
28    }
29}
30
31#[derive(Debug, Clone)]
32/// A complete, decoded Java class file.
33///
34/// This type owns the parsed class-file data. Its member and constant-pool accessors return
35/// borrowed views, so no additional model allocation is required while inspecting a class.
36pub struct Class {
37    node: ClassNode,
38}
39
40impl Class {
41    pub(crate) fn from_node(node: ClassNode) -> Self {
42        Self { node }
43    }
44
45    /// Returns the class-file version recorded in the header.
46    #[must_use]
47    pub fn version(&self) -> ClassVersion {
48        ClassVersion {
49            minor: self.node.minor_version,
50            major: self.node.major_version,
51        }
52    }
53
54    /// Returns the raw class access-flag bitset.
55    ///
56    /// Interpret the bits in the context of the Java Virtual Machine Specification's `ClassFile`
57    /// access flags.
58    #[must_use]
59    pub fn access_flags(&self) -> u16 {
60        self.node.access_flags
61    }
62
63    /// Returns this class's internal JVM name.
64    ///
65    /// Internal names use slash-separated package segments and are not normalized by FerroBabe.
66    #[must_use]
67    pub fn name(&self) -> &str {
68        &self.node.name
69    }
70
71    /// Returns the direct superclass internal name, if the class has one.
72    ///
73    /// `None` represents the `java/lang/Object` root class.
74    #[must_use]
75    pub fn super_name(&self) -> Option<&str> {
76        self.node.super_name.as_deref()
77    }
78
79    /// Returns the `SourceFile` attribute value when present.
80    #[must_use]
81    pub fn source_file(&self) -> Option<&str> {
82        self.node.source_file.as_deref()
83    }
84
85    /// Iterates over direct interface internal names in class-file order.
86    pub fn interfaces(&self) -> impl ExactSizeIterator<Item = &str> {
87        self.node.interfaces.iter().map(String::as_str)
88    }
89
90    /// Iterates over fields in class-file order.
91    pub fn fields(&self) -> impl ExactSizeIterator<Item = Field<'_>> {
92        self.node.fields.iter().map(Field::new)
93    }
94
95    /// Iterates over methods in class-file order.
96    ///
97    /// Each returned method can borrow the class constant pool to resolve exception handler names.
98    pub fn methods(&self) -> impl ExactSizeIterator<Item = Method<'_>> {
99        self.node
100            .methods
101            .iter()
102            .map(|method| Method::new(method, &self.node.constant_pool))
103    }
104
105    /// Iterates over every constant-pool slot, including unusable reserved slots.
106    ///
107    /// The returned index is the original one-based class-file index. Slot zero is included only
108    /// when supplied by the underlying model and is reported as [`ConstantRef::Unusable`].
109    pub fn constants(&self) -> impl ExactSizeIterator<Item = (ConstantPoolIndex, ConstantRef<'_>)> {
110        self.node
111            .constant_pool
112            .iter()
113            .enumerate()
114            .map(|(index, entry)| {
115                (
116                    ConstantPoolIndex::new(index as u16),
117                    ConstantRef::from(entry),
118                )
119            })
120    }
121
122    /// Returns the constant-pool entry at `index`.
123    ///
124    /// Returns `None` when `index` is outside the decoded constant pool.
125    #[must_use]
126    pub fn constant(&self, index: ConstantPoolIndex) -> Option<ConstantRef<'_>> {
127        self.node
128            .constant_pool
129            .get(index.get() as usize)
130            .map(ConstantRef::from)
131    }
132}
133
134#[derive(Debug, Clone)]
135/// The outcome of parsing one class-file input.
136///
137/// A complete result contains a [`Class`]. Best-effort recovery returns a partial result when
138/// only the class-file header was available; callers can inspect [`Self::version`] and
139/// [`Self::diagnostics`] in that case.
140pub struct Disassembly {
141    header: ClassVersion,
142    class: Option<Class>,
143    diagnostics: Vec<Diagnostic>,
144}
145
146impl Disassembly {
147    pub(crate) fn complete(class: Class) -> Self {
148        Self {
149            header: class.version(),
150            class: Some(class),
151            diagnostics: Vec::new(),
152        }
153    }
154
155    pub(crate) fn partial(header: ClassVersion, diagnostics: Vec<Diagnostic>) -> Self {
156        Self {
157            header,
158            class: None,
159            diagnostics,
160        }
161    }
162
163    /// Returns the class-file version read from the input header.
164    ///
165    /// This is available for both complete and partial results.
166    #[must_use]
167    pub fn version(&self) -> ClassVersion {
168        self.header
169    }
170
171    /// Returns `true` when a complete [`Class`] model is available.
172    #[must_use]
173    pub fn is_complete(&self) -> bool {
174        self.class.is_some()
175    }
176
177    /// Returns the complete parsed class, if decoding finished successfully.
178    ///
179    /// Returns `None` for a best-effort partial result.
180    #[must_use]
181    pub fn class(&self) -> Option<&Class> {
182        self.class.as_ref()
183    }
184
185    /// Returns diagnostics collected during best-effort recovery.
186    ///
187    /// Complete results currently return an empty slice.
188    #[must_use]
189    pub fn diagnostics(&self) -> &[Diagnostic] {
190        &self.diagnostics
191    }
192}