From 1ddc4afbfcfffabe56a5d031657a8298d7bb76dd Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Thu, 15 Oct 2026 12:53:22 +0200 Subject: [PATCH] examples: add frame_repro - recompute-frames repro over an untouched jar --- crates/noxis/examples/frame_repro.rs | 93 ++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 crates/noxis/examples/frame_repro.rs diff --git a/crates/noxis/examples/frame_repro.rs b/crates/noxis/examples/frame_repro.rs new file mode 100644 index 0000000..4999030 --- /dev/null +++ b/crates/noxis/examples/frame_repro.rs @@ -0,0 +1,93 @@ +//! Repro for the predecessor frame bug (todo.md ยง3.4): recompute frames on +//! every method of an UNTOUCHED jar (no pass runs), write, verify with real +//! `java -Xverify:all`, report which classes fail and why. +//! +//! ```text +//! cargo run --release --example frame_repro -- --jar [--out ] +//! ``` +#![allow(clippy::unwrap_used, clippy::expect_used)] + +use std::path::PathBuf; + +use noxis_analysis::{ClassHierarchy, JdkHierarchy}; +use noxis_classfile::{compute_frames, format}; +use noxis_jar::{load, read_file, save, write_file}; +use noxis_verify::verify_jar; + +fn main() { + let jar = std::env::args() + .skip_while(|a| a != "--jar") + .nth(1) + .expect("--jar "); + let entries = read_file(&PathBuf::from(&jar)).expect("read jar"); + let mut program = load(entries).expect("load program"); + + let base = ClassHierarchy::build(&program); + let hierarchy = JdkHierarchy::wrap(&base); + + let mut recomputed = 0usize; + let mut failed_recompute = 0usize; + for ci in 0..program.classes.len() { + let class_name = program.classes[ci].this_class.clone(); + let methods = program.classes[ci].methods.len(); + for mi in 0..methods { + let Some(snapshot) = program.classes[ci].methods[mi].code.clone() else { + continue; + }; + let method = program.classes[ci].methods[mi].clone(); + match compute_frames(&class_name, &method, &hierarchy) { + Ok(frames) => { + program.classes[ci].methods[mi] + .code + .as_mut() + .expect("code") + .frames = frames; + recomputed += 1; + } + Err(e) => { + failed_recompute += 1; + eprintln!("compute_frames FAILED {class_name}#{} : {e}", method.name); + // Keep the stored frames instead of giving up the class. + program.classes[ci].methods[mi].code = Some(snapshot); + } + } + } + } + println!("recomputed {recomputed} methods, {failed_recompute} compute_frames errors"); + + // Round-trip every class through the writer, so a write-time bug shows + // up the same way it would in the real pipeline. + let mut write_failed = 0usize; + for class in &program.classes { + if let Err(e) = format::write(class) { + write_failed += 1; + eprintln!("write FAILED {}: {e}", class.this_class); + } + } + println!("write errors: {write_failed}"); + + let entries = save(&program).expect("save program"); + let out = std::env::args() + .skip_while(|a| a != "--out") + .nth(1) + .map(PathBuf::from) + .unwrap_or_else(|| std::env::temp_dir().join("frame_repro_out.jar")); + write_file(&out, &entries).expect("write jar"); + println!("wrote {}", out.display()); + + let report = verify_jar(&out, &[]); + let mut pass = 0usize; + let mut fail = 0usize; + let mut skip = 0usize; + for (name, verdict) in &report { + match verdict { + noxis_verify::JavaVerdict::Verified => pass += 1, + noxis_verify::JavaVerdict::Failed(msg) => { + fail += 1; + println!("FAIL {name}: {msg}"); + } + noxis_verify::JavaVerdict::Skipped(_) => skip += 1, + } + } + println!("PASS={pass} FAIL={fail} SKIP={skip}"); +}