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}