NoisePass now builds branching helper methods (counted int loop, random arithmetic, even/odd merge, non-throwing ops only) whose results the entry sums into a private static field, instead of an empty run()V. Names come from the same NameGenerator styles as real code and live in a random real package (no more shadow/decoy uniformity for grep to catch); every generated method gets fresh StackMapTable frames. weave_first_reachable takes (class, entry) pairs. New NoiseConfig.junk_methods_per_class (default 3). Tests assert invariants (unique valid identifiers, calls, field store, determinism) plus real-JVM verification of junk classes; paranoid+junk_ratio=1 smoke on lorep: 18 junk classes, all load.
81 lines
2.5 KiB
Rust
81 lines
2.5 KiB
Rust
//! Weaving junk classes into the reachability graph of live methods.
|
|
|
|
use noxis_analysis::ClassHierarchy;
|
|
use noxis_classfile::{Insn, InvokeKind, Program, compute_frames};
|
|
use noxis_core::{CoreError, PassId};
|
|
|
|
/// The woven call descriptor (entries are always `()V`).
|
|
const WOVEN_DESCRIPTOR: &str = "()V";
|
|
|
|
/// Weave a call of the first junk entry into the first method with code.
|
|
///
|
|
/// The call lands before the terminal (`Return`/`AReturn`/`AThrow`) so it
|
|
/// never strands an unreachable tail without frames; frames are recomputed.
|
|
pub(super) fn weave_first_reachable(
|
|
program: &mut Program,
|
|
real_classes: usize,
|
|
entries: &[(String, String)],
|
|
) -> Result<usize, CoreError> {
|
|
let missing = || CoreError::pass(PassId::Noise, "weave target vanished");
|
|
let Some((owner, entry)) = entries.first() else {
|
|
return Err(missing());
|
|
};
|
|
let target = program.classes[..real_classes.min(program.classes.len())]
|
|
.iter()
|
|
.enumerate()
|
|
.find_map(|(ci, c)| {
|
|
c.methods
|
|
.iter()
|
|
.position(|m| m.code.is_some())
|
|
.map(|mi| (ci, mi))
|
|
});
|
|
let Some((ci, mi)) = target else { return Ok(0) };
|
|
|
|
let class_name = program
|
|
.classes
|
|
.get(ci)
|
|
.ok_or_else(missing)?
|
|
.this_class
|
|
.clone();
|
|
let call = Insn::Invoke {
|
|
kind: InvokeKind::Static,
|
|
owner: owner.clone(),
|
|
name: entry.clone(),
|
|
descriptor: WOVEN_DESCRIPTOR.to_string(),
|
|
itf: false,
|
|
};
|
|
{
|
|
let code = program
|
|
.classes
|
|
.get_mut(ci)
|
|
.and_then(|c| c.methods.get_mut(mi))
|
|
.and_then(|m| m.code.as_mut())
|
|
.ok_or_else(missing)?;
|
|
if matches!(
|
|
code.insns.last(),
|
|
Some(Insn::Return(_) | Insn::AReturn | Insn::AThrow)
|
|
) {
|
|
code.insns.insert(code.insns.len() - 1, call);
|
|
} else {
|
|
code.insns.push(call);
|
|
}
|
|
}
|
|
|
|
let snapshot = program
|
|
.classes
|
|
.get(ci)
|
|
.and_then(|c| c.methods.get(mi))
|
|
.cloned()
|
|
.ok_or_else(missing)?;
|
|
let hierarchy = ClassHierarchy::build(program);
|
|
let frames = compute_frames(&class_name, &snapshot, &hierarchy)
|
|
.map_err(|e| CoreError::pass(PassId::Noise, e.to_string()))?;
|
|
program
|
|
.classes
|
|
.get_mut(ci)
|
|
.and_then(|c| c.methods.get_mut(mi))
|
|
.and_then(|m| m.code.as_mut())
|
|
.ok_or_else(missing)?
|
|
.frames = frames;
|
|
Ok(1)
|
|
}
|