obfuskator/crates/noxis-core/src/pipeline/mod.rs
loki5512344 a2e4c69210 classfile,core: pick frame compact form by byte delta; honor --no-* flags
StackMapTable compact-vs-extended selection used the instruction delta,
but JVM validity requires the byte offset delta <= 63. Track byte offsets
in convert_frames and branch on byte_delta while keeping the instruction
delta stored (ristretto re-derives bytes).

Pipeline::run ignored config.disable, so --no-* CLI flags had no effect;
gate each pass on config.is_enabled.
2026-10-08 22:39:22 +02:00

150 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Конвейер пассов: единый порядок исполнения и отчёт.
pub mod context;
pub mod report;
use noxis_classfile::Program;
use serde::Serialize;
use self::context::Context;
use self::report::Report;
use crate::config::PassId;
use crate::error::CoreError;
/// Результат одного пасса.
#[derive(Debug, Clone, Serialize)]
pub struct PassReport {
pub pass: PassId,
/// Сколько объектов трансформировано.
pub transformed: usize,
/// Сколько объектов сознательно пропущено.
pub skipped: usize,
/// Человекочитаемые заметки пасса.
pub notes: Vec<String>,
}
impl PassReport {
/// Пустой отчёт (пасс ещё не реализован).
pub fn not_implemented(pass: PassId, todo: &'static str) -> Self {
Self {
pass,
transformed: 0,
skipped: 0,
notes: vec![format!("не реализовано: {todo}")],
}
}
}
/// Единый контракт трансформации программы.
pub trait Pass: Send + Sync {
/// Идентификатор пасса (совпадает с ключом в конфиге).
fn id(&self) -> PassId;
/// Применить пасс. Порядок пассов задаёт [`Pipeline`].
fn apply(&self, ctx: &mut Context, program: &mut Program) -> Result<PassReport, CoreError>;
}
/// Упорядоченная цепочка пассов.
///
/// Порядок — не деталь: см. `docs/architecture.md` (rename → resources →
/// strings → constants → flow → noise → debug), иначе часть техник ломает
/// предыдущие или работает по уже мёртвым именам.
#[derive(Default)]
pub struct Pipeline {
passes: Vec<Box<dyn Pass>>,
}
impl Pipeline {
/// Пустой конвейер.
pub fn new() -> Self {
Self::default()
}
/// Добавить пасс в конец.
pub fn push(mut self, pass: Box<dyn Pass>) -> Self {
self.passes.push(pass);
self
}
/// Число пассов в цепочке.
pub fn len(&self) -> usize {
self.passes.len()
}
/// Пуст ли конвейер.
pub fn is_empty(&self) -> bool {
self.passes.is_empty()
}
/// Прогнать всю программу через все пассы.
pub fn run(&self, ctx: &mut Context, program: &mut Program) -> Result<Report, CoreError> {
let mut report = Report {
seed: ctx.master_seed,
..Report::default()
};
report.bump("classes", program.classes.len());
report.bump("resources", program.resources.len());
for pass in &self.passes {
let id = pass.id();
if !ctx.config.is_enabled(id) {
tracing::info!(target: "noxis", pass = %id, "пасс выключен — пропущен");
continue;
}
tracing::info!(target: "noxis", pass = %id, "пасс запущен");
let result = pass.apply(ctx, program)?;
tracing::info!(
target: "noxis",
pass = %id,
transformed = result.transformed,
skipped = result.skipped,
"пасс завершён"
);
report.passes.push(result);
}
report.notes = std::mem::take(&mut ctx.notes);
Ok(report)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::unwrap_used)]
use super::*;
struct Stub(PassId);
impl Pass for Stub {
fn id(&self) -> PassId {
self.0
}
fn apply(
&self,
_ctx: &mut Context,
_program: &mut Program,
) -> Result<PassReport, CoreError> {
Ok(PassReport {
pass: self.0,
transformed: 1,
skipped: 0,
notes: Vec::new(),
})
}
}
#[test]
fn runs_in_order_and_collects_reports() {
let pipeline = Pipeline::new()
.push(Box::new(Stub(PassId::Rename)))
.push(Box::new(Stub(PassId::Strings)));
let mut ctx = Context::new(crate::config::Config::default());
let mut program = Program::default();
let report = pipeline.run(&mut ctx, &mut program).unwrap();
assert_eq!(report.passes.len(), 2);
assert_eq!(report.passes[0].pass, PassId::Rename);
assert_eq!(report.passes[1].pass, PassId::Strings);
assert_eq!(report.counters.get("classes"), Some(&0));
}
}