diff --git a/Cargo.toml b/Cargo.toml index d1aa695..a91e7b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "classfile-parser" description = "A parser for Java Class files written in rust" version = "0.3.9" -edition = "2021" +edition = "2024" authors = ["Nick Palmer "] keywords = ["java", "parsing", "class", "classfile"] categories = ["parser-implementations"] @@ -12,6 +12,10 @@ license = "MIT" exclude = ["java-assets/out/**/*"] [dependencies] -nom = "7" +nom = "^7" bitflags = "^2.3" cesu8 = "^1.1" +binrw = "0.15.0" + +[dev-dependencies] +assert_matches = "1.5.0" diff --git a/README.md b/README.md index 3f54dad..088beaf 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ use classfile_parser::class_parser; fn main() { let classfile_bytes = include_bytes!("../path/to/JavaClass.class"); - + match class_parser(classfile_bytes) { Ok((_, class_file)) => { println!( @@ -84,30 +84,30 @@ fn main() { - [x] Methods - [x] Attributes - [x] Basic attribute info block parsing - - [ ] Known typed attributes parsing + - [x] Known typed attributes parsing - [x] Critical for JVM - [x] ConstantValue - [x] Code - [x] StackMapTable - [x] Exceptions - [x] BootstrapMethods - - [ ] Critical for Java SE - - [ ] InnerClasses - - [ ] EnclosingMethod - - [ ] Synthetic - - [ ] Signature - - [ ] RuntimeVisibleAnnotations - - [ ] RuntimeInvisibleAnnotations - - [ ] RuntimeVisibleParameterAnnotations - - [ ] RuntimeInvisibleParameterAnnotations - - [ ] RuntimeVisibleTypeAnnotations - - [ ] RuntimeInvisibleTypeAnnotations - - [ ] AnnotationDefault + - [x] Critical for Java SE + - [x] InnerClasses + - [x] EnclosingMethod + - [x] Synthetic + - [x] Signature + - [x] RuntimeVisibleAnnotations + - [x] RuntimeInvisibleAnnotations + - [x] RuntimeVisibleParameterAnnotations + - [x] RuntimeInvisibleParameterAnnotations + - [x] RuntimeVisibleTypeAnnotations + - [x] RuntimeInvisibleTypeAnnotations + - [x] AnnotationDefault - [X] MethodParameters - - [ ] Useful but not critical + - [x] Useful but not critical - [x] SourceFile - - [ ] SourceDebugExtension - - [ ] LineNumberTable + - [~] SourceDebugExtension + - [x] LineNumberTable - [x] LocalVariableTable - - [ ] LocalVariableTypeTable - - [ ] Deprecated + - [x] LocalVariableTypeTable + - [x] Deprecated diff --git a/java-assets/compile.sh b/java-assets/compile.sh index 2ce8fb1..cea4c97 100755 --- a/java-assets/compile.sh +++ b/java-assets/compile.sh @@ -25,6 +25,8 @@ javac -d java-assets/compiled-classes/ -g:none java-assets/src/Factorial.java javac -d java-assets/compiled-classes/ java-assets/src/Instructions.java javac -d java-assets/compiled-classes/ java-assets/src/UnicodeStrings.java javac -d java-assets/compiled-classes/ java-assets/src/DeprecatedAnnotation.java +javac -d java-assets/compiled-classes/ java-assets/src/InnerClasses.java +javac -d java-assets/compiled-classes/ java-assets/src/Annotations.java javac -g -d java-assets/compiled-classes/ java-assets/src/LocalVariableTable.java javac -d java-assets/compiled-classes/ java-assets/src/HelloWorld.java diff --git a/java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class new file mode 100644 index 0000000..ca06a61 Binary files /dev/null and b/java-assets/compiled-classes/Annotations$InvisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class new file mode 100644 index 0000000..36cae7a Binary files /dev/null and b/java-assets/compiled-classes/Annotations$ParamInvisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations$ParamVisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$ParamVisibleAtRuntime.class new file mode 100644 index 0000000..72a018c Binary files /dev/null and b/java-assets/compiled-classes/Annotations$ParamVisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class new file mode 100644 index 0000000..856365e Binary files /dev/null and b/java-assets/compiled-classes/Annotations$TypeInvisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class new file mode 100644 index 0000000..bbb2721 Binary files /dev/null and b/java-assets/compiled-classes/Annotations$TypeVisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations$VisibleAtRuntime.class b/java-assets/compiled-classes/Annotations$VisibleAtRuntime.class new file mode 100644 index 0000000..cff9b27 Binary files /dev/null and b/java-assets/compiled-classes/Annotations$VisibleAtRuntime.class differ diff --git a/java-assets/compiled-classes/Annotations.class b/java-assets/compiled-classes/Annotations.class new file mode 100644 index 0000000..78ced4f Binary files /dev/null and b/java-assets/compiled-classes/Annotations.class differ diff --git a/java-assets/compiled-classes/BasicClass.class b/java-assets/compiled-classes/BasicClass.class index ec64225..cab2379 100644 Binary files a/java-assets/compiled-classes/BasicClass.class and b/java-assets/compiled-classes/BasicClass.class differ diff --git a/java-assets/compiled-classes/BootstrapMethods.class b/java-assets/compiled-classes/BootstrapMethods.class index 166a793..54154ac 100644 Binary files a/java-assets/compiled-classes/BootstrapMethods.class and b/java-assets/compiled-classes/BootstrapMethods.class differ diff --git a/java-assets/compiled-classes/DeprecatedAnnotation.class b/java-assets/compiled-classes/DeprecatedAnnotation.class index dfaba31..106fdeb 100644 Binary files a/java-assets/compiled-classes/DeprecatedAnnotation.class and b/java-assets/compiled-classes/DeprecatedAnnotation.class differ diff --git a/java-assets/compiled-classes/Factorial.class b/java-assets/compiled-classes/Factorial.class index bbb56bc..9709e7f 100644 Binary files a/java-assets/compiled-classes/Factorial.class and b/java-assets/compiled-classes/Factorial.class differ diff --git a/java-assets/compiled-classes/HelloWorld.class b/java-assets/compiled-classes/HelloWorld.class index c4d915c..230ca2b 100644 Binary files a/java-assets/compiled-classes/HelloWorld.class and b/java-assets/compiled-classes/HelloWorld.class differ diff --git a/java-assets/compiled-classes/InnerClasses$1.class b/java-assets/compiled-classes/InnerClasses$1.class new file mode 100644 index 0000000..7410ad0 Binary files /dev/null and b/java-assets/compiled-classes/InnerClasses$1.class differ diff --git a/java-assets/compiled-classes/InnerClasses$1EnglishGreeting.class b/java-assets/compiled-classes/InnerClasses$1EnglishGreeting.class new file mode 100644 index 0000000..0639785 Binary files /dev/null and b/java-assets/compiled-classes/InnerClasses$1EnglishGreeting.class differ diff --git a/java-assets/compiled-classes/InnerClasses$2.class b/java-assets/compiled-classes/InnerClasses$2.class new file mode 100644 index 0000000..7f2815c Binary files /dev/null and b/java-assets/compiled-classes/InnerClasses$2.class differ diff --git a/java-assets/compiled-classes/InnerClasses$HelloWorld.class b/java-assets/compiled-classes/InnerClasses$HelloWorld.class new file mode 100644 index 0000000..c5831ef Binary files /dev/null and b/java-assets/compiled-classes/InnerClasses$HelloWorld.class differ diff --git a/java-assets/compiled-classes/InnerClasses.class b/java-assets/compiled-classes/InnerClasses.class new file mode 100644 index 0000000..9e42ecd Binary files /dev/null and b/java-assets/compiled-classes/InnerClasses.class differ diff --git a/java-assets/compiled-classes/Instructions.class b/java-assets/compiled-classes/Instructions.class index c1f08b4..3cb696c 100644 Binary files a/java-assets/compiled-classes/Instructions.class and b/java-assets/compiled-classes/Instructions.class differ diff --git a/java-assets/compiled-classes/LocalVariableTable.class b/java-assets/compiled-classes/LocalVariableTable.class index ff0af9a..33d8448 100644 Binary files a/java-assets/compiled-classes/LocalVariableTable.class and b/java-assets/compiled-classes/LocalVariableTable.class differ diff --git a/java-assets/compiled-classes/UnicodeStrings.class b/java-assets/compiled-classes/UnicodeStrings.class index 2330623..243ac6b 100644 Binary files a/java-assets/compiled-classes/UnicodeStrings.class and b/java-assets/compiled-classes/UnicodeStrings.class differ diff --git a/java-assets/compiled-classes/malformed.class b/java-assets/compiled-classes/malformed.class index 4089af7..202b7c9 100644 Binary files a/java-assets/compiled-classes/malformed.class and b/java-assets/compiled-classes/malformed.class differ diff --git a/java-assets/src/Annotations.java b/java-assets/src/Annotations.java new file mode 100644 index 0000000..03f7765 --- /dev/null +++ b/java-assets/src/Annotations.java @@ -0,0 +1,55 @@ +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +public class Annotations { + public @TypeVisibleAtRuntime(value = "type visible") String visibleAnnotationType; + public @TypeInvisibleAtRuntime(value = "type invisible") String invisibleAnnotationType; + + @Retention(RetentionPolicy.RUNTIME) + public @interface VisibleAtRuntime { + String value() default "default annotation"; + } + + @Retention(RetentionPolicy.CLASS) + public @interface InvisibleAtRuntime { + String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface ParamVisibleAtRuntime { + String value(); + } + + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.PARAMETER) + public @interface ParamInvisibleAtRuntime { + String value(); + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE_USE) + public @interface TypeVisibleAtRuntime { + String value(); + } + + @Retention(RetentionPolicy.CLASS) + @Target(ElementType.TYPE_USE) + public @interface TypeInvisibleAtRuntime { + String value(); + } + + @VisibleAtRuntime(value = "visisble") + @InvisibleAtRuntime(value = "invisible") + public void myMethod(@ParamVisibleAtRuntime(value = "visible") String v, @ParamInvisibleAtRuntime(value = "invisible") String i) { + System.out.print(v); + System.out.println(i); + } + + public void main(String[] args) { + Annotations annotations = new Annotations(); + annotations.myMethod("Hello,", " World!"); + } +} diff --git a/java-assets/src/InnerClasses.java b/java-assets/src/InnerClasses.java new file mode 100644 index 0000000..6bbdc7b --- /dev/null +++ b/java-assets/src/InnerClasses.java @@ -0,0 +1,47 @@ +public class InnerClasses { + interface HelloWorld { + public void greet(); + public void greetSomeone(String someone); + } + + public void sayHello() { + class EnglishGreeting implements HelloWorld { + String name = "world"; + public void greet() { + greetSomeone("world"); + } + public void greetSomeone(String someone) { + name = someone; + System.out.println("Hello " + name); + } + } + + HelloWorld englishGreeting = new EnglishGreeting(); + + HelloWorld frenchGreeting = new HelloWorld() { + String name = "tout le monde"; + public void greet() { + greetSomeone("tout le monde"); + } + public void greetSomeone(String someone) { + name = someone; + System.out.println("Salut " + name); + } + }; + + HelloWorld spanishGreeting = new HelloWorld() { + String name = "mundo"; + public void greet() { + greetSomeone("mundo"); + } + + public void greetSomeone(String someone) { + name = someone; + System.out.println("Hola, " + name); + } + }; + englishGreeting.greet(); + frenchGreeting.greetSomeone("Fred"); + spanishGreeting.greet(); + } +} diff --git a/src/attribute_info/mod.rs b/src/attribute_info/mod.rs index 509ae8b..49de015 100644 --- a/src/attribute_info/mod.rs +++ b/src/attribute_info/mod.rs @@ -7,7 +7,19 @@ pub use self::parser::attribute_parser; pub use self::parser::bootstrap_methods_attribute_parser; pub use self::parser::code_attribute_parser; pub use self::parser::constant_value_attribute_parser; +pub use self::parser::element_value_parser; +pub use self::parser::enclosing_method_attribute_parser; pub use self::parser::exceptions_attribute_parser; +pub use self::parser::inner_classes_attribute_parser; +pub use self::parser::line_number_table_attribute_parser; pub use self::parser::method_parameters_attribute_parser; +pub use self::parser::runtime_invisible_annotations_attribute_parser; +pub use self::parser::runtime_invisible_parameter_annotations_attribute_parser; +pub use self::parser::runtime_invisible_type_annotations_attribute_parser; +pub use self::parser::runtime_visible_annotations_attribute_parser; +pub use self::parser::runtime_visible_parameter_annotations_attribute_parser; +pub use self::parser::runtime_visible_type_annotations_attribute_parser; +pub use self::parser::signature_attribute_parser; +pub use self::parser::source_debug_extension_parser; pub use self::parser::sourcefile_attribute_parser; pub use self::parser::stack_map_table_attribute_parser; diff --git a/src/attribute_info/parser.rs b/src/attribute_info/parser.rs index 1c42aee..7226f3c 100644 --- a/src/attribute_info/parser.rs +++ b/src/attribute_info/parser.rs @@ -1,10 +1,10 @@ use nom::{ + Err as BaseErr, bytes::complete::take, combinator::{map, success}, error::{Error, ErrorKind}, multi::count, - number::complete::{be_u16, be_u32, be_u8}, - Err as BaseErr, + number::complete::{be_u8, be_u16, be_u32}, }; use crate::attribute_info::types::StackMapFrame::*; @@ -94,6 +94,412 @@ pub fn parameters_parser(input: &[u8]) -> Result<(&[u8], ParameterAttribute), Er )) } +pub fn inner_classes_attribute_parser( + input: &[u8], +) -> Result<(&[u8], InnerClassesAttribute), Err<&[u8]>> { + let (input, number_of_classes) = be_u16(input)?; + let (input, classes) = count(inner_class_info_parser, number_of_classes as usize)(input)?; + let ret = ( + input, + InnerClassesAttribute { + number_of_classes, + classes, + }, + ); + + Ok(ret) +} + +pub fn inner_class_info_parser(input: &[u8]) -> Result<(&[u8], InnerClassInfo), Err<&[u8]>> { + let (input, inner_class_info_index) = be_u16(input)?; + let (input, outer_class_info_index) = be_u16(input)?; + let (input, inner_name_index) = be_u16(input)?; + let (input, inner_class_access_flags) = be_u16(input)?; + Ok(( + input, + InnerClassInfo { + inner_class_info_index, + outer_class_info_index, + inner_name_index, + inner_class_access_flags, + }, + )) +} + +pub fn enclosing_method_attribute_parser( + input: &[u8], +) -> Result<(&[u8], EnclosingMethodAttribute), Err<&[u8]>> { + let (input, class_index) = be_u16(input)?; + let (input, method_index) = be_u16(input)?; + Ok(( + input, + EnclosingMethodAttribute { + class_index, + method_index, + }, + )) +} + +pub fn signature_attribute_parser(input: &[u8]) -> Result<(&[u8], SignatureAttribute), Err<&[u8]>> { + let (input, signature_index) = be_u16(input)?; + Ok((input, SignatureAttribute { signature_index })) +} + +pub fn runtime_visible_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeVisibleAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeVisibleAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + +pub fn runtime_invisible_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, annotations) = count(annotation_parser, num_annotations as usize)(input)?; + Ok(( + input, + RuntimeInvisibleAnnotationsAttribute { + num_annotations, + annotations, + }, + )) +} + +pub fn runtime_visible_parameter_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeVisibleParameterAnnotationsAttribute), Err<&[u8]>> { + let (input, num_parameters) = be_u8(input)?; + let (input, parameter_annotations) = count( + runtime_visible_annotations_attribute_parser, + num_parameters as usize, + )(input)?; + Ok(( + input, + RuntimeVisibleParameterAnnotationsAttribute { + num_parameters, + parameter_annotations, + }, + )) +} + +pub fn runtime_invisible_parameter_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleParameterAnnotationsAttribute), Err<&[u8]>> { + let (input, num_parameters) = be_u8(input)?; + let (input, parameter_annotations) = count( + runtime_invisible_annotations_attribute_parser, + num_parameters as usize, + )(input)?; + Ok(( + input, + RuntimeInvisibleParameterAnnotationsAttribute { + num_parameters, + parameter_annotations, + }, + )) +} +pub fn runtime_visible_type_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeVisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, type_annotations) = count(type_annotation_parser, num_annotations as usize)(input)?; + + Ok(( + input, + RuntimeVisibleTypeAnnotationsAttribute { + num_annotations, + type_annotations, + }, + )) +} + +pub fn runtime_invisible_type_annotations_attribute_parser( + input: &[u8], +) -> Result<(&[u8], RuntimeInvisibleTypeAnnotationsAttribute), Err<&[u8]>> { + let (input, num_annotations) = be_u16(input)?; + let (input, type_annotations) = count(type_annotation_parser, num_annotations as usize)(input)?; + + Ok(( + input, + RuntimeInvisibleTypeAnnotationsAttribute { + num_annotations, + type_annotations, + }, + )) +} + +pub fn type_annotation_parser(input: &[u8]) -> Result<(&[u8], TypeAnnotation), Err<&[u8]>> { + let (input, target_type) = be_u8(input)?; + let mut target_info: TargetInfo = TargetInfo::Empty; + match target_type { + 0x0 | 0x1 => { + let (_input, type_parameter_index) = be_u8(input)?; + target_info = TargetInfo::TypeParameter { + type_parameter_index, + }; + } + 0x10 => { + let (_input, supertype_index) = be_u16(input)?; + target_info = TargetInfo::SuperType { supertype_index }; + } + 0x11..=0x12 => { + let (input, type_parameter_index) = be_u8(input)?; + let (_input, bound_index) = be_u8(input)?; + target_info = TargetInfo::TypeParameterBound { + type_parameter_index, + bound_index, + } + } + 0x13..=0x15 => { + // Empty target_info + } + 0x16 => { + let (_input, formal_parameter_index) = be_u8(input)?; + target_info = TargetInfo::FormalParameter { + formal_parameter_index, + }; + } + 0x17 => { + let (_input, throws_type_index) = be_u16(input)?; + target_info = TargetInfo::Throws { throws_type_index }; + } + 0x40 | 0x41 => { + let (input, table_length) = be_u16(input)?; + let (_input, tables) = count( + local_variable_table_annotation_parser, + table_length as usize, + )(input)?; + target_info = TargetInfo::LocalVar { + table_length, + tables, + }; + } + 0x42 => { + let (_input, exception_table_index) = be_u16(input)?; + target_info = TargetInfo::Catch { + exception_table_index, + } + } + 0x43..=0x46 => { + let (_input, offset) = be_u16(input)?; + target_info = TargetInfo::Offset { offset } + } + 0x47..=0x4B => { + let (input, offset) = be_u16(input)?; + let (_input, type_argument_index) = be_u8(input)?; + target_info = TargetInfo::TypeArgument { + offset, + type_argument_index, + }; + } + _ => { + eprintln!( + "Parsing RuntimeVisibleTypeAnnotationsAttribute with target_type = {}", + target_type + ); + } + } + let (input, target_path) = target_path_parser(input)?; + let (input, type_index) = be_u16(input)?; + let (input, num_element_value_pairs) = be_u16(input)?; + let (input, element_value_pairs) = + count(element_value_pair_parser, num_element_value_pairs as usize)(input)?; + + Ok(( + input, + TypeAnnotation { + target_type, + target_info, + target_path, + type_index, + num_element_value_pairs, + element_value_pairs, + }, + )) +} + +fn target_path_parser(input: &[u8]) -> Result<(&[u8], TypePath), Err<&[u8]>> { + let (input, path_length) = be_u8(input)?; + let (input, paths) = count( + |input| { + let (input, type_path_kind) = be_u8(input)?; + let (input, type_argument_index) = be_u8(input)?; + Ok(( + input, + TypePathEntry { + type_path_kind, + type_argument_index, + }, + )) + }, + path_length as usize, + )(input)?; + Ok((input, TypePath { path_length, paths })) +} + +pub fn local_variable_table_annotation_parser( + input: &[u8], +) -> Result<(&[u8], LocalVarTableAnnotation), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, length) = be_u16(input)?; + let (input, index) = be_u16(input)?; + Ok(( + input, + LocalVarTableAnnotation { + start_pc, + length, + index, + }, + )) +} +fn annotation_parser(input: &[u8]) -> Result<(&[u8], RuntimeAnnotation), Err<&[u8]>> { + let (input, type_index) = be_u16(input)?; + let (input, num_element_value_pairs) = be_u16(input)?; + eprintln!( + "Parsing annotation with type index = {}, and {} element value pairs", + type_index, num_element_value_pairs + ); + let (input, element_value_pairs) = + count(element_value_pair_parser, num_element_value_pairs as usize)(input)?; + Ok(( + input, + RuntimeAnnotation { + type_index, + num_element_value_pairs, + element_value_pairs, + }, + )) +} + +fn element_value_pair_parser(input: &[u8]) -> Result<(&[u8], ElementValuePair), Err<&[u8]>> { + let (input, element_name_index) = be_u16(input)?; + let (input, value) = element_value_parser(input)?; + Ok(( + input, + ElementValuePair { + element_name_index, + value, + }, + )) +} + +fn array_value_parser(input: &[u8]) -> Result<(&[u8], ElementArrayValue), Err<&[u8]>> { + let (input, num_values) = be_u16(input)?; + let (input, values) = count(element_value_parser, num_values as usize)(input)?; + Ok((input, ElementArrayValue { num_values, values })) +} + +pub fn element_value_parser(input: &[u8]) -> Result<(&[u8], ElementValue), Err<&[u8]>> { + let (input, tag) = be_u8(input)?; + eprintln!("Element value parsing: tag = {}", tag as char); + + match tag as char { + 'B' | 'C' | 'I' | 'S' | 'Z' | 'D' | 'F' | 'J' | 's' => { + let (input, const_value_index) = be_u16(input)?; + eprintln!( + "Element value parsing: const_value_index = {}", + const_value_index + ); + Ok(( + input, + ElementValue::ConstValueIndex { + tag: tag as char, + value: const_value_index, + }, + )) + } + 'e' => { + let (input, enum_const_value) = enum_const_value_parser(input)?; + eprintln!( + "Element value parsing: enum_const_value = {:?}", + enum_const_value + ); + Ok((input, ElementValue::EnumConst(enum_const_value))) + } + 'c' => { + let (input, class_info_index) = be_u16(input)?; + eprintln!( + "Element value parsing: class_info_index = {}", + class_info_index + ); + Ok((input, ElementValue::ClassInfoIndex(class_info_index))) + } + '@' => { + let (input, annotation_value) = annotation_parser(input)?; + eprintln!( + "Element value parsing: annotation_value = {:?}", + annotation_value + ); + Ok((input, ElementValue::AnnotationValue(annotation_value))) + } + '[' => { + let (input, array_value) = array_value_parser(input)?; + eprintln!("Element value parsing: array_value = {:?}", array_value); + Ok((input, ElementValue::ElementArray(array_value))) + } + _ => Result::Err(Err::Error(error_position!(input, ErrorKind::NoneOf))), + } +} + +fn enum_const_value_parser(input: &[u8]) -> Result<(&[u8], EnumConstValue), Err<&[u8]>> { + let (input, type_name_index) = be_u16(input)?; + let (input, const_name_index) = be_u16(input)?; + Ok(( + input, + EnumConstValue { + type_name_index, + const_name_index, + }, + )) +} + +// not even really parsing ... +pub fn source_debug_extension_parser( + input: &[u8], +) -> Result<(&[u8], SourceDebugExtensionAttribute), Err<&[u8]>> { + let debug_extension = Vec::from(input); + Ok((input, SourceDebugExtensionAttribute { debug_extension })) +} + +pub fn line_number_table_attribute_parser( + input: &[u8], +) -> Result<(&[u8], LineNumberTable), Err<&[u8]>> { + let (input, line_number_table_length) = be_u16(input)?; + let (input, line_number_table) = count( + line_number_table_entry_parser, + line_number_table_length as usize, + )(input)?; + Ok(( + input, + LineNumberTable { + line_number_table_length, + line_number_table, + }, + )) +} + +pub fn line_number_table_entry_parser( + input: &[u8], +) -> Result<(&[u8], LineNumberTableEntry), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, line_number) = be_u16(input)?; + Ok(( + input, + LineNumberTableEntry { + start_pc, + line_number, + }, + )) +} + fn same_frame_parser(input: &[u8], frame_type: u8) -> Result<(&[u8], StackMapFrame), Err<&[u8]>> { success(SameFrame { frame_type })(input) } diff --git a/src/attribute_info/types.rs b/src/attribute_info/types.rs index 2a690cd..6febc66 100644 --- a/src/attribute_info/types.rs +++ b/src/attribute_info/types.rs @@ -1,7 +1,12 @@ +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct AttributeInfo { pub attribute_name_index: u16, pub attribute_length: u32, + #[br(args { count: attribute_length.try_into().unwrap() })] pub info: Vec, } @@ -37,6 +42,210 @@ pub struct ParameterAttribute { pub access_flags: u16, } +#[derive(Clone, Debug)] +pub struct InnerClassesAttribute { + pub number_of_classes: u16, + pub classes: Vec, +} + +#[derive(Clone, Debug)] +pub struct InnerClassInfo { + pub inner_class_info_index: u16, + pub outer_class_info_index: u16, + pub inner_name_index: u16, + pub inner_class_access_flags: u16, +} + +bitflags! { + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] + pub struct InnerClassAccessFlags: u16 { + const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. + const PRIVATE = 0x0002; // Declared private; may not be accessed from outside its package. + const PROTECTED = 0x0004; // Declared praotected; may only be accessed within children. + const STATIC = 0x0008; // Declared static. + const FINAL = 0x0010; // Declared final; no subclasses allowed. + const INTERFACE = 0x0200; // Is an interface, not a class. + const ABSTRACT = 0x0400; // Declared abstract; must not be instantiated. + const SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code. + const ANNOTATION = 0x2000; // Declared as an annotation type. + const ENUM = 0x4000; // Declared as an enum type. + } +} + +#[derive(Clone, Debug)] +pub struct EnclosingMethodAttribute { + pub class_index: u16, + pub method_index: u16, +} + +// in all reality this struct isn't required b/c it's zero sized +// "Synthetic" is a marker attribute +#[derive(Clone, Debug)] +pub struct SyntheticAttribute {} + +#[derive(Clone, Debug)] +pub struct SignatureAttribute { + pub signature_index: u16, +} + +#[derive(Clone, Debug)] +pub struct RuntimeVisibleAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleAnnotationsAttribute { + pub num_annotations: u16, + pub annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeVisibleParameterAnnotationsAttribute { + pub num_parameters: u8, + pub parameter_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleParameterAnnotationsAttribute { + pub num_parameters: u8, + pub parameter_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeVisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub type_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct RuntimeInvisibleTypeAnnotationsAttribute { + pub num_annotations: u16, + pub type_annotations: Vec, +} + +#[derive(Clone, Debug)] +pub struct TypeAnnotation { + pub target_type: u8, + pub target_info: TargetInfo, + pub target_path: TypePath, + pub type_index: u16, + pub num_element_value_pairs: u16, + pub element_value_pairs: Vec, +} + +#[derive(Clone, Debug)] +pub enum TargetInfo { + TypeParameter { + type_parameter_index: u8, + }, + SuperType { + supertype_index: u16, + }, + TypeParameterBound { + type_parameter_index: u8, + bound_index: u8, + }, + Empty, + FormalParameter { + formal_parameter_index: u8, + }, + Throws { + throws_type_index: u16, + }, + LocalVar { + table_length: u16, + tables: Vec, + }, + Catch { + exception_table_index: u16, + }, + Offset { + offset: u16, + }, + TypeArgument { + offset: u16, + type_argument_index: u8, + }, +} + +#[derive(Clone, Debug)] +pub struct TypePath { + pub path_length: u8, + pub paths: Vec, +} + +#[derive(Clone, Debug)] +pub struct TypePathEntry { + pub type_path_kind: u8, + pub type_argument_index: u8, +} + +#[derive(Clone, Debug)] +pub struct LocalVarTableAnnotation { + pub start_pc: u16, + pub length: u16, + pub index: u16, +} + +#[derive(Clone, Debug)] +pub struct RuntimeAnnotation { + pub type_index: u16, + pub num_element_value_pairs: u16, + pub element_value_pairs: Vec, +} + +pub type DefaultAnnotation = ElementValue; + +#[derive(Clone, Debug)] +pub struct ElementValuePair { + pub element_name_index: u16, + pub value: ElementValue, +} + +#[derive(Clone, Debug)] +pub enum ElementValue { + // pub tag: u8, + ConstValueIndex { tag: char, value: u16 }, + EnumConst(EnumConstValue), + ClassInfoIndex(u16), + AnnotationValue(RuntimeAnnotation), + ElementArray(ElementArrayValue), +} + +#[derive(Clone, Debug)] +pub struct ElementArrayValue { + pub num_values: u16, + pub values: Vec, +} + +#[derive(Clone, Debug)] +pub struct EnumConstValue { + pub type_name_index: u16, + pub const_name_index: u16, +} + +#[derive(Clone, Debug)] +pub struct SourceDebugExtensionAttribute { + // Per the spec: + // The debug_extension array holds extended debugging information which has no + // semantic effect on the Java Virtual Machine. The information is represented + // using a modified UTF-8 string with no terminating zero byte. + pub debug_extension: Vec, +} + +#[derive(Clone, Debug)] +pub struct LineNumberTable { + pub line_number_table_length: u16, + pub line_number_table: Vec, +} + +#[derive(Clone, Debug)] +pub struct LineNumberTableEntry { + pub start_pc: u16, + pub line_number: u16, +} + #[derive(Clone, Debug)] pub enum VerificationTypeInfo { Top, diff --git a/src/code_attribute/mod.rs b/src/code_attribute/mod.rs index 2ab312f..cae52dd 100644 --- a/src/code_attribute/mod.rs +++ b/src/code_attribute/mod.rs @@ -6,3 +6,4 @@ pub use self::types::*; pub use self::parser::code_parser; pub use self::parser::instruction_parser; pub use self::parser::local_variable_table_parser; +pub use self::parser::local_variable_type_table_parser; diff --git a/src/code_attribute/parser.rs b/src/code_attribute/parser.rs index 3cdafcd..f706f8d 100644 --- a/src/code_attribute/parser.rs +++ b/src/code_attribute/parser.rs @@ -1,15 +1,18 @@ use crate::code_attribute::types::Instruction; use nom::{ + Err as BaseErr, IResult, Offset, bytes::complete::{tag, take}, combinator::{complete, fail, map, success}, error::Error, multi::{count, many0}, - number::complete::{be_i16, be_i32, be_i8, be_u16, be_u32, be_u8}, + number::complete::{be_i8, be_i16, be_i32, be_u8, be_u16, be_u32}, sequence::{pair, preceded, tuple}, - Err as BaseErr, IResult, Offset, }; -use super::{LocalVariableTableAttribute, LocalVariableTableItem}; +use super::{ + LocalVariableTableAttribute, LocalVariableTableItem, LocalVariableTypeTableAttribute, + LocalVariableTypeTableItem, +}; type Err = BaseErr>; fn offset<'a>(remaining: &'a [u8], input: &[u8]) -> IResult<&'a [u8], usize> { @@ -331,3 +334,40 @@ pub fn variable_table_item_parser( }, )) } + +pub fn local_variable_type_table_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTypeTableAttribute), Err<&[u8]>> { + let (input, local_variable_type_table_length) = be_u16(input)?; + let (input, local_variable_type_table) = count( + local_variable_type_table_item_parser, + local_variable_type_table_length as usize, + )(input)?; + Ok(( + input, + LocalVariableTypeTableAttribute { + local_variable_type_table_length, + local_variable_type_table, + }, + )) +} + +pub fn local_variable_type_table_item_parser( + input: &[u8], +) -> Result<(&[u8], LocalVariableTypeTableItem), Err<&[u8]>> { + let (input, start_pc) = be_u16(input)?; + let (input, length) = be_u16(input)?; + let (input, name_index) = be_u16(input)?; + let (input, signature_index) = be_u16(input)?; + let (input, index) = be_u16(input)?; + Ok(( + input, + LocalVariableTypeTableItem { + start_pc, + length, + name_index, + signature_index, + index, + }, + )) +} diff --git a/src/code_attribute/types.rs b/src/code_attribute/types.rs index 650ab06..5d336a5 100644 --- a/src/code_attribute/types.rs +++ b/src/code_attribute/types.rs @@ -249,3 +249,18 @@ pub struct LocalVariableTableItem { pub descriptor_index: u16, pub index: u16, } + +#[derive(Clone, Debug)] +pub struct LocalVariableTypeTableAttribute { + pub local_variable_type_table_length: u16, + pub local_variable_type_table: Vec, +} + +#[derive(Clone, Debug)] +pub struct LocalVariableTypeTableItem { + pub start_pc: u16, + pub length: u16, + pub name_index: u16, + pub signature_index: u16, + pub index: u16, +} diff --git a/src/constant_info/parser.rs b/src/constant_info/parser.rs index bf062dd..7cd196b 100644 --- a/src/constant_info/parser.rs +++ b/src/constant_info/parser.rs @@ -1,58 +1,57 @@ use crate::constant_info::*; use nom::{ + Err, bytes::complete::take, combinator::map, error::{Error, ErrorKind}, - number::complete::{be_f32, be_f64, be_i32, be_i64, be_u16, be_u8}, - Err, + number::complete::{be_f32, be_f64, be_i32, be_i64, be_u8, be_u16}, }; fn utf8_constant(input: &[u8]) -> Utf8Constant { let utf8_string = cesu8::from_java_cesu8(input).unwrap_or_else(|_| String::from_utf8_lossy(input)); Utf8Constant { - utf8_string: utf8_string.to_string(), - bytes: input.to_owned(), + utf8_string: utf8_string.to_string().into(), } } -fn const_utf8(input: &[u8]) -> ConstantInfoResult { +fn const_utf8(input: &[u8]) -> ConstantInfoResult<'_> { let (input, length) = be_u16(input)?; let (input, constant) = map(take(length), utf8_constant)(input)?; Ok((input, ConstantInfo::Utf8(constant))) } -fn const_integer(input: &[u8]) -> ConstantInfoResult { +fn const_integer(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_i32(input)?; Ok((input, ConstantInfo::Integer(IntegerConstant { value }))) } -fn const_float(input: &[u8]) -> ConstantInfoResult { +fn const_float(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_f32(input)?; Ok((input, ConstantInfo::Float(FloatConstant { value }))) } -fn const_long(input: &[u8]) -> ConstantInfoResult { +fn const_long(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_i64(input)?; Ok((input, ConstantInfo::Long(LongConstant { value }))) } -fn const_double(input: &[u8]) -> ConstantInfoResult { +fn const_double(input: &[u8]) -> ConstantInfoResult<'_> { let (input, value) = be_f64(input)?; Ok((input, ConstantInfo::Double(DoubleConstant { value }))) } -fn const_class(input: &[u8]) -> ConstantInfoResult { +fn const_class(input: &[u8]) -> ConstantInfoResult<'_> { let (input, name_index) = be_u16(input)?; Ok((input, ConstantInfo::Class(ClassConstant { name_index }))) } -fn const_string(input: &[u8]) -> ConstantInfoResult { +fn const_string(input: &[u8]) -> ConstantInfoResult<'_> { let (input, string_index) = be_u16(input)?; Ok((input, ConstantInfo::String(StringConstant { string_index }))) } -fn const_field_ref(input: &[u8]) -> ConstantInfoResult { +fn const_field_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -64,7 +63,7 @@ fn const_field_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_ref(input: &[u8]) -> ConstantInfoResult { +fn const_method_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -76,7 +75,7 @@ fn const_method_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult { +fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult<'_> { let (input, class_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -88,7 +87,7 @@ fn const_interface_method_ref(input: &[u8]) -> ConstantInfoResult { )) } -fn const_name_and_type(input: &[u8]) -> ConstantInfoResult { +fn const_name_and_type(input: &[u8]) -> ConstantInfoResult<'_> { let (input, name_index) = be_u16(input)?; let (input, descriptor_index) = be_u16(input)?; Ok(( @@ -100,7 +99,7 @@ fn const_name_and_type(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_handle(input: &[u8]) -> ConstantInfoResult { +fn const_method_handle(input: &[u8]) -> ConstantInfoResult<'_> { let (input, reference_kind) = be_u8(input)?; let (input, reference_index) = be_u16(input)?; Ok(( @@ -112,7 +111,7 @@ fn const_method_handle(input: &[u8]) -> ConstantInfoResult { )) } -fn const_method_type(input: &[u8]) -> ConstantInfoResult { +fn const_method_type(input: &[u8]) -> ConstantInfoResult<'_> { let (input, descriptor_index) = be_u16(input)?; Ok(( input, @@ -120,7 +119,7 @@ fn const_method_type(input: &[u8]) -> ConstantInfoResult { )) } -fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult { +fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult<'_> { let (input, bootstrap_method_attr_index) = be_u16(input)?; let (input, name_and_type_index) = be_u16(input)?; Ok(( @@ -135,7 +134,7 @@ fn const_invoke_dynamic(input: &[u8]) -> ConstantInfoResult { type ConstantInfoResult<'a> = Result<(&'a [u8], ConstantInfo), Err>>; type ConstantInfoVecResult<'a> = Result<(&'a [u8], Vec), Err>>; -fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult { +fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult<'_> { match const_type { 1 => const_utf8(input), 3 => const_integer(input), @@ -155,13 +154,13 @@ fn const_block_parser(input: &[u8], const_type: u8) -> ConstantInfoResult { } } -fn single_constant_parser(input: &[u8]) -> ConstantInfoResult { +fn single_constant_parser(input: &[u8]) -> ConstantInfoResult<'_> { let (input, const_type) = be_u8(input)?; let (input, const_block) = const_block_parser(input, const_type)?; Ok((input, const_block)) } -pub fn constant_parser(i: &[u8], const_pool_size: usize) -> ConstantInfoVecResult { +pub fn constant_parser(i: &[u8], const_pool_size: usize) -> ConstantInfoVecResult<'_> { let mut index = 0; let mut input = i; let mut res = Vec::with_capacity(const_pool_size); diff --git a/src/constant_info/types.rs b/src/constant_info/types.rs index fde08a3..f66612b 100644 --- a/src/constant_info/types.rs +++ b/src/constant_info/types.rs @@ -1,4 +1,7 @@ +use binrw::{NullWideString, binrw}; + #[derive(Clone, Debug)] +#[binrw] pub enum ConstantInfo { Utf8(Utf8Constant), Integer(IntegerConstant), @@ -18,77 +21,91 @@ pub enum ConstantInfo { } #[derive(Clone, Debug)] +#[binrw] pub struct Utf8Constant { - pub utf8_string: String, - pub bytes: Vec, + pub utf8_string: NullWideString, + // pub bytes: Vec, } #[derive(Clone, Debug)] +#[binrw] pub struct IntegerConstant { pub value: i32, } #[derive(Clone, Debug)] +#[binrw] pub struct FloatConstant { pub value: f32, } #[derive(Clone, Debug)] +#[binrw] pub struct LongConstant { pub value: i64, } #[derive(Clone, Debug)] +#[binrw] pub struct DoubleConstant { pub value: f64, } #[derive(Clone, Debug)] +#[binrw] pub struct ClassConstant { pub name_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct StringConstant { pub string_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct FieldRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct InterfaceMethodRefConstant { pub class_index: u16, pub name_and_type_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct NameAndTypeConstant { pub name_index: u16, pub descriptor_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodHandleConstant { pub reference_kind: u8, pub reference_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct MethodTypeConstant { pub descriptor_index: u16, } #[derive(Clone, Debug)] +#[binrw] pub struct InvokeDynamicConstant { pub bootstrap_method_attr_index: u16, pub name_and_type_index: u16, diff --git a/src/field_info/parser.rs b/src/field_info/parser.rs index 6eed08f..1a7a9dd 100644 --- a/src/field_info/parser.rs +++ b/src/field_info/parser.rs @@ -1,4 +1,4 @@ -use nom::{multi::count, number::complete::be_u16, IResult}; +use nom::{IResult, multi::count, number::complete::be_u16}; use crate::attribute_info::attribute_parser; diff --git a/src/field_info/types.rs b/src/field_info/types.rs index ca4e3e2..91cf144 100644 --- a/src/field_info/types.rs +++ b/src/field_info/types.rs @@ -1,17 +1,24 @@ use crate::attribute_info::AttributeInfo; +use binrw::binrw; #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct FieldInfo { pub access_flags: FieldAccessFlags, pub name_index: u16, pub descriptor_index: u16, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct FieldAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct FieldAccessFlags: u16 { + impl FieldAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const PRIVATE = 0x0002; // Declared private; usable only within the defining class. const PROTECTED = 0x0004; // Declared protected; may be accessed within subclasses. diff --git a/src/lib.rs b/src/lib.rs index 26a7f23..f0ad88e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! A parser for [Java Classfiles](https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-4.html) use std::fs::File; -use std::io::{prelude::*, BufReader}; +use std::io::{BufReader, prelude::*}; use std::path::Path; #[macro_use] @@ -46,34 +46,38 @@ pub fn parse_class(class_name: &str) -> Result { }; let mut reader = BufReader::new(file); - parse_class_from_reader(&mut reader, display.to_string()) + parse_class_from_reader(&mut reader) } /// Attempt to parse a class file given a reader that implements the std::io::Read trait. -/// The file_path parameter is only used in case of errors to provide reasonable error -/// messages. +/// Parameters shouldn't be passed for the sole purpose of debug output, this should be +/// abstracted instead. +/// OLD: The file_path parameter is only used in case of errors to provide +/// reasonable error messages. /// /// ```rust /// let mut reader = "this_will_be_parsed_as_classfile".as_bytes(); -/// let result = classfile_parser::parse_class_from_reader(&mut reader, "path/to/Java.class".to_string()); +/// let result = classfile_parser::parse_class_from_reader(&mut reader); /// assert!(result.is_err()); /// ``` -pub fn parse_class_from_reader( - reader: &mut T, - file_path: String, -) -> Result { +pub fn parse_class_from_reader(reader: &mut T) -> Result { let mut class_bytes = Vec::new(); - if let Err(why) = reader.read_to_end(&mut class_bytes) { - return Err(format!( - "Unable to read {}: {}", - file_path, - &why.to_string() - )); - } + reader + .read_to_end(&mut class_bytes) + .expect("cannot continue, read_to_end failed"); let parsed_class = class_parser(&class_bytes); match parsed_class { - Ok((_, c)) => Ok(c), - _ => Err(format!("Failed to parse classfile {}", file_path)), + Ok((a, c)) => { + if !a.is_empty() { + eprintln!( + "Warning: not all bytes were consumed when parsing classfile, {} bytes remaining", + a.len() + ); + } + + Ok(c) + } + Err(e) => Err(format!("Failed to parse classfile: {}", e)), } } diff --git a/src/method_info/parser.rs b/src/method_info/parser.rs index 01b023f..0f66fd0 100644 --- a/src/method_info/parser.rs +++ b/src/method_info/parser.rs @@ -1,4 +1,4 @@ -use nom::{multi::count, number::complete::be_u16, IResult}; +use nom::{IResult, multi::count, number::complete::be_u16}; use crate::attribute_info::attribute_parser; diff --git a/src/method_info/types.rs b/src/method_info/types.rs index 4d316ba..411950c 100644 --- a/src/method_info/types.rs +++ b/src/method_info/types.rs @@ -1,17 +1,25 @@ use crate::attribute_info::AttributeInfo; +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big)] pub struct MethodInfo { pub access_flags: MethodAccessFlags, pub name_index: u16, pub descriptor_index: u16, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct MethodAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct MethodAccessFlags: u16 { + impl MethodAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const PRIVATE = 0x0002; // Declared private; accessible only within the defining class. const PROTECTED = 0x0004; // Declared protected; may be accessed within subclasses. diff --git a/src/types.rs b/src/types.rs index b1bd1bd..8616790 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,28 +3,40 @@ use crate::constant_info::ConstantInfo; use crate::field_info::FieldInfo; use crate::method_info::MethodInfo; +use binrw::binrw; + #[derive(Clone, Debug)] +#[binrw] +#[brw(big, magic = b"\xca\xfe\xba\xbe")] pub struct ClassFile { pub minor_version: u16, pub major_version: u16, pub const_pool_size: u16, + #[br(args { count: const_pool_size.into() })] pub const_pool: Vec, pub access_flags: ClassAccessFlags, pub this_class: u16, pub super_class: u16, pub interfaces_count: u16, + #[br(args { count: interfaces_count.into() })] pub interfaces: Vec, pub fields_count: u16, + #[br(args { count: fields_count.into() })] pub fields: Vec, pub methods_count: u16, + #[br(args { count: methods_count.into() })] pub methods: Vec, pub attributes_count: u16, + #[br(args { count: attributes_count.into() })] pub attributes: Vec, } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[binrw] +pub struct ClassAccessFlags(u16); + bitflags! { - #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] - pub struct ClassAccessFlags: u16 { + impl ClassAccessFlags: u16 { const PUBLIC = 0x0001; // Declared public; may be accessed from outside its package. const FINAL = 0x0010; // Declared final; no subclasses allowed. const SUPER = 0x0020; // Treat superclass methods specially when invoked by the invokespecial instruction. @@ -33,15 +45,6 @@ bitflags! { const SYNTHETIC = 0x1000; // Declared synthetic; not present in the source code. const ANNOTATION = 0x2000; // Declared as an annotation type. const ENUM = 0x4000; // Declared as an enum type. + const MODULE = 0x8000; // Declared as a module type. } } - -#[cfg(test)] -#[allow(dead_code)] -trait TraitTester: - Copy + Clone + PartialEq + Eq + PartialOrd + Ord + ::std::hash::Hash + ::std::fmt::Debug -{ -} - -#[cfg(test)] -impl TraitTester for ClassAccessFlags {} diff --git a/tests/attr_bootstrap_methods.rs b/tests/attr_bootstrap_methods.rs index 11c5bfe..a419e9f 100644 --- a/tests/attr_bootstrap_methods.rs +++ b/tests/attr_bootstrap_methods.rs @@ -11,26 +11,32 @@ fn test_attribute_bootstrap_methods() { "../java-assets/compiled-classes/BootstrapMethods.class" )) { Ok((_, c)) => { - println!("Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", c.major_version, c.minor_version, c.const_pool_size, c.this_class, c.super_class, c.interfaces_count, c.fields_count, c.methods_count, c.attributes_count, c.access_flags); + println!( + "Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", + c.major_version, + c.minor_version, + c.const_pool_size, + c.this_class, + c.super_class, + c.interfaces_count, + c.fields_count, + c.methods_count, + c.attributes_count, + c.access_flags + ); let mut bootstrap_method_const_index = 0; println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "BootstrapMethods" { - if bootstrap_method_const_index != 0 { - assert!( - false, - "Should not find more than one BootstrapMethods constant" - ); - } - bootstrap_method_const_index = (const_index + 1) as u16; - } + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "BootstrapMethods" + { + if bootstrap_method_const_index != 0 { + panic!("Should not find more than one BootstrapMethods constant"); } - _ => {} + bootstrap_method_const_index = (const_index + 1) as u16; } } assert_ne!(bootstrap_method_const_index, 0); @@ -40,7 +46,7 @@ fn test_attribute_bootstrap_methods() { bootstrap_method_const_index ); - for (_, attribute_item) in c.attributes.iter().enumerate() { + for attribute_item in c.attributes.iter() { if attribute_item.attribute_name_index == bootstrap_method_const_index { match bootstrap_methods_attribute_parser(&attribute_item.info) { Ok((_, bsma)) => { @@ -54,7 +60,7 @@ fn test_attribute_bootstrap_methods() { } } - assert!(false, "Should not get to here"); + panic!("Should not get to here"); } _ => panic!("Not a valid class file"), } @@ -66,14 +72,13 @@ fn should_have_no_bootstrap_method_attr_if_no_invoke_dynamic() { "../java-assets/compiled-classes/BasicClass.class" )) { Ok((_, c)) => { - for (_, const_item) in c.const_pool.iter().enumerate() { - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "BootstrapMethods" { - assert!(false, "Should not have found a BootstrapMethods constant in a class not requiring it") - } - } - _ => {} + for const_item in c.const_pool.iter() { + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "BootstrapMethods" + { + panic!( + "Should not have found a BootstrapMethods constant in a class not requiring it" + ) } } } diff --git a/tests/attr_stack_map_table.rs b/tests/attr_stack_map_table.rs index a6bd130..6fb776d 100644 --- a/tests/attr_stack_map_table.rs +++ b/tests/attr_stack_map_table.rs @@ -18,19 +18,13 @@ fn test_attribute_stack_map_table() { println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "StackMapTable" { - if stack_map_table_index != 0 { - assert!( - false, - "Should not find more than one StackMapTable constant" - ); - } - stack_map_table_index = (const_index + 1) as u16; - } + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "StackMapTable" + { + if stack_map_table_index != 0 { + panic!("Should not find more than one StackMapTable constant"); } - _ => {} + stack_map_table_index = (const_index + 1) as u16; } } println!("Methods:"); @@ -54,16 +48,13 @@ fn test_attribute_stack_map_table() { println!("Code Attrs:"); for (idx, code_attr) in code.attributes.iter().enumerate() { println!("\t[{}] = {:?}", idx, code_attr); - match *code_attr { - AttributeInfo { - ref attribute_name_index, - attribute_length: _, - info: _, - } => { - if attribute_name_index == &stack_map_table_index { - stack_map_table_attr_index = idx; - } - } + let AttributeInfo { + ref attribute_name_index, + attribute_length: _, + info: _, + } = *code_attr; + if attribute_name_index == &stack_map_table_index { + stack_map_table_attr_index = idx; } } diff --git a/tests/classfile.rs b/tests/classfile.rs index a807a85..e63b94f 100644 --- a/tests/classfile.rs +++ b/tests/classfile.rs @@ -10,39 +10,44 @@ fn test_valid_class() { let res = class_parser(valid_class); match res { Result::Ok((_, c)) => { - println!("Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", c.major_version, c.minor_version, c.const_pool_size, c.this_class, c.super_class, c.interfaces_count, c.fields_count, c.methods_count, c.attributes_count, c.access_flags); + println!( + "Valid class file, version {},{} const_pool({}), this=const[{}], super=const[{}], interfaces({}), fields({}), methods({}), attributes({}), access({:?})", + c.major_version, + c.minor_version, + c.const_pool_size, + c.this_class, + c.super_class, + c.interfaces_count, + c.fields_count, + c.methods_count, + c.attributes_count, + c.access_flags + ); let mut code_const_index = 0; println!("Constant pool:"); for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "Code" { - code_const_index = (const_index + 1) as u16; - } - } - _ => {} + if let ConstantInfo::Utf8(ref c) = *const_item + && c.utf8_string.to_string() == "Code" + { + code_const_index = (const_index + 1) as u16; } } println!("Code index = {}", code_const_index); println!("Interfaces:"); - let mut interface_index = 0; - for i in &c.interfaces { + for (interface_index, i) in c.interfaces.iter().enumerate() { println!( "\t[{}] = const[{}] = {:?}", interface_index, i, c.const_pool[(i - 1) as usize] ); - - interface_index += 1; } println!("Fields:"); - let mut field_index = 0; - for f in &c.fields { + for (field_index, f) in c.fields.iter().enumerate() { println!( "\t[{}] Name(const[{}] = {:?}) - access({:?})", field_index, @@ -50,11 +55,9 @@ fn test_valid_class() { c.const_pool[(f.name_index - 1) as usize], f.access_flags ); - field_index += 1; } println!("Methods:"); - let mut method_index = 0; - for m in &c.methods { + for (method_index, m) in c.methods.iter().enumerate() { println!( "\t[{}] Name(const[{}] = {:?}) - access({:?})", method_index, @@ -62,7 +65,6 @@ fn test_valid_class() { c.const_pool[(m.name_index - 1) as usize], m.access_flags ); - method_index += 1; for a in &m.attributes { if a.attribute_name_index == code_const_index { @@ -98,28 +100,26 @@ fn test_utf_string_constants() { let mut found_utf_unpaired_string = false; for (const_index, const_item) in c.const_pool.iter().enumerate() { println!("\t[{}] = {:?}", (const_index + 1), const_item); - match *const_item { - ConstantInfo::Utf8(ref c) => { - if c.utf8_string == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" { - found_utf_maths_string = true; - } - if c.utf8_string - == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" - { - found_utf_runes_string = true; - } - if c.utf8_string == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" - { - found_utf_braille_string = true; - } - if c.utf8_string == "\0𠜎" { - found_utf_modified_string = true; - } - if c.utf8_string == "X���X" && c.bytes.len() == 5 { - found_utf_unpaired_string = true; - } + if let ConstantInfo::Utf8(ref c) = *const_item { + if c.utf8_string.to_string() == "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm" + { + found_utf_maths_string = true; + } + if c.utf8_string.to_string() + == "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + { + found_utf_runes_string = true; + } + if c.utf8_string.to_string() == "⡌⠁⠧⠑ ⠼⠁⠒ ⡍⠜⠇⠑⠹⠰⠎ ⡣⠕⠌" + { + found_utf_braille_string = true; + } + if c.utf8_string.to_string() == "\0𠜎" { + found_utf_modified_string = true; + } + if c.utf8_string.to_string() == "X���X" && c.utf8_string.len() == 5 { + found_utf_unpaired_string = true; } - _ => {} } } @@ -140,9 +140,8 @@ fn test_utf_string_constants() { fn test_malformed_class() { let malformed_class = include_bytes!("../java-assets/compiled-classes/malformed.class"); let res = class_parser(malformed_class); - match res { - Result::Ok((_, _)) => panic!("The file is not valid and shouldn't be parsed"), - _ => {} + if let Result::Ok((_, _)) = res { + panic!("The file is not valid and shouldn't be parsed") }; } diff --git a/tests/code_attribute.rs b/tests/code_attribute.rs index 43bbfa1..c492465 100644 --- a/tests/code_attribute.rs +++ b/tests/code_attribute.rs @@ -1,10 +1,27 @@ +//only works for nightly builds at the moment +//#![feature(assert_matches)] + extern crate classfile_parser; -use classfile_parser::attribute_info::{code_attribute_parser, method_parameters_attribute_parser}; +//use std::assert_matches::assert_matches; + +use classfile_parser::attribute_info::{ + DefaultAnnotation, ElementValue, InnerClassAccessFlags, TargetInfo, code_attribute_parser, + element_value_parser, enclosing_method_attribute_parser, inner_classes_attribute_parser, + line_number_table_attribute_parser, method_parameters_attribute_parser, + runtime_invisible_annotations_attribute_parser, + runtime_invisible_parameter_annotations_attribute_parser, + runtime_visible_annotations_attribute_parser, + runtime_visible_parameter_annotations_attribute_parser, + runtime_visible_type_annotations_attribute_parser, signature_attribute_parser, + source_debug_extension_parser, +}; use classfile_parser::class_parser; use classfile_parser::code_attribute::{ - code_parser, instruction_parser, Instruction, LocalVariableTableAttribute, + Instruction, LocalVariableTableAttribute, code_parser, instruction_parser, + local_variable_type_table_parser, }; +use classfile_parser::constant_info::{ConstantInfo, Utf8Constant}; use classfile_parser::method_info::MethodAccessFlags; #[test] @@ -85,7 +102,9 @@ fn test_class() { fn lookup_string(c: &classfile_parser::ClassFile, index: u16) -> Option { let con = &c.const_pool[(index - 1) as usize]; match con { - classfile_parser::constant_info::ConstantInfo::Utf8(utf8) => Some(utf8.utf8_string.clone()), + classfile_parser::constant_info::ConstantInfo::Utf8(utf8) => { + Some(utf8.utf8_string.to_string()) + } _ => None, } } @@ -105,7 +124,7 @@ fn method_parameters() { assert_eq!( lookup_string( &class, - method_parameters.parameters.get(0).unwrap().name_index + method_parameters.parameters.first().unwrap().name_index ), Some("a".to_string()) ); @@ -118,6 +137,209 @@ fn method_parameters() { ); } +#[test] +fn inner_classes() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + for attr in &class.attributes { + match lookup_string(&class, attr.attribute_name_index) { + Some(x) if x == "InnerClasses" => { + let (_, inner_class_attrs) = inner_classes_attribute_parser(&attr.info).unwrap(); + + assert_eq!(inner_class_attrs.number_of_classes, 4); + + assert_eq!( + inner_class_attrs.number_of_classes, + inner_class_attrs.classes.len() as u16 + ); + + for c in inner_class_attrs.classes { + dbg!(&class.const_pool[(c.inner_class_info_index - 1) as usize]); + + // only == 0 when this class is a top-level class or interface, or when it's + // a local class or an anonymous class. + if c.outer_class_info_index != 0 { + assert_ne!(c.inner_class_info_index, c.outer_class_info_index); + + dbg!(&class.const_pool[(c.outer_class_info_index - 1) as usize]); + } + + // only == 0 when this class is anonymous + if c.inner_name_index != 0 { + dbg!(&class.const_pool[(c.inner_name_index - 1) as usize]); + } + + dbg!(InnerClassAccessFlags::from_bits_truncate( + c.inner_class_access_flags + )); + } + //uncomment to see dbg output from above + //assert!(false); + } + Some(_) => {} + None => panic!( + "Could not find attribute name for index {}", + attr.attribute_name_index + ), + } + } +} + +#[test] +// test for enclosing method attribute, which only applies to local and anonymous classes +fn enclosing_method() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses$2.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + for attr in &class.attributes { + match lookup_string(&class, attr.attribute_name_index) { + Some(x) if x == "EnclosingMethod" => { + assert_eq!(attr.attribute_length, 4); + + let (_, inner_class_attrs) = enclosing_method_attribute_parser(&attr.info).unwrap(); + + match &class.const_pool[(inner_class_attrs.class_index - 1) as usize] { + classfile_parser::constant_info::ConstantInfo::Class(class_constant) => { + /* nightly only rn + * use regular asserts + decomposition instead + let _expected = String::from("InnerClasses"); + assert_matches!( + &class.const_pool[(class_constant.name_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + }) + ); */ + if let ConstantInfo::Utf8(inner_str) = + &class.const_pool[(class_constant.name_index - 1) as usize] + { + assert_eq!( + inner_str.utf8_string, + binrw::NullWideString::from("InnerClasses") + ); + } + + dbg!(&class.const_pool[(class_constant.name_index - 1) as usize]); + } + _ => panic!("Expected Class constant"), + } + + match &class.const_pool[(inner_class_attrs.method_index - 1) as usize] { + classfile_parser::constant_info::ConstantInfo::NameAndType( + name_and_type_constant, + ) => { + /* + let mut _expected = String::from("sayHello"); + assert_matches!( + &class.const_pool[(name_and_type_constant.name_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + }) + ); + */ + if let ConstantInfo::Utf8(inner_str) = + &class.const_pool[(name_and_type_constant.name_index - 1) as usize] + { + assert_eq!( + inner_str.utf8_string, + binrw::NullWideString::from("sayHello") + ); + } + dbg!(&class.const_pool[(name_and_type_constant.name_index - 1) as usize]); + + /* + _expected = String::from("()V"); + assert_matches!( + &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize], + ConstantInfo::Utf8(classfile_parser::constant_info::Utf8Constant { + utf8_string: _expected, + }) + ); + */ + if let ConstantInfo::Utf8(inner_str) = &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize] + { + assert_eq!(inner_str.utf8_string, binrw::NullWideString::from("()V")); + } + dbg!( + &class.const_pool + [(name_and_type_constant.descriptor_index - 1) as usize] + ); + } + _ => panic!("Expected NameAndType constant"), + } + + //uncomment to see dbg output from above + //assert!(false); + } + Some(_) => {} + None => panic!( + "Could not find attribute name for index {}", + attr.attribute_name_index + ), + } + } +} + +#[test] +fn synthetic_attribute() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/InnerClasses$2.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let synthetic_attrs = class + .attributes + .iter() + .filter( + |attribute_info| match lookup_string(&class, attribute_info.attribute_name_index) { + Some(s) if s == "Synethic" => true, + Some(_) => false, + None => panic!( + "Could not find attribute name for index {}", + attribute_info.attribute_name_index + ), + }, + ) + .collect::>(); + + for attr in &synthetic_attrs { + assert_eq!(attr.attribute_length, 0); + } +} + +//works on both method attributes and ClassFile attributes +#[test] +fn signature_attribute() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/BootstrapMethods.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let signature_attrs = class + .methods + .iter() + .flat_map(|method_info| &method_info.attributes) + .filter( + |attribute_info| match lookup_string(&class, attribute_info.attribute_name_index) { + Some(s) if s == "Signature" => { + eprintln!("Got a signature attr!"); + true + } + Some(_) => false, + None => panic!( + "Could not find attribute name for index {}", + attribute_info.attribute_name_index + ), + }, + ) + .collect::>(); + + for attr in &signature_attrs { + let (_, signature_attr) = signature_attribute_parser(&attr.info).unwrap(); + let signature_string = lookup_string(&class, signature_attr.signature_index).unwrap(); + dbg!(signature_string); + } + + //uncomment to see dbg output from above + //assert!(false); +} + #[test] fn local_variable_table() { // The class was not compiled with "javac -g" @@ -174,6 +396,318 @@ fn local_variable_table() { ); } +#[test] +fn runtime_visible_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_annotations_attribute.len(), 1); + let f = runtime_visible_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = RuntimeVisibleTypeAnnotationsAttribute { + num_annotations: 1, + annotations: vec![RuntimeAnnotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![ElementValuePair { + element_name_index: 31, + value: ElementValue::ConstValueIndex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 46); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 47); + } + _ => panic!("Expected ConstValueIndex"), + } +} + +#[test] +fn runtime_invisible_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleAnnotations")) + .collect::>(); + + assert_eq!(runtime_invisible_annotations_attribute.len(), 1); + let f = runtime_invisible_annotations_attribute.first().unwrap(); + + let invisible_annotations = runtime_invisible_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + /* + let should_be = RuntimeVisibleTypeAnnotationsAttribute { + num_annotations: 1, + annotations: vec![RuntimeAnnotation { + type_index: 30, + num_element_value_pairs: 1, + element_value_pairs: vec![ElementValuePair { + element_name_index: 31, + value: ElementValue::ConstValueIndex { + tag: 's', + value: 32, + }, + }], + }], + }; + */ + + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.annotations.len(), 1); + assert_eq!(inner.1.annotations[0].type_index, 49); + assert_eq!(inner.1.annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + + match inner.1.annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 50); + } + _ => panic!("Expected ConstValueIndex"), + } +} + +#[test] +fn runtime_visible_parameter_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleParameterAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_annotations_attribute.len(), 1); + let f = runtime_visible_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_parameter_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + assert_eq!(inner.1.num_parameters, 2); + assert_eq!(inner.1.parameter_annotations.len(), 2); + assert_eq!(inner.1.parameter_annotations[0].num_annotations, 1); + assert_eq!(inner.1.parameter_annotations[0].annotations.len(), 1); + + match inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 53); + } + _ => panic!( + "expected ConstValueIndex, got {:?}", + inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value + ), + } +} + +#[test] +fn runtime_invisible_parameter_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_annotations_attribute = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleParameterAnnotations")) + .collect::>(); + + assert_eq!(runtime_invisible_annotations_attribute.len(), 1); + let f = runtime_invisible_annotations_attribute.first().unwrap(); + + let invisible_annotations = runtime_invisible_parameter_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert!(&inner.0.is_empty()); + + assert_eq!(inner.1.num_parameters, 2); + assert_eq!(inner.1.parameter_annotations.len(), 2); + assert_eq!(inner.1.parameter_annotations[1].num_annotations, 1); + assert_eq!(inner.1.parameter_annotations[1].annotations.len(), 1); + + match inner.1.parameter_annotations[1].annotations[0].element_value_pairs[0].value { + ElementValue::ConstValueIndex { tag, value } => { + assert_eq!(tag, 's'); + assert_eq!(value, 50); + } + _ => panic!( + "expected ConstValueIndex, got {:?}", + inner.1.parameter_annotations[0].annotations[0].element_value_pairs[0].value + ), + } +} + +#[test] +fn runtime_visible_type_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_visible_type_annotations_attribute = class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeVisibleTypeAnnotations")) + .collect::>(); + + assert_eq!(runtime_visible_type_annotations_attribute.len(), 1); + let f = runtime_visible_type_annotations_attribute.first().unwrap(); + + let visible_annotations = runtime_visible_type_annotations_attribute_parser(&f.info); + let inner = &visible_annotations.unwrap(); + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.type_annotations.len(), 1); + assert_eq!(inner.1.type_annotations[0].target_type, 19); + //assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); + assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); + assert_eq!(inner.1.type_annotations[0].type_index, 36); + assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.type_annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + /* + assert_matches!( + inner.1.type_annotations[0].element_value_pairs[0].value, + ElementValue::ConstValueIndex { + tag: 's', + value: 38 + } + ); + */ +} + +#[test] +fn runtime_invisible_type_annotations() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Annotations.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let runtime_invisible_type_annotations_attribute = class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "RuntimeInvisibleTypeAnnotations")) + .collect::>(); + assert_eq!(runtime_invisible_type_annotations_attribute.len(), 1); + let f = runtime_invisible_type_annotations_attribute + .first() + .unwrap(); + + let invisible_annotations = runtime_visible_type_annotations_attribute_parser(&f.info); + let inner = &invisible_annotations.unwrap(); + assert_eq!(inner.1.num_annotations, 1); + assert_eq!(inner.1.type_annotations.len(), 1); + assert_eq!(inner.1.type_annotations[0].target_type, 19); + //assert_matches!(inner.1.type_annotations[0].target_info, TargetInfo::Empty); + assert_eq!(inner.1.type_annotations[0].target_path.path_length, 0); + assert_eq!(inner.1.type_annotations[0].target_path.paths.len(), 0); + assert_eq!(inner.1.type_annotations[0].type_index, 41); + assert_eq!(inner.1.type_annotations[0].num_element_value_pairs, 1); + assert_eq!(inner.1.type_annotations[0].element_value_pairs.len(), 1); + assert_eq!( + inner.1.type_annotations[0].element_value_pairs[0].element_name_index, + 37 + ); + /* + assert_matches!( + inner.1.type_annotations[0].element_value_pairs[0].value, + ElementValue::ConstValueIndex { + tag: 's', + value: 42 + } + ); + */ +} + +#[test] +fn default_annotation_value() { + let class_bytes = + include_bytes!("../java-assets/compiled-classes/Annotations$VisibleAtRuntime.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let default_annotation_attributes = class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "AnnotationDefault")) + .collect::>(); + assert_eq!(default_annotation_attributes.len(), 1); + let f = default_annotation_attributes.first().unwrap(); + + let default_annotation = element_value_parser(&f.info); + let inner: DefaultAnnotation = default_annotation.unwrap().1 as DefaultAnnotation; + /* + assert_matches!( + inner, + ElementValue::ConstValueIndex { + tag: 's', + value: 10 + } + ); + */ + if let ElementValue::ConstValueIndex { tag, value } = inner { + assert_eq!(tag, 's'); + assert_eq!(value, 10); + } +} + +// SourceDebugExtension attributes appear to be custom/non-standard. While it would +// be nice to parse, ultimately the spec defines the attribute as a byte array that +// contains "extended debugging information which has no semantic effect on the Java +// Virtual Machine", so I will leave this test to be better developed when example +// use cases are found. +// #[test] +fn source_debug_extension() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let source_debug_extension_attribute = class + .attributes + .iter() + .filter(|attribute_info| matches!(lookup_string(&class, attribute_info.attribute_name_index), Some(s) if s == "SourceDebugExtension")) + .collect::>(); + assert_eq!(source_debug_extension_attribute.len(), 1); + let f = source_debug_extension_attribute.first().unwrap(); + + let default_annotation = source_debug_extension_parser(&f.info); + let inner = &default_annotation.unwrap(); + dbg!(inner); +} + #[test] fn source_file() { let class_bytes = include_bytes!("../java-assets/compiled-classes/BasicClass.class"); @@ -201,3 +735,132 @@ fn source_file() { assert_eq!(s, "BasicClass.java"); } + +#[test] +fn line_number_table() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/Instructions.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let default_annotation_attributes = class + .methods + .iter() + .find(|m| m.access_flags.contains(MethodAccessFlags::STATIC)) + .unwrap(); + + let (_, code_attribute) = + code_attribute_parser(&default_annotation_attributes.attributes[0].info).unwrap(); + + assert_eq!( + code_attribute.attributes.len(), + code_attribute.attributes_count as usize + ); + + let line_number_tables = &code_attribute + .attributes + .iter() + .filter(|a| lookup_string(&class, a.attribute_name_index).unwrap() == "LineNumberTable") + .map(|a| line_number_table_attribute_parser(&a.info).unwrap().1) + .collect::>(); + + assert_eq!(line_number_tables.len(), 1); + assert_eq!(line_number_tables[0].line_number_table_length, 12); + assert_eq!(line_number_tables[0].line_number_table.len(), 12); + assert_eq!(line_number_tables[0].line_number_table[0].start_pc, 0); + assert_eq!(line_number_tables[0].line_number_table[0].line_number, 3); +} + +#[test] +fn local_variable_type_table() { + // The class was not compiled with "javac -g" + let class_bytes = include_bytes!("../java-assets/compiled-classes/LocalVariableTable.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + let method_info = &class.methods.iter().last().unwrap(); + + let local_variable_table_type_attribute = method_info + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "Code" => code_attribute_parser(&attribute_info.info).ok(), + _ => None, + } + }) + .map(|i| i.1) + .unwrap() + .attributes + .iter() + .find_map(|attribute_info| { + match lookup_string(&class, attribute_info.attribute_name_index)?.as_str() { + "LocalVariableTypeTable" => { + local_variable_type_table_parser(&attribute_info.info).ok() + } + _ => None, + } + }) + .map(|a| a.1) + .unwrap(); + + let types: Vec = local_variable_table_type_attribute + .local_variable_type_table + .iter() + .filter_map(|i| lookup_string(&class, i.signature_index)) + .collect(); + + // All used types in method code block of last method + assert_eq!( + types, + vec!["Ljava/util/HashMap;"] + ); +} + +#[test] +fn deprecated() { + let class_bytes = include_bytes!("../java-assets/compiled-classes/DeprecatedAnnotation.class"); + let (_, class) = class_parser(class_bytes).unwrap(); + + let deprecated_class_attribute = &class + .attributes + .iter() + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_class_attribute.len(), 1); + + let deprecated_method_attribute = &class + .methods + .iter() + .flat_map(|m| &m.attributes) + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_method_attribute.len(), 1); + + let deprecated_field_attribute = &class + .fields + .iter() + .flat_map(|f| &f.attributes) + .filter(|attribute_info| { + matches!( + lookup_string(&class, attribute_info.attribute_name_index) + .unwrap() + .as_str(), + "Deprecated" + ) + }) + .collect::>(); + + assert_eq!(deprecated_field_attribute.len(), 1); +}