Skip to main content

ristretto_classfile/
class_file.rs

1use crate::attributes::Attribute;
2use crate::byte_reader::ByteReader;
3use crate::class_access_flags::ClassAccessFlags;
4use crate::constant_pool::ConstantPool;
5use crate::display::indent_lines;
6use crate::error::Error::InvalidMagicNumber;
7use crate::error::Result;
8use crate::field::Field;
9use crate::java_string::JavaStr;
10use crate::method::Method;
11use crate::verifiers::verifier;
12use crate::version::Version;
13use byteorder::{BigEndian, WriteBytesExt};
14use std::fmt;
15
16/// The magic number that identifies a valid Java class file.
17///
18/// Every Java class file begins with this 4-byte value (`0xCAFE_BABE`) in big-endian format. This
19/// signature allows the JVM to identify valid class files and reject invalid ones.
20const MAGIC: u32 = 0xCAFE_BABE;
21
22/// `ClassFile` represents the content of a Java .class file.
23///
24/// A class file contains the definition of a single class or interface, including:
25/// - Version information
26/// - Constant pool (containing various string constants, class and field references, etc.)
27/// - Access flags (defining visibility and properties)
28/// - This class and super class references
29/// - Interfaces implemented by the class
30/// - Fields and methods of the class
31/// - Class attributes
32///
33/// # Examples
34///
35/// ```rust,no_run
36/// use ristretto_classfile::ClassFile;
37/// use std::fs;
38///
39/// // Read the bytes of a class file
40/// let bytes = fs::read("path/to/Example.class")?;
41///
42/// // Parse the bytes into a ClassFile
43/// let class_file = ClassFile::from_bytes(&bytes)?;
44///
45/// // Now you can inspect the class
46/// println!("Class name: {}", class_file.class_name()?);
47/// println!("Class version: {}", class_file.version);
48/// # Ok::<(), ristretto_classfile::Error>(())
49/// ```
50///
51/// # References
52///
53/// See: <https://docs.oracle.com/javase/specs/jvms/se25/html/jvms-4.html#jvms-4.1>
54#[derive(Clone, Debug, Default, Eq, PartialEq)]
55pub struct ClassFile<'a> {
56    pub version: Version,
57    pub constant_pool: ConstantPool<'a>,
58    pub access_flags: ClassAccessFlags,
59    pub this_class: u16,
60    pub super_class: u16,
61    pub interfaces: Vec<u16>,
62    pub fields: Vec<Field>,
63    pub methods: Vec<Method>,
64    pub attributes: Vec<Attribute>,
65    /// The code source URL for this class (e.g., `file:/path/to/classes/`).
66    /// Not part of the binary class file format.
67    pub code_source_url: Option<String>,
68}
69
70impl<'a> ClassFile<'a> {
71    /// Convert this `ClassFile` into an owned version with `'static` lifetime.
72    #[must_use]
73    pub fn into_owned(self) -> ClassFile<'static> {
74        ClassFile {
75            version: self.version,
76            constant_pool: self.constant_pool.into_owned(),
77            access_flags: self.access_flags,
78            this_class: self.this_class,
79            super_class: self.super_class,
80            interfaces: self.interfaces,
81            fields: self.fields,
82            methods: self.methods,
83            attributes: self.attributes,
84            code_source_url: self.code_source_url,
85        }
86    }
87
88    /// Get the fully qualified class name.
89    ///
90    /// # Errors
91    ///
92    /// Returns an error if the class name is not found.
93    ///
94    /// # Examples
95    ///
96    /// ```rust,no_run
97    /// # use ristretto_classfile::ClassFile;
98    /// # let bytes = vec![];
99    /// # let class_file = ClassFile::from_bytes(&bytes)?;
100    /// let class_name = class_file.class_name().expect("Failed to get class name");
101    /// println!("Class name: {class_name}"); // e.g., "java.lang.String"
102    /// # Ok::<(), ristretto_classfile::Error>(())
103    /// ```
104    pub fn class_name(&self) -> Result<&JavaStr> {
105        self.constant_pool.try_get_class(self.this_class)
106    }
107
108    /// Verify the `ClassFile`.
109    ///
110    /// This method checks that the class file is well-formed according to the JVM specification.
111    /// It validates the constant pool entries, method definitions, and other aspects of the class.
112    ///
113    /// # Examples
114    ///
115    /// ```rust,no_run
116    /// use ristretto_classfile::ClassFile;
117    ///
118    /// let class_file = ClassFile::default();
119    ///
120    /// // Verify that the class file is valid
121    /// if let Err(error) = class_file.verify() {
122    ///     eprintln!("Class verification failed: {error:?}");
123    /// } else {
124    ///     println!("Class verified successfully");
125    /// }
126    /// ```
127    ///
128    /// # Errors
129    /// Returns a `VerificationError` if the verification fails.
130    pub fn verify(&self) -> Result<()> {
131        verifier::verify(self).map_err(crate::Error::from)
132    }
133
134    /// Deserialize the `ClassFile` from bytes.
135    ///
136    /// This function reads a binary class file format and constructs a `ClassFile` instance. It
137    /// follows the Java class file format specification to parse:
138    /// - Magic number (`0xCAFE_BABE`)
139    /// - Version information
140    /// - Constant pool
141    /// - Access flags
142    /// - Class references
143    /// - Interfaces
144    /// - Fields
145    /// - Methods
146    /// - Attributes
147    ///
148    /// # Errors
149    ///
150    /// Returns an error if:
151    /// - The bytes do not start with the correct magic number
152    /// - The class file format is invalid
153    /// - There are IO errors when reading the bytes
154    ///
155    /// # Examples
156    ///
157    /// ```rust,no_run
158    /// use ristretto_classfile::ClassFile;
159    /// use std::fs;
160    ///
161    /// // Read the bytes of a class file
162    /// let bytes = fs::read("path/to/Example.class")?;
163    ///
164    /// // Parse the bytes into a ClassFile
165    /// let class_file = ClassFile::from_bytes(&bytes)?;
166    ///
167    /// // Now you can inspect the class
168    /// println!("Class name: {}", class_file.class_name()?);
169    /// println!("Class version: {}", class_file.version);
170    /// # Ok::<(), ristretto_classfile::Error>(())
171    /// ```
172    pub fn from_bytes(bytes: &[u8]) -> Result<ClassFile<'static>> {
173        let mut reader = ByteReader::new(bytes);
174        Self::parse_from_reader(&mut reader)
175    }
176
177    /// Parse a `ClassFile` directly from a byte slice, borrowing string data where possible.
178    ///
179    /// Unlike [`from_bytes`](Self::from_bytes), which returns `ClassFile<'static>` with owned
180    /// strings, this method returns `ClassFile<'a>` that borrows UTF-8 data directly from the
181    /// input slice, avoiding allocation overhead.
182    ///
183    /// # Errors
184    ///
185    /// Returns an error if the bytes do not represent a valid class file.
186    pub fn from_slice(data: &'a [u8]) -> Result<ClassFile<'a>> {
187        let mut reader = ByteReader::new(data);
188        Self::from_byte_reader(&mut reader)
189    }
190
191    /// Parse from a `ByteReader` and return a `ClassFile<'static>` by converting all
192    /// borrowed strings to owned.
193    fn parse_from_reader(reader: &mut ByteReader<'_>) -> Result<ClassFile<'static>> {
194        let magic = reader.read_u32()?;
195        if magic != MAGIC {
196            return Err(InvalidMagicNumber(magic));
197        }
198
199        let version = Version::from_bytes(reader)?;
200        let constant_pool = ConstantPool::from_bytes(reader)?.into_owned();
201        let access_flags = ClassAccessFlags::from_bits_truncate(reader.read_u16()?);
202        let this_class = reader.read_u16()?;
203        let super_class = reader.read_u16()?;
204
205        let interfaces_count = reader.read_u16()? as usize;
206        let mut interfaces = Vec::with_capacity(interfaces_count);
207        for _ in 0..interfaces_count {
208            interfaces.push(reader.read_u16()?);
209        }
210
211        let field_count = reader.read_u16()? as usize;
212        let mut fields = Vec::with_capacity(field_count);
213        for _ in 0..field_count {
214            let field = Field::from_bytes(&constant_pool, reader)?;
215            fields.push(field);
216        }
217
218        let method_count = reader.read_u16()? as usize;
219        let mut methods = Vec::with_capacity(method_count);
220        for _ in 0..method_count {
221            let method = Method::from_bytes(&constant_pool, reader)?;
222            methods.push(method);
223        }
224
225        let attribute_count = reader.read_u16()? as usize;
226        let mut attributes = Vec::with_capacity(attribute_count);
227        for _ in 0..attribute_count {
228            let attribute = Attribute::from_bytes(&constant_pool, reader)?;
229            attributes.push(attribute);
230        }
231
232        Ok(ClassFile {
233            version,
234            constant_pool,
235            access_flags,
236            this_class,
237            super_class,
238            interfaces,
239            fields,
240            methods,
241            attributes,
242            code_source_url: None,
243        })
244    }
245
246    /// Internal fast-path parser using `ByteReader` for zero-overhead reads.
247    fn from_byte_reader(reader: &mut ByteReader<'a>) -> Result<ClassFile<'a>> {
248        let magic = reader.read_u32()?;
249        if magic != MAGIC {
250            return Err(InvalidMagicNumber(magic));
251        }
252
253        let version = Version::from_bytes(reader)?;
254        let constant_pool = ConstantPool::from_bytes(reader)?;
255        let access_flags = ClassAccessFlags::from_bits_truncate(reader.read_u16()?);
256        let this_class = reader.read_u16()?;
257        let super_class = reader.read_u16()?;
258
259        let interfaces_count = reader.read_u16()? as usize;
260        let mut interfaces = Vec::with_capacity(interfaces_count);
261        for _ in 0..interfaces_count {
262            interfaces.push(reader.read_u16()?);
263        }
264
265        let field_count = reader.read_u16()? as usize;
266        let mut fields = Vec::with_capacity(field_count);
267        for _ in 0..field_count {
268            let field = Field::from_bytes(&constant_pool, reader)?;
269            fields.push(field);
270        }
271
272        let method_count = reader.read_u16()? as usize;
273        let mut methods = Vec::with_capacity(method_count);
274        for _ in 0..method_count {
275            let method = Method::from_bytes(&constant_pool, reader)?;
276            methods.push(method);
277        }
278
279        let attribute_count = reader.read_u16()? as usize;
280        let mut attributes = Vec::with_capacity(attribute_count);
281        for _ in 0..attribute_count {
282            let attribute = Attribute::from_bytes(&constant_pool, reader)?;
283            attributes.push(attribute);
284        }
285
286        Ok(ClassFile {
287            version,
288            constant_pool,
289            access_flags,
290            this_class,
291            super_class,
292            interfaces,
293            fields,
294            methods,
295            attributes,
296            code_source_url: None,
297        })
298    }
299
300    /// Serialize the `ClassFile` to bytes.
301    ///
302    /// This function converts a `ClassFile` instance into its binary representation according to
303    /// the Java class file format specification.
304    ///
305    /// # Examples
306    ///
307    /// ```rust,no_run
308    /// use ristretto_classfile::{ClassFile, ConstantPool, Version, ClassAccessFlags, JAVA_21};
309    /// use std::fs;
310    /// use std::io::Write;
311    ///
312    /// // Create a new class file
313    /// let mut constant_pool = ConstantPool::default();
314    /// let this_class = constant_pool.add_class("HelloWorld")?;
315    /// let super_class = constant_pool.add_class("java/lang/Object")?;
316    ///
317    /// let class_file = ClassFile {
318    ///     version: JAVA_21,
319    ///     access_flags: ClassAccessFlags::PUBLIC,
320    ///     constant_pool,
321    ///     this_class,
322    ///     super_class,
323    ///     ..Default::default()
324    /// };
325    ///
326    /// // Verify the class file is valid
327    /// class_file.verify()?;
328    ///
329    /// // Write the class file to a vector of bytes
330    /// let mut buffer = Vec::new();
331    /// class_file.to_bytes(&mut buffer)?;
332    ///
333    /// // Now you can save these bytes to a file
334    /// fs::write("HelloWorld.class", buffer)?;
335    /// # Ok::<(), Box<dyn std::error::Error>>(())
336    /// ```
337    ///
338    /// # Errors
339    /// - If there are more than 65,534 interfaces, fields, methods, or attributes
340    ///   (exceeding u16 capacity)
341    /// - If there are IO errors when writing to the output vector
342    pub fn to_bytes(&self, bytes: &mut Vec<u8>) -> Result<()> {
343        bytes.write_u32::<BigEndian>(MAGIC)?;
344        self.version.to_bytes(bytes)?;
345        self.constant_pool.to_bytes(bytes)?;
346        self.access_flags.to_bytes(bytes)?;
347        bytes.write_u16::<BigEndian>(self.this_class)?;
348        bytes.write_u16::<BigEndian>(self.super_class)?;
349
350        let interfaces_length = u16::try_from(self.interfaces.len())?;
351        bytes.write_u16::<BigEndian>(interfaces_length)?;
352        for interface in &self.interfaces {
353            bytes.write_u16::<BigEndian>(*interface)?;
354        }
355
356        let fields_length = u16::try_from(self.fields.len())?;
357        bytes.write_u16::<BigEndian>(fields_length)?;
358        for field in &self.fields {
359            field.to_bytes(bytes)?;
360        }
361
362        let methods_length = u16::try_from(self.methods.len())?;
363        bytes.write_u16::<BigEndian>(methods_length)?;
364        for method in &self.methods {
365            method.to_bytes(bytes)?;
366        }
367
368        let attributes_length = u16::try_from(self.attributes.len())?;
369        bytes.write_u16::<BigEndian>(attributes_length)?;
370        for attribute in &self.attributes {
371            attribute.to_bytes(bytes)?;
372        }
373
374        Ok(())
375    }
376}
377
378impl fmt::Display for ClassFile<'_> {
379    /// Implements the Display trait for `ClassFile` to provide a human-readable representation.
380    ///
381    /// The formatted output includes:
382    /// - Class name and access flags
383    /// - Version information (minor and major versions)
384    /// - Flag descriptions
385    /// - Class references (`this_class` and `super_class`)
386    /// - Counts of interfaces, fields, methods, and attributes
387    /// - Full constant pool listing
388    /// - Interface references
389    /// - Field definitions
390    /// - Method definitions
391    /// - Class attributes
392    ///
393    /// This format is similar to the output of the `javap -v` command.
394    ///
395    /// # Examples
396    ///
397    /// ```rust,no_run
398    /// use ristretto_classfile::ClassFile;
399    /// use std::fs;
400    ///
401    /// // Load a class file
402    /// let bytes = fs::read("HelloWorld.class")?;
403    /// let class_file = ClassFile::from_bytes(&bytes)?;
404    ///
405    /// // Print the class file in a human-readable format
406    /// println!("{class_file}");
407    ///
408    /// // Output will look similar to:
409    /// // public class HelloWorld
410    /// //   minor version: 0
411    /// //   major version: 65 (Java 21)
412    /// //   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
413    /// //   this_class: #7
414    /// //   super_class: #2
415    /// //   interfaces: 0, fields: 0, methods: 1, attributes: 1
416    /// // Constant pool:
417    /// //    #1 = Methodref          #2.#3
418    /// //    #2 = Class              #4
419    /// //    ...
420    /// # Ok::<(), Box<dyn std::error::Error>>(())
421    /// ```
422    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423        let class_name = self.class_name().map_err(|_| fmt::Error)?;
424        writeln!(f, "{} {class_name}", self.access_flags.as_code())?;
425        writeln!(f, "  minor version: {}", self.version.minor())?;
426        writeln!(
427            f,
428            "  major version: {} ({})",
429            self.version.major(),
430            self.version
431        )?;
432        writeln!(f, "  flags: {}", self.access_flags)?;
433        writeln!(f, "  this_class: #{}", self.this_class)?;
434        writeln!(f, "  super_class: #{}", self.super_class)?;
435        writeln!(
436            f,
437            "  interfaces: {}, fields: {}, methods: {}, attributes: {}",
438            self.interfaces.len(),
439            self.fields.len(),
440            self.methods.len(),
441            self.attributes.len()
442        )?;
443
444        writeln!(f, "Constant pool:")?;
445        write!(f, "{}", self.constant_pool)?;
446        writeln!(f, "{{")?;
447
448        if !self.interfaces.is_empty() {
449            writeln!(f, "interfaces:")?;
450            for interface in &self.interfaces {
451                writeln!(f, "  #{interface}")?;
452            }
453        }
454
455        if !self.fields.is_empty() {
456            writeln!(f, "fields:")?;
457            for (index, field) in self.fields.iter().enumerate() {
458                if index > 0 {
459                    writeln!(f)?;
460                }
461                writeln!(f, "{}", indent_lines(&field.to_string(), "  "))?;
462            }
463        }
464
465        writeln!(f, "methods:")?;
466        for (index, method) in self.methods.iter().enumerate() {
467            if index > 0 {
468                writeln!(f)?;
469            }
470            writeln!(f, "{}", indent_lines(&method.to_string(), "  "))?;
471        }
472
473        writeln!(f, "}}")?;
474
475        for attribute in &self.attributes {
476            writeln!(f, "{}", &attribute.to_string())?;
477        }
478        Ok(())
479    }
480}
481
482#[cfg(test)]
483mod test {
484    use super::*;
485    use crate::Error::{InvalidConstantPoolIndexType, IoError};
486    use crate::error::Result;
487    use crate::{Constant, JAVA_8, JAVA_21};
488    use indoc::indoc;
489
490    #[test]
491    fn test_invalid_magic() {
492        let invalid_magic: u32 = 0x0102_0304;
493        let bytes = invalid_magic.to_be_bytes();
494        assert_eq!(
495            Err(InvalidMagicNumber(invalid_magic)),
496            ClassFile::from_bytes(&bytes)
497        );
498    }
499
500    #[test]
501    fn test_class_name() -> Result<()> {
502        let class_bytes = include_bytes!("../../classes/Minimum.class");
503        let class_file = ClassFile::from_bytes(class_bytes.as_slice())?;
504        assert_eq!("Minimum", class_file.class_name()?);
505        Ok(())
506    }
507
508    #[test]
509    fn test_class_name_invalid_constant_pool() -> Result<()> {
510        let mut constant_pool = ConstantPool::default();
511        let utf8_index = constant_pool.add_utf8("Test")?;
512        let class_file = ClassFile {
513            constant_pool,
514            this_class: utf8_index,
515            ..Default::default()
516        };
517        assert_eq!(
518            Err(InvalidConstantPoolIndexType(1)),
519            class_file.class_name()
520        );
521        Ok(())
522    }
523
524    #[test]
525    fn test_verify() -> Result<()> {
526        let class_bytes = include_bytes!("../../classes/Minimum.class");
527        let class_file = ClassFile::from_bytes(class_bytes.as_slice())?;
528        assert!(class_file.verify().is_ok());
529        Ok(())
530    }
531
532    #[test]
533    fn test_verify_error() -> Result<()> {
534        let mut constant_pool = ConstantPool::default();
535        let this_class = constant_pool.add_class("Test")?;
536        // Add an invalid constant to trigger a verification error.
537        constant_pool.push(Constant::Class(u16::MAX));
538        let class_file = ClassFile {
539            version: JAVA_21,
540            constant_pool: constant_pool.clone(),
541            this_class,
542            ..Default::default()
543        };
544
545        assert_eq!(
546            Err(crate::Error::VerificationError(
547                crate::verifiers::error::VerifyError::InvalidConstantPoolIndex(3)
548            )),
549            class_file.verify()
550        );
551        Ok(())
552    }
553
554    #[test]
555    fn test_minimum_to_string() -> Result<()> {
556        let class_bytes = include_bytes!("../../classes/Minimum.class");
557        let class_file = ClassFile::from_bytes(class_bytes.as_slice())?;
558        let expected = indoc! {r"
559            public class Minimum
560              minor version: 0
561              major version: 52 (Java 8)
562              flags: (0x0021) ACC_PUBLIC, ACC_SUPER
563              this_class: #7
564              super_class: #2
565              interfaces: 0, fields: 0, methods: 1, attributes: 1
566            Constant pool:
567               #1 = Methodref          #2.#3
568               #2 = Class              #4
569               #3 = NameAndType        #5:#6
570               #4 = Utf8               java/lang/Object
571               #5 = Utf8               <init>
572               #6 = Utf8               ()V
573               #7 = Class              #8
574               #8 = Utf8               Minimum
575               #9 = Utf8               Code
576              #10 = Utf8               LineNumberTable
577              #11 = Utf8               SourceFile
578              #12 = Utf8               Minimum.java
579            {
580            methods:
581              flags: (0x0001) ACC_PUBLIC
582              name_index: #5
583              descriptor_index: #6
584              attributes:
585                Code:
586                  stack=1, locals=1
587                     0: aload_0
588                     1: invokespecial #1
589                     4: return
590                  LineNumberTable:
591                    line 1: 0
592            }
593            SourceFile { name_index: 11, source_file_index: 12 }
594        "};
595
596        assert_eq!(expected, class_file.to_string());
597        Ok(())
598    }
599
600    #[test]
601    fn test_minimum_serialization() -> Result<()> {
602        let class_bytes = include_bytes!("../../classes/Minimum.class");
603        let expected_bytes = class_bytes.to_vec();
604        let class_file = ClassFile::from_bytes(&expected_bytes)?;
605
606        assert_eq!(JAVA_8, class_file.version);
607        assert_eq!(
608            ClassAccessFlags::PUBLIC | ClassAccessFlags::SUPER,
609            class_file.access_flags
610        );
611        assert_eq!("Minimum", class_file.class_name()?);
612
613        let mut bytes = Vec::with_capacity(4);
614        class_file.to_bytes(&mut bytes)?;
615        assert_eq!(expected_bytes, bytes);
616        Ok(())
617    }
618
619    #[test]
620    fn test_from_bytes_invalid() {
621        let bytes = vec![
622            202, 254, 186, 190, 254, 0, 0, 48, 0, 0, 160, 93, 37, 0, 212, 186,
623        ];
624        assert_eq!(
625            Err(IoError("Invalid constant pool count".to_string())),
626            ClassFile::from_bytes(&bytes)
627        );
628    }
629}