ferro_babe/lib.rs
1#![warn(missing_docs)]
2//! FerroBabe disassembles individual Java class files without requiring a Java runtime.
3//!
4//! [`Disassembler`] is the entry point. It parses bytes or a [`std::io::Read`] source into a
5//! [`Disassembly`], which exposes either a complete [`Class`] or the header and diagnostics that
6//! were available before decoding stopped. Use [`FerroFormatter`] for FerroBabe's compact text
7//! output, or implement [`Formatter`] to render the structured model yourself.
8
9mod decode;
10mod diagnostic;
11mod error;
12/// Text-formatting APIs for parsed class files.
13pub mod format;
14mod input;
15/// Borrowed views over class-file metadata, instructions, and constant-pool entries.
16pub mod model;
17mod options;
18
19pub use diagnostic::{Diagnostic, DiagnosticSeverity, DiagnosticStage};
20pub use error::FerroBabeError;
21pub use format::{FerroFormatter, Formatter};
22pub use model::{Class, ClassVersion, Disassembly};
23pub use options::{DisassemblerBuilder, DisassemblerOptions, RecoveryMode};
24
25use decode::decode_class;
26use input::ClassHeader;
27use std::io::Read;
28
29#[derive(Debug, Clone, Default)]
30/// Parses Java class files into a structured reverse-engineering model.
31///
32/// The default instance uses [`RecoveryMode::BestEffort`]. It preserves a readable class-file
33/// header and records a [`Diagnostic`] when complete decoding cannot continue.
34pub struct Disassembler {
35 options: DisassemblerOptions,
36}
37
38impl Disassembler {
39 /// Creates a builder for configuring a disassembler.
40 ///
41 /// The builder starts with [`RecoveryMode::BestEffort`].
42 #[must_use]
43 pub fn builder() -> DisassemblerBuilder {
44 DisassemblerBuilder::default()
45 }
46
47 /// Creates a disassembler with the supplied options.
48 #[must_use]
49 pub fn new(options: DisassemblerOptions) -> Self {
50 Self { options }
51 }
52
53 /// Returns the options used by this disassembler.
54 #[must_use]
55 pub fn options(&self) -> &DisassemblerOptions {
56 &self.options
57 }
58
59 /// Parses one complete Java class-file byte sequence.
60 ///
61 /// In best-effort mode, an error after the class-file header is read returns a partial
62 /// [`Disassembly`] with diagnostics. In strict mode, the same decoding failure is returned as
63 /// [`FerroBabeError`]. Invalid, incomplete, or unsupported headers always return an error.
64 ///
65 /// # Errors
66 ///
67 /// Returns an error when the input has no readable supported class-file header, or when
68 /// strict recovery is selected and decoding fails.
69 pub fn parse(&self, bytes: &[u8]) -> Result<Disassembly, FerroBabeError> {
70 let header = ClassHeader::read(bytes)?;
71 match decode_class(bytes, header) {
72 Ok(class) => Ok(Disassembly::complete(class)),
73 Err(error) if self.options.recovery() == RecoveryMode::BestEffort => Ok(
74 Disassembly::partial(header.into(), vec![Diagnostic::from_decode_error(&error)]),
75 ),
76 Err(error) => Err(error),
77 }
78 }
79
80 /// Reads and parses one Java class file from `reader`.
81 ///
82 /// The complete reader is buffered before parsing, so the returned [`Disassembly`] does not
83 /// borrow from `reader`.
84 ///
85 /// # Errors
86 ///
87 /// Returns [`FerroBabeError::InputRead`] when reading fails, or any error documented by
88 /// [`Self::parse`] after the bytes have been read.
89 pub fn parse_reader<R: Read>(&self, mut reader: R) -> Result<Disassembly, FerroBabeError> {
90 let mut bytes = Vec::new();
91 reader
92 .read_to_end(&mut bytes)
93 .map_err(|source| FerroBabeError::InputRead { source })?;
94 self.parse(&bytes)
95 }
96
97 /// Parses `bytes` and renders a complete class with [`FerroFormatter`].
98 ///
99 /// # Errors
100 ///
101 /// Returns parse errors, [`FerroBabeError::IncompleteDisassembly`] for a recovered partial
102 /// result, or a formatting error from the default formatter.
103 pub fn disassemble(&self, bytes: &[u8]) -> Result<String, FerroBabeError> {
104 self.disassemble_with(bytes, &FerroFormatter)
105 }
106
107 /// Parses `bytes` and renders the resulting complete class with `formatter`.
108 ///
109 /// `formatter` receives the same [`Class`] view available through [`Disassembly::class`].
110 ///
111 /// # Errors
112 ///
113 /// Returns parse errors, [`FerroBabeError::IncompleteDisassembly`] for a partial result, or
114 /// [`FerroBabeError::Format`] when `formatter` cannot write its output.
115 pub fn disassemble_with<F: Formatter>(
116 &self,
117 bytes: &[u8],
118 formatter: &F,
119 ) -> Result<String, FerroBabeError> {
120 let disassembly = self.parse(bytes)?;
121 let class = disassembly
122 .class()
123 .ok_or(FerroBabeError::IncompleteDisassembly)?;
124 formatter.format(class)
125 }
126}