mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-05 20:47:45 +01:00
eab6a0f01b
Parser follow-ups to #722: Java reference searches keep earlier hits and report partial coverage, DEX signed LEB128 takes at most five bytes, legacy AAPT long strings recover within a shared budget, and ext indirect metadata is read within bounds.
77 lines
3.0 KiB
Rust
77 lines
3.0 KiB
Rust
use craft_anything_formats::java_class;
|
|
use craft_anything_formats::java_ops::{decode, decode_one};
|
|
|
|
#[test]
|
|
fn invokeinterface_requires_a_count_and_zero_reserved_byte() {
|
|
for code in [[0xb9, 0, 1, 0, 0], [0xb9, 0, 1, 1, 1], [0xb9, 0, 1, 255, 255]] {
|
|
assert!(decode_one(&code, 0).is_none(), "{code:?}");
|
|
assert!(!decode(&code, 16).1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invokedynamic_requires_both_reserved_bytes_to_be_zero() {
|
|
for code in [[0xba, 0, 1, 1, 0], [0xba, 0, 1, 0, 1], [0xba, 0, 1, 255, 255]] {
|
|
assert!(decode_one(&code, 0).is_none(), "{code:?}");
|
|
assert!(!decode(&code, 16).1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn valid_and_truncated_invocations_keep_their_boundaries() {
|
|
for count in [1, 2, 255] {
|
|
let instruction = decode_one(&[0xb9, 0x12, 0x34, count, 0], 0).unwrap();
|
|
assert_eq!(instruction.cp_index, Some(0x1234));
|
|
assert_eq!(instruction.len, 5);
|
|
}
|
|
let instruction = decode_one(&[0xba, 0x12, 0x34, 0, 0], 0).unwrap();
|
|
assert_eq!(instruction.cp_index, Some(0x1234));
|
|
for code in [[0xb9, 0, 1, 1, 0], [0xba, 0, 1, 0, 0]] {
|
|
for len in 1..5 {
|
|
assert!(decode_one(&code[..len], 0).is_none());
|
|
}
|
|
}
|
|
let (instructions, complete) = decode(&[0xb9, 0, 1, 1, 0, 0xba, 0, 2, 0, 0, 0xb1], 16);
|
|
assert!(complete);
|
|
assert_eq!(instructions.len(), 3);
|
|
assert_eq!(instructions[2].pc, 10);
|
|
}
|
|
|
|
fn reference_class() -> java_class::ClassFile {
|
|
let mut class = java_class::parse(&craft_anything_testkit::java::hello_class()).unwrap();
|
|
class.methods.retain(|method| method.name == "run");
|
|
class
|
|
}
|
|
|
|
#[test]
|
|
fn malformed_invocations_mark_reference_scans_incomplete() {
|
|
let valid = reference_class();
|
|
let (expected, incomplete) = java_class::references(&valid, "java/io/PrintStream", None, 10);
|
|
assert!(!incomplete);
|
|
assert_eq!(expected.len(), 1);
|
|
for invalid in [[0xb9, 0, 1, 0, 0], [0xb9, 0, 1, 1, 1], [0xba, 0, 1, 1, 0], [0xba, 0, 1, 0, 1]] {
|
|
let mut class = valid.clone();
|
|
let code = &mut class.methods[0].code.as_mut().unwrap().bytecode;
|
|
let later_reference = code.clone();
|
|
*code = invalid.to_vec();
|
|
code.extend_from_slice(&later_reference);
|
|
let (found, incomplete) = java_class::references(&class, "java/io/PrintStream", None, 10);
|
|
assert!(found.is_empty());
|
|
assert!(incomplete, "invalid invocation must not hide an unfinished reference scan: {invalid:?}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn incomplete_scans_keep_earlier_references_and_scan_other_methods() {
|
|
let mut class = reference_class();
|
|
let mut later_method = class.methods[0].clone();
|
|
later_method.name = "later".into();
|
|
class.methods[0].code.as_mut().unwrap().bytecode.extend_from_slice(&[0xba, 0, 1, 1, 0]);
|
|
class.methods.push(later_method);
|
|
let (found, incomplete) = java_class::references(&class, "java/io/PrintStream", None, 10);
|
|
assert!(incomplete);
|
|
assert_eq!(found.len(), 2);
|
|
assert!(found[0].caller.contains(".run:("));
|
|
assert!(found[1].caller.contains(".later:("));
|
|
}
|