Initial commit: noxis JVM bytecode obfuscator
Workspace of 9 crates (noxis, noxis-core, noxis-classfile, noxis-analysis, noxis-passes, noxis-runtime, noxis-jar, noxis-verify, noxis-structure-check). AGPL-3.0-only license, Rust 2024 edition, unsafe_code denied.
This commit is contained in:
commit
a641e69455
140 changed files with 17197 additions and 0 deletions
121
crates/noxis-classfile/src/format/frames/emit.rs
Normal file
121
crates/noxis-classfile/src/format/frames/emit.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//! Эмиссия компактных кадров в точках, где их ждёт верификатор.
|
||||
//!
|
||||
//! Вид кадра выбирается сравнением с предыдущим кадром (`Same`/`Append`/
|
||||
//! `Chop`/`SameLocals`, иначе `Full`) — как у `javac`. Хвостовые `Top`
|
||||
//! при сравнении и записи отрезаются: пустой слот — не тип.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use super::merge::Merger;
|
||||
use super::xfer::State;
|
||||
use super::{StackMapFrame, VerificationType};
|
||||
use crate::error::ClassfileError;
|
||||
|
||||
/// Кадры во всех точках `targets` (отсортированы — дельты по порядку).
|
||||
///
|
||||
/// Дельты — в номерах инструкций (как backend после чтения); writer сам
|
||||
/// пересчитает их в байты.
|
||||
pub(super) fn emit_frames(
|
||||
states: &[Option<State>],
|
||||
targets: &BTreeSet<u32>,
|
||||
initial: &[VerificationType],
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<StackMapFrame>, ClassfileError> {
|
||||
let mut out = Vec::new();
|
||||
let mut previous_locals = initial.to_vec();
|
||||
let mut previous_index: Option<u32> = None;
|
||||
for target in targets {
|
||||
let at = *target as usize;
|
||||
let Some(state) = &states[at] else {
|
||||
continue;
|
||||
};
|
||||
let delta = match previous_index {
|
||||
None => *target,
|
||||
Some(previous) => target
|
||||
.checked_sub(previous)
|
||||
.and_then(|delta| delta.checked_sub(1))
|
||||
.ok_or_else(|| merger.invalid("кадры не по порядку"))?,
|
||||
};
|
||||
out.push(select(delta, &state.locals, &previous_locals, &state.stack));
|
||||
previous_locals = state.locals.clone();
|
||||
previous_index = Some(*target);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn select(
|
||||
delta: u32,
|
||||
locals: &[VerificationType],
|
||||
previous: &[VerificationType],
|
||||
stack: &[VerificationType],
|
||||
) -> StackMapFrame {
|
||||
// Сравнение — на гранулярности переменных (`long` — одна запись,
|
||||
// как у `javac`): иначе половины пар дают `Append{[Top, Long]}`.
|
||||
let current = vars(locals);
|
||||
let base = vars(previous);
|
||||
if current == base {
|
||||
if stack.is_empty() {
|
||||
return StackMapFrame::Same {
|
||||
offset_delta: delta,
|
||||
};
|
||||
}
|
||||
if let [local] = stack {
|
||||
return StackMapFrame::SameLocals {
|
||||
offset_delta: delta,
|
||||
local: local.clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
if stack.is_empty()
|
||||
&& let Some(added) = current.strip_prefix(base.as_slice())
|
||||
&& (1..=3).contains(&added.len())
|
||||
&& !added.contains(&VerificationType::Top)
|
||||
{
|
||||
return StackMapFrame::Append {
|
||||
offset_delta: delta,
|
||||
locals: added.to_vec(),
|
||||
};
|
||||
}
|
||||
if stack.is_empty()
|
||||
&& let Some(dropped) = base.strip_prefix(current.as_slice())
|
||||
&& (1..=3).contains(&dropped.len())
|
||||
&& let Ok(count) = u8::try_from(dropped.len())
|
||||
{
|
||||
return StackMapFrame::Chop {
|
||||
offset_delta: delta,
|
||||
count,
|
||||
};
|
||||
}
|
||||
StackMapFrame::Full {
|
||||
offset_delta: delta,
|
||||
locals: current,
|
||||
stack: stack.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Слоты → переменные: `[Long, Top]` — одна запись; хвостовые `Top` — прочь.
|
||||
///
|
||||
/// Одиночные `Top` внутри (дыры) сохраняются: в `Full` это легальный тип,
|
||||
/// а в `Append` мы их не пускаем (см. `select`).
|
||||
fn vars(locals: &[VerificationType]) -> Vec<VerificationType> {
|
||||
let mut out = Vec::with_capacity(locals.len());
|
||||
let mut index = 0;
|
||||
while index < locals.len() {
|
||||
match &locals[index] {
|
||||
VerificationType::Long | VerificationType::Double
|
||||
if locals.get(index + 1) == Some(&VerificationType::Top) =>
|
||||
{
|
||||
out.push(locals[index].clone());
|
||||
index += 2;
|
||||
}
|
||||
other => {
|
||||
out.push(other.clone());
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
while out.last() == Some(&VerificationType::Top) {
|
||||
out.pop();
|
||||
}
|
||||
out
|
||||
}
|
||||
122
crates/noxis-classfile/src/format/frames/flow.rs
Normal file
122
crates/noxis-classfile/src/format/frames/flow.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//! CFG тела метода: метки, рёбра, точки кадров, диапазоны handler'ов.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use super::merge::Merger;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label};
|
||||
use crate::model::ExceptionHandler;
|
||||
|
||||
/// Метки → номера инструкций без маркеров.
|
||||
pub(super) fn strip_marks<'a>(
|
||||
insns: &'a [Insn],
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<(BTreeMap<Label, u32>, Vec<&'a Insn>), ClassfileError> {
|
||||
let mut labels = BTreeMap::new();
|
||||
let mut bare = Vec::with_capacity(insns.len());
|
||||
for insn in insns {
|
||||
match insn {
|
||||
Insn::Mark(label) => {
|
||||
let index = u32::try_from(bare.len())
|
||||
.map_err(|_| merger.invalid("слишком много инструкций"))?;
|
||||
if let Some(prev) = labels.insert(*label, index)
|
||||
&& prev != index
|
||||
{
|
||||
return Err(merger.invalid(&format!("метка {label} объявлена дважды")));
|
||||
}
|
||||
}
|
||||
_ => bare.push(insn),
|
||||
}
|
||||
}
|
||||
Ok((labels, bare))
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
pub(super) fn successors(
|
||||
insn: &Insn,
|
||||
index: u32,
|
||||
len: u32,
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<u32>, ClassfileError> {
|
||||
let mut out = Vec::new();
|
||||
// Без продолжения: переходы без возврата и выходы из метода.
|
||||
// `Jsr`/`Ret` сюда не доходят (их отвергает `transfer`), но рёбра есть.
|
||||
let terminal = matches!(
|
||||
insn,
|
||||
Insn::Goto(..)
|
||||
| Insn::Jsr(..)
|
||||
| Insn::Ret(..)
|
||||
| Insn::TableSwitch { .. }
|
||||
| Insn::LookupSwitch { .. }
|
||||
| Insn::Return(..)
|
||||
| Insn::AReturn
|
||||
| Insn::AThrow
|
||||
);
|
||||
if !terminal && index + 1 < len {
|
||||
out.push(index + 1);
|
||||
}
|
||||
for target in insn.targets() {
|
||||
out.push(
|
||||
labels
|
||||
.get(&target)
|
||||
.copied()
|
||||
.ok_or_else(|| merger.invalid(&format!("нет метки {target}")))?,
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Точки, где верификатор требует кадры: цели переходов + входы handler'ов.
|
||||
pub(super) fn frame_targets(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
handlers: &[HandlerRange],
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<BTreeSet<u32>, ClassfileError> {
|
||||
let mut targets = BTreeSet::new();
|
||||
for insn in bare {
|
||||
for target in insn.targets() {
|
||||
targets.insert(
|
||||
labels
|
||||
.get(&target)
|
||||
.copied()
|
||||
.ok_or_else(|| merger.invalid(&format!("нет метки {target}")))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (_, _, handler, _) in handlers {
|
||||
targets.insert(*handler);
|
||||
}
|
||||
Ok(targets)
|
||||
}
|
||||
|
||||
/// Точка handler'а в номерах инструкций: `(start, end, handler, тип перехвата)`.
|
||||
///
|
||||
/// Диапазоны полуоткрытые `[start, end)`, как в JVMS.
|
||||
pub(super) type HandlerRange = (u32, u32, u32, Option<String>);
|
||||
|
||||
/// Handler'ы в номерах инструкций для рёбер исключений.
|
||||
pub(super) fn handler_ranges(
|
||||
table: &[ExceptionHandler],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<HandlerRange>, ClassfileError> {
|
||||
table
|
||||
.iter()
|
||||
.map(|handler| {
|
||||
let index_of = |label: Label| -> Result<u32, ClassfileError> {
|
||||
labels
|
||||
.get(&label)
|
||||
.copied()
|
||||
.ok_or_else(|| merger.invalid(&format!("нет метки {label}")))
|
||||
};
|
||||
Ok((
|
||||
index_of(handler.start)?,
|
||||
index_of(handler.end)?,
|
||||
index_of(handler.handler)?,
|
||||
handler.catch_type.clone(),
|
||||
))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
158
crates/noxis-classfile/src/format/frames/merge/descs.rs
Normal file
158
crates/noxis-classfile/src/format/frames/merge/descs.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
//! Разбор дескрипторов типов (`(...)V`, `[L...;`).
|
||||
|
||||
use super::super::VerificationType;
|
||||
|
||||
/// Ошибка разбора дескриптора (контекст добавляет вызывающий код).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescError(pub String);
|
||||
|
||||
/// Уложить слоты значения в locals с позиции; вернуть следующую позицию.
|
||||
pub(crate) fn write_local(
|
||||
locals: &mut [VerificationType],
|
||||
slot: usize,
|
||||
value: Vec<VerificationType>,
|
||||
) -> Result<usize, ()> {
|
||||
if slot + value.len() > locals.len() {
|
||||
return Err(());
|
||||
}
|
||||
let width = value.len().max(1);
|
||||
for (offset, item) in value.into_iter().enumerate() {
|
||||
locals[slot + offset] = item;
|
||||
}
|
||||
Ok(slot + width)
|
||||
}
|
||||
|
||||
/// Разобрать дескриптор массива на (глубина, элемент без `;`).
|
||||
pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
|
||||
let dims = descriptor.bytes().take_while(|&b| b == b'[').count();
|
||||
if dims == 0 {
|
||||
return None;
|
||||
}
|
||||
Some((dims, descriptor[dims..].trim_end_matches(';')))
|
||||
}
|
||||
|
||||
/// Слоты одного полевого дескриптора (`long`/`double` — парой с `Top`).
|
||||
pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, DescError> {
|
||||
let mut chars = descriptor.chars();
|
||||
match chars.next() {
|
||||
Some('B') | Some('C') | Some('I') | Some('S') | Some('Z') => {
|
||||
Ok(vec![VerificationType::Integer])
|
||||
}
|
||||
Some('F') => Ok(vec![VerificationType::Float]),
|
||||
Some('J') => Ok(vec![VerificationType::Long, VerificationType::Top]),
|
||||
Some('D') => Ok(vec![VerificationType::Double, VerificationType::Top]),
|
||||
Some('V') => Ok(Vec::new()),
|
||||
Some('L') => {
|
||||
let mut name = String::new();
|
||||
for char in chars {
|
||||
if char == ';' {
|
||||
return Ok(vec![VerificationType::Object(name)]);
|
||||
}
|
||||
name.push(char);
|
||||
}
|
||||
Err(DescError(format!("нет `;` в {descriptor}")))
|
||||
}
|
||||
Some('[') => Ok(vec![VerificationType::Object(descriptor.to_string())]),
|
||||
_ => Err(DescError(format!("кривой дескриптор {descriptor}"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Слоты параметров и возврата (`None` — `void`).
|
||||
type MethodSlots = (Vec<Vec<VerificationType>>, Option<Vec<VerificationType>>);
|
||||
|
||||
/// Дескриптор метода: слоты параметров и слоты возврата (`None` — `void`).
|
||||
pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
|
||||
let (params, ret) = descriptor
|
||||
.split_once('(')
|
||||
.and_then(|(_, rest)| rest.split_once(')'))
|
||||
.ok_or_else(|| DescError(format!("кривой дескриптор {descriptor}")))?;
|
||||
let mut out = Vec::new();
|
||||
let mut index = 0;
|
||||
let bytes = params.as_bytes();
|
||||
while index < bytes.len() {
|
||||
match bytes[index] as char {
|
||||
'B' | 'C' | 'I' | 'S' | 'Z' => {
|
||||
out.push(vec![VerificationType::Integer]);
|
||||
index += 1;
|
||||
}
|
||||
'F' => {
|
||||
out.push(vec![VerificationType::Float]);
|
||||
index += 1;
|
||||
}
|
||||
'J' => {
|
||||
out.push(vec![VerificationType::Long, VerificationType::Top]);
|
||||
index += 1;
|
||||
}
|
||||
'D' => {
|
||||
out.push(vec![VerificationType::Double, VerificationType::Top]);
|
||||
index += 1;
|
||||
}
|
||||
'L' => {
|
||||
let end = params[index..]
|
||||
.find(';')
|
||||
.ok_or_else(|| DescError(format!("нет `;` в {descriptor}")))?;
|
||||
out.push(vec![VerificationType::Object(
|
||||
params[index + 1..index + end].to_string(),
|
||||
)]);
|
||||
index += end + 1;
|
||||
}
|
||||
'[' => {
|
||||
let mut end = index;
|
||||
while bytes[end] == b'[' {
|
||||
end += 1;
|
||||
}
|
||||
if bytes[end] == b'L' {
|
||||
let semi = params[end..]
|
||||
.find(';')
|
||||
.ok_or_else(|| DescError(format!("нет `;` в {descriptor}")))?;
|
||||
end += semi + 1;
|
||||
} else {
|
||||
end += 1;
|
||||
}
|
||||
if end > bytes.len() {
|
||||
return Err(DescError(format!("кривой дескриптор {descriptor}")));
|
||||
}
|
||||
out.push(vec![VerificationType::Object(
|
||||
params[index..end].to_string(),
|
||||
)]);
|
||||
index = end;
|
||||
}
|
||||
_ => return Err(DescError(format!("кривой дескриптор {descriptor}"))),
|
||||
}
|
||||
}
|
||||
let ret = if ret == "V" {
|
||||
None
|
||||
} else {
|
||||
Some(field_slots(ret)?)
|
||||
};
|
||||
Ok((out, ret))
|
||||
}
|
||||
|
||||
/// Тип элемента по дескриптору массива в стеке.
|
||||
pub(crate) fn array_element(
|
||||
array: &VerificationType,
|
||||
kind: crate::insn::ArrayKind,
|
||||
) -> VerificationType {
|
||||
use crate::insn::ArrayKind::{Boolean, Byte, Char, Double, Float, Int, Long, Reference, Short};
|
||||
if let VerificationType::Object(descriptor) = array
|
||||
&& let Some((dims, elem)) = split_array(descriptor)
|
||||
{
|
||||
if dims > 1 {
|
||||
return VerificationType::Object(descriptor[1..].to_string());
|
||||
}
|
||||
return match elem {
|
||||
"Z" | "B" | "C" | "S" | "I" => VerificationType::Integer,
|
||||
"J" => VerificationType::Long,
|
||||
"F" => VerificationType::Float,
|
||||
"D" => VerificationType::Double,
|
||||
_ => VerificationType::Object(format!("[{elem};")),
|
||||
};
|
||||
}
|
||||
match kind {
|
||||
Boolean | Byte | Short | Char | Int => VerificationType::Integer,
|
||||
Long => VerificationType::Long,
|
||||
Float => VerificationType::Float,
|
||||
Double => VerificationType::Double,
|
||||
Reference => VerificationType::Object("java/lang/Object".to_string()),
|
||||
}
|
||||
}
|
||||
198
crates/noxis-classfile/src/format/frames/merge/mod.rs
Normal file
198
crates/noxis-classfile/src/format/frames/merge/mod.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
//! Слияние типов (LUB) и разбор дескрипторов.
|
||||
//!
|
||||
//! Решётка: `Top` — «пусто» (берётся сосед), `Null` впитывается в ссылку,
|
||||
//! одинаковые — сами в себя, ссылки — наименьший общий предок по иерархии.
|
||||
//! Несочетаемые инициализированные типы (`int` vs ссылка после `astore`
|
||||
//! в handler'е) дают `Top`, как обрезанные кадры `javac`: `Top` в кадре
|
||||
//! принимает любое фактическое значение, поэтому это всегда sound.
|
||||
//! Исключение — неинициализированные типы: их слияние означает сломанный
|
||||
//! поток конструирования, здесь честная ошибка, а не молчаливый `Top`.
|
||||
|
||||
pub(super) mod descs;
|
||||
|
||||
use super::VerificationType;
|
||||
use super::xfer::State;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::model::Method;
|
||||
|
||||
use descs::write_local;
|
||||
|
||||
/// Иерархия классов для LUB: достаточно цепочки суперклассов.
|
||||
pub trait Hierarchy {
|
||||
/// Прямой суперкласс (`None` — `java/lang/Object` или неизвестный).
|
||||
fn superclass(&self, class: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Контекст слияния: имена для ошибок + иерархия для LCA.
|
||||
pub(super) struct Merger<'h> {
|
||||
pub class: String,
|
||||
pub method: String,
|
||||
pub hierarchy: &'h dyn Hierarchy,
|
||||
}
|
||||
|
||||
impl Merger<'_> {
|
||||
pub fn invalid(&self, msg: &str) -> ClassfileError {
|
||||
ClassfileError::InvalidCode {
|
||||
class: self.class.clone(),
|
||||
msg: format!("{}: {msg}", self.method),
|
||||
}
|
||||
}
|
||||
|
||||
/// Влить входящее состояние; `true` — слот изменился.
|
||||
pub fn merge_into(
|
||||
&self,
|
||||
slot: &mut Option<State>,
|
||||
incoming: State,
|
||||
) -> Result<bool, ClassfileError> {
|
||||
match slot {
|
||||
None => {
|
||||
*slot = Some(incoming);
|
||||
Ok(true)
|
||||
}
|
||||
Some(old) => {
|
||||
let merged = self.lub_states(old, &incoming)?;
|
||||
if *old == merged {
|
||||
Ok(false)
|
||||
} else {
|
||||
*old = merged;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Начальное состояние: параметры (+ `this`) поверх `Top`.
|
||||
pub fn initial(&self, method: &Method, max_locals: u16) -> Result<State, ClassfileError> {
|
||||
let is_static = method.access_flags & 0x0008 != 0;
|
||||
let (params, _) =
|
||||
descs::method_slots(&method.descriptor).map_err(|e| self.invalid(&e.0))?;
|
||||
let mut locals = vec![VerificationType::Top; max_locals as usize];
|
||||
let mut slot = 0;
|
||||
if !is_static {
|
||||
let this = if method.name == "<init>" {
|
||||
VerificationType::UninitializedThis
|
||||
} else {
|
||||
VerificationType::Object(self.class.clone())
|
||||
};
|
||||
slot = write_local(&mut locals, slot, vec![this])
|
||||
.map_err(|_| self.invalid("параметры не влезают в locals"))?;
|
||||
}
|
||||
for param in params {
|
||||
slot = write_local(&mut locals, slot, param)
|
||||
.map_err(|_| self.invalid("параметры не влезают в locals"))?;
|
||||
}
|
||||
Ok(State {
|
||||
locals,
|
||||
stack: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn lub_states(&self, first: &State, second: &State) -> Result<State, ClassfileError> {
|
||||
if first.locals.len() != second.locals.len() {
|
||||
return Err(self.invalid("разная длина locals при слиянии"));
|
||||
}
|
||||
if first.stack.len() != second.stack.len() {
|
||||
return Err(self.invalid("разная глубина стека при слиянии"));
|
||||
}
|
||||
Ok(State {
|
||||
locals: first
|
||||
.locals
|
||||
.iter()
|
||||
.zip(&second.locals)
|
||||
.map(|(a, b)| self.lub_types(a, b))
|
||||
.collect::<Result<_, _>>()?,
|
||||
stack: first
|
||||
.stack
|
||||
.iter()
|
||||
.zip(&second.stack)
|
||||
.map(|(a, b)| self.lub_types(a, b))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn lub_types(
|
||||
&self,
|
||||
first: &VerificationType,
|
||||
second: &VerificationType,
|
||||
) -> Result<VerificationType, ClassfileError> {
|
||||
use VerificationType::{Double, Float, Integer, Long, Null, Object, Top};
|
||||
if first == second {
|
||||
return Ok(first.clone());
|
||||
}
|
||||
match (first, second) {
|
||||
(Top, other) | (other, Top) => Ok(other.clone()),
|
||||
(Null, other @ Object(_)) | (other @ Object(_), Null) => Ok(other.clone()),
|
||||
(Null, Null) => Ok(Null),
|
||||
(Integer, Integer) | (Float, Float) | (Long, Long) | (Double, Double) => {
|
||||
Ok(first.clone())
|
||||
}
|
||||
(Object(a), Object(b)) => Ok(Object(self.common_superclass(a, b))),
|
||||
// Одинаковые уже разобраны выше; здесь неинициализированные
|
||||
// всегда разные — это сломанный поток конструирования.
|
||||
(VerificationType::UninitializedThis | VerificationType::Uninitialized(_), _)
|
||||
| (_, VerificationType::UninitializedThis | VerificationType::Uninitialized(_)) => {
|
||||
Err(self.invalid("слияние неинициализированных типов"))
|
||||
}
|
||||
// Несочетаемые инициализированные (например, `int` из тела `try`
|
||||
// и `Throwable` из handler'а в одном слоте): `Top`, как у `javac`.
|
||||
_ => Ok(Top),
|
||||
}
|
||||
}
|
||||
|
||||
/// Наименьший общий предок: подъём по цепочкам, запасной — `Object`.
|
||||
fn common_superclass(&self, first: &str, second: &str) -> String {
|
||||
if first == second {
|
||||
return first.to_string();
|
||||
}
|
||||
if descs::split_array(first).is_some() || descs::split_array(second).is_some() {
|
||||
return self.array_lca(first, second);
|
||||
}
|
||||
let mut chain = Vec::new();
|
||||
let mut current = Some(first.to_string());
|
||||
let mut guard = 0;
|
||||
while let Some(name) = current {
|
||||
if name == second {
|
||||
return name;
|
||||
}
|
||||
chain.push(name.clone());
|
||||
current = self.hierarchy.superclass(&name);
|
||||
guard += 1;
|
||||
if guard > 1024 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut current = Some(second.to_string());
|
||||
guard = 0;
|
||||
while let Some(name) = current {
|
||||
if chain.contains(&name) {
|
||||
return name;
|
||||
}
|
||||
current = self.hierarchy.superclass(&name);
|
||||
guard += 1;
|
||||
if guard > 1024 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
"java/lang/Object".to_string()
|
||||
}
|
||||
|
||||
/// LCA массивов: та же размерность + LCA элементов, иначе `Object`.
|
||||
fn array_lca(&self, first: &str, second: &str) -> String {
|
||||
let (dims_a, elem_a) = descs::split_array(first).unwrap_or((0, first));
|
||||
let (dims_b, elem_b) = descs::split_array(second).unwrap_or((0, second));
|
||||
if dims_a == 0 || dims_b == 0 || dims_a != dims_b {
|
||||
return "java/lang/Object".to_string();
|
||||
}
|
||||
if elem_a == elem_b {
|
||||
return first.to_string();
|
||||
}
|
||||
if elem_a.len() == 1 || elem_b.len() == 1 {
|
||||
return "java/lang/Object".to_string();
|
||||
}
|
||||
let common = self.common_superclass(
|
||||
elem_a.strip_prefix('L').unwrap_or(elem_a),
|
||||
elem_b.strip_prefix('L').unwrap_or(elem_b),
|
||||
);
|
||||
format!("{}L{common};", "[".repeat(dims_a))
|
||||
}
|
||||
}
|
||||
235
crates/noxis-classfile/src/format/frames/mod.rs
Normal file
235
crates/noxis-classfile/src/format/frames/mod.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
//! `StackMapTable`: представление фреймов и вычисление после трансформаций.
|
||||
//!
|
||||
//! Фреймы — самая дорогая часть любого обфускатора на Rust: JVM-верификатор
|
||||
//! (class version ≥ 50) требует точного `StackMapTable`, иначе класс не грузится.
|
||||
//! Варианты реализации и их цены — `docs/stack.md`.
|
||||
//!
|
||||
//! Метод: CFG по меткам → worklist с типами (`xfer`) → слияние LUB (`merge`)
|
||||
//! → эмиссия компактных кадров (`emit`). Байтовые смещения берутся тем же
|
||||
//! преобразованием, что использует writer, — иначе кадры разъедутся с байтами.
|
||||
|
||||
mod emit;
|
||||
mod flow;
|
||||
mod merge;
|
||||
mod xfer;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label, Prim};
|
||||
use crate::model::Method;
|
||||
|
||||
use flow::{HandlerRange, frame_targets, handler_ranges, strip_marks, successors};
|
||||
pub use merge::Hierarchy;
|
||||
use merge::Merger;
|
||||
use xfer::{State, transfer};
|
||||
|
||||
/// Тип верификации из JVMS §4.10.1.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VerificationType {
|
||||
Top,
|
||||
Integer,
|
||||
Float,
|
||||
Double,
|
||||
Long,
|
||||
Null,
|
||||
UninitializedThis,
|
||||
/// Внутреннее имя класса (`java/lang/Object`) или дескриптор массива.
|
||||
Object(String),
|
||||
/// Байтовое смещение инструкции `new`.
|
||||
Uninitialized(u16),
|
||||
}
|
||||
|
||||
impl VerificationType {
|
||||
/// Тип, соответствующий примитиву.
|
||||
pub fn of_prim(p: Prim) -> Self {
|
||||
match p {
|
||||
Prim::Int => VerificationType::Integer,
|
||||
Prim::Float => VerificationType::Float,
|
||||
Prim::Long => VerificationType::Long,
|
||||
Prim::Double => VerificationType::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Запись `StackMapTable` (JVMS §4.7.4).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StackMapFrame {
|
||||
Same {
|
||||
offset_delta: u32,
|
||||
},
|
||||
SameLocals {
|
||||
offset_delta: u32,
|
||||
local: VerificationType,
|
||||
},
|
||||
Chop {
|
||||
offset_delta: u32,
|
||||
count: u8,
|
||||
},
|
||||
Append {
|
||||
offset_delta: u32,
|
||||
locals: Vec<VerificationType>,
|
||||
},
|
||||
Full {
|
||||
offset_delta: u32,
|
||||
locals: Vec<VerificationType>,
|
||||
stack: Vec<VerificationType>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Вычислить фреймы для тела метода после всех трансформаций.
|
||||
///
|
||||
/// Кадры нужны только классам версии ≥ 50; `jsr`/`ret` отвергаются (такие
|
||||
/// классы старше и кадры им не нужны). Иерархия — для LUB ссылочных типов.
|
||||
pub fn compute_frames(
|
||||
class: &str,
|
||||
method: &Method,
|
||||
hierarchy: &dyn Hierarchy,
|
||||
) -> Result<Vec<StackMapFrame>, ClassfileError> {
|
||||
let code = method
|
||||
.code
|
||||
.as_ref()
|
||||
.ok_or_else(|| invalid(class, &method.name, "нет тела"))?;
|
||||
if code.insns.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let merger = Merger {
|
||||
class: class.to_string(),
|
||||
method: method.name.clone(),
|
||||
hierarchy,
|
||||
};
|
||||
let (labels, bare) = strip_marks(&code.insns, &merger)?;
|
||||
let bytes = layout(&bare, &labels, &merger)?;
|
||||
let handlers = handler_ranges(&code.exception_table, &labels, &merger)?;
|
||||
let initial = merger.initial(method, code.max_locals)?;
|
||||
let mut states: Vec<Option<State>> = vec![None; bare.len()];
|
||||
states[0] = Some(initial.clone());
|
||||
run_worklist(&merger, &bare, &bytes, &labels, &handlers, &mut states)?;
|
||||
let targets = frame_targets(&bare, &labels, &handlers, &merger)?;
|
||||
emit::emit_frames(&states, &targets, &initial.locals, &merger)
|
||||
}
|
||||
|
||||
/// Байтовое смещение каждой инструкции — тем же кодированием, что в writer'е.
|
||||
fn layout(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<u16>, ClassfileError> {
|
||||
use crate::format::write::{code, pool::Pool};
|
||||
|
||||
let mut pool = Pool::new();
|
||||
let resolve = |label: Label| -> Result<u32, ClassfileError> {
|
||||
labels
|
||||
.get(&label)
|
||||
.copied()
|
||||
.ok_or_else(|| merger.invalid(&format!("нет метки {label}")))
|
||||
};
|
||||
let mut rt_insns = Vec::with_capacity(bare.len());
|
||||
for (index, insn) in bare.iter().enumerate() {
|
||||
let index = u32::try_from(index).map_err(|_| merger.invalid("слишком много инструкций"))?;
|
||||
if let Some(rt) = code::insns::convert(insn, index, &mut pool, &resolve)? {
|
||||
rt_insns.push(rt);
|
||||
}
|
||||
}
|
||||
let map = code::measure(&rt_insns)?;
|
||||
(0..bare.len())
|
||||
.map(|index| {
|
||||
map.get(&(index as u32))
|
||||
.copied()
|
||||
.ok_or_else(|| merger.invalid("замер потерял инструкцию"))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Основной цикл: состояния текут по рёбрам CFG до неподвижной точки.
|
||||
fn run_worklist(
|
||||
merger: &Merger<'_>,
|
||||
bare: &[&Insn],
|
||||
bytes: &[u16],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
handlers: &[HandlerRange],
|
||||
states: &mut [Option<State>],
|
||||
) -> Result<(), ClassfileError> {
|
||||
let mut queue: VecDeque<u32> = VecDeque::from([0]);
|
||||
let mut fuel = 1_000_000u32;
|
||||
while let Some(index) = queue.pop_front() {
|
||||
let at = index as usize;
|
||||
let state = states[at]
|
||||
.clone()
|
||||
.ok_or_else(|| merger.invalid("пустое состояние"))?;
|
||||
let mut out = state;
|
||||
transfer(&merger.class, &merger.method, bare[at], bytes[at], &mut out).map_err(
|
||||
|error| match error {
|
||||
ClassfileError::InvalidCode { class, msg } => ClassfileError::InvalidCode {
|
||||
class,
|
||||
msg: format!("[{at}] {msg}"),
|
||||
},
|
||||
other => other,
|
||||
},
|
||||
)?;
|
||||
let nexts = successors(bare[at], index, bare.len() as u32, labels, merger)?;
|
||||
for next in nexts {
|
||||
flow_into(merger, states, &mut queue, &mut fuel, next, out.clone())?;
|
||||
}
|
||||
for (start, end, handler, catch) in handlers {
|
||||
if index >= *start && index < *end {
|
||||
let caught = State {
|
||||
locals: out.locals.clone(),
|
||||
stack: vec![catch_type(catch)],
|
||||
};
|
||||
flow_into(merger, states, &mut queue, &mut fuel, *handler, caught)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Влить состояние в инструкцию; `true` — изменилось, продолжить обход.
|
||||
fn flow_into(
|
||||
merger: &Merger<'_>,
|
||||
states: &mut [Option<State>],
|
||||
queue: &mut VecDeque<u32>,
|
||||
fuel: &mut u32,
|
||||
next: u32,
|
||||
state: State,
|
||||
) -> Result<(), ClassfileError> {
|
||||
if *fuel == 0 {
|
||||
return Err(merger.invalid("нет неподвижной точки"));
|
||||
}
|
||||
*fuel -= 1;
|
||||
let slot = states
|
||||
.get_mut(next as usize)
|
||||
.ok_or_else(|| merger.invalid("переход вне тела метода"))?;
|
||||
let changed = merger
|
||||
.merge_into(slot, state)
|
||||
.map_err(|error| match error {
|
||||
ClassfileError::InvalidCode { class, msg } => ClassfileError::InvalidCode {
|
||||
class,
|
||||
msg: format!("[->{next}] {msg}"),
|
||||
},
|
||||
other => other,
|
||||
})?;
|
||||
if changed {
|
||||
queue.push_back(next);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
/// Тип исключения на входе в handler (`catch-all` — любое throwable).
|
||||
fn catch_type(catch: &Option<String>) -> VerificationType {
|
||||
match catch {
|
||||
Some(name) => VerificationType::Object(name.clone()),
|
||||
None => VerificationType::Object("java/lang/Throwable".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid(class: &str, method: &str, msg: &str) -> ClassfileError {
|
||||
ClassfileError::InvalidCode {
|
||||
class: class.to_string(),
|
||||
msg: format!("{method}: {msg}"),
|
||||
}
|
||||
}
|
||||
179
crates/noxis-classfile/src/format/frames/tests.rs
Normal file
179
crates/noxis-classfile/src/format/frames/tests.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
//! Unit-тесты `compute_frames` на синтезированном IR.
|
||||
|
||||
#![allow(clippy::expect_used, clippy::unwrap_used)]
|
||||
|
||||
use super::{StackMapFrame, VerificationType, compute_frames};
|
||||
use crate::Hierarchy;
|
||||
use crate::insn::{BranchCond, Cond, Const, Insn, Prim, SlotKind};
|
||||
use crate::model::{Code, ExceptionHandler, Method};
|
||||
|
||||
/// Пустая иерархия: всё неизвестное — наследники `Object` (для LUB хватает).
|
||||
struct NoHierarchy;
|
||||
|
||||
impl Hierarchy for NoHierarchy {
|
||||
fn superclass(&self, _class: &str) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn code_method(
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
max_stack: u16,
|
||||
max_locals: u16,
|
||||
insns: Vec<Insn>,
|
||||
table: Vec<ExceptionHandler>,
|
||||
) -> Method {
|
||||
Method {
|
||||
access_flags: 0x0008,
|
||||
name: name.to_string(),
|
||||
descriptor: descriptor.to_string(),
|
||||
code: Some(Code {
|
||||
max_stack,
|
||||
max_locals,
|
||||
insns,
|
||||
exception_table: table,
|
||||
frames: vec![],
|
||||
attributes: vec![],
|
||||
}),
|
||||
attributes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Ветвь → два кадра (`Same`, `SameLocals`).
|
||||
#[test]
|
||||
fn branch_emits_same_frames() {
|
||||
let mut method = code_method(
|
||||
"max",
|
||||
"(II)I",
|
||||
2,
|
||||
2,
|
||||
vec![
|
||||
Insn::Mark(0),
|
||||
load(0),
|
||||
load(1),
|
||||
Insn::Branch(BranchCond::Int(Cond::Ge), 1),
|
||||
Insn::Const(Const::Int(0)),
|
||||
Insn::Goto(2),
|
||||
Insn::Mark(1),
|
||||
Insn::Const(Const::Int(1)),
|
||||
Insn::Mark(2),
|
||||
Insn::Return(Some(Prim::Int)),
|
||||
],
|
||||
vec![],
|
||||
);
|
||||
method.access_flags = 0x0009;
|
||||
let frames = compute_frames("demo/Max", &method, &NoHierarchy).expect("frames");
|
||||
assert_eq!(
|
||||
frames,
|
||||
vec![
|
||||
StackMapFrame::Same { offset_delta: 5 },
|
||||
StackMapFrame::SameLocals {
|
||||
offset_delta: 0,
|
||||
local: VerificationType::Integer,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// `try/catch`: handler получает `Full`, продолжение — `Append`.
|
||||
#[test]
|
||||
fn handler_emits_full_frame() {
|
||||
let method = code_method(
|
||||
"run",
|
||||
"()V",
|
||||
1,
|
||||
2,
|
||||
vec![
|
||||
Insn::Mark(0),
|
||||
Insn::Const(Const::Int(0)),
|
||||
store(0),
|
||||
Insn::Mark(1),
|
||||
Insn::Const(Const::Int(1)),
|
||||
store(0),
|
||||
Insn::Goto(3),
|
||||
Insn::Mark(2),
|
||||
store_ref(1),
|
||||
Insn::Const(Const::Int(2)),
|
||||
store(0),
|
||||
Insn::Mark(3),
|
||||
Insn::Return(None),
|
||||
],
|
||||
vec![ExceptionHandler {
|
||||
start: 1,
|
||||
end: 2,
|
||||
handler: 2,
|
||||
catch_type: Some("java/lang/Exception".to_string()),
|
||||
}],
|
||||
);
|
||||
let frames = compute_frames("demo/Try", &method, &NoHierarchy).expect("frames");
|
||||
assert_eq!(
|
||||
frames,
|
||||
vec![
|
||||
StackMapFrame::Full {
|
||||
offset_delta: 5,
|
||||
locals: vec![VerificationType::Integer],
|
||||
stack: vec![VerificationType::Object("java/lang/Exception".to_string())],
|
||||
},
|
||||
StackMapFrame::Append {
|
||||
offset_delta: 2,
|
||||
locals: vec![VerificationType::Object("java/lang/Exception".to_string())],
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Два `new` на разных ветвях нельзя слить — честная ошибка, не `Top`.
|
||||
#[test]
|
||||
fn uninitialized_merge_is_rejected() {
|
||||
let method = code_method(
|
||||
"pick",
|
||||
"(Z)Ljava/lang/Object;",
|
||||
1,
|
||||
2,
|
||||
vec![
|
||||
Insn::Mark(0),
|
||||
load(0),
|
||||
Insn::Branch(BranchCond::Zero(Cond::Ne), 1),
|
||||
Insn::New("a/A".to_string()),
|
||||
store_ref(1),
|
||||
Insn::Goto(2),
|
||||
Insn::Mark(1),
|
||||
Insn::New("a/A".to_string()),
|
||||
store_ref(1),
|
||||
Insn::Mark(2),
|
||||
Insn::Load {
|
||||
slot: 1,
|
||||
kind: SlotKind::Ref,
|
||||
},
|
||||
Insn::AReturn,
|
||||
],
|
||||
vec![],
|
||||
);
|
||||
let err = compute_frames("demo/Pick", &method, &NoHierarchy).expect_err("слияние");
|
||||
assert!(
|
||||
err.to_string().contains("неинициализированных"),
|
||||
"не тот текст: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
fn load(slot: u16) -> Insn {
|
||||
Insn::Load {
|
||||
slot,
|
||||
kind: SlotKind::Int,
|
||||
}
|
||||
}
|
||||
|
||||
fn store(slot: u16) -> Insn {
|
||||
Insn::Store {
|
||||
slot,
|
||||
kind: SlotKind::Int,
|
||||
}
|
||||
}
|
||||
|
||||
fn store_ref(slot: u16) -> Insn {
|
||||
Insn::Store {
|
||||
slot,
|
||||
kind: SlotKind::Ref,
|
||||
}
|
||||
}
|
||||
133
crates/noxis-classfile/src/format/frames/xfer/calls.rs
Normal file
133
crates/noxis-classfile/src/format/frames/xfer/calls.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
//! Вызовы и константы: дескрипторы, `<init>`, слоты значений.
|
||||
|
||||
use super::super::VerificationType;
|
||||
use super::super::merge::descs::{DescError, field_slots, method_slots};
|
||||
use super::State;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, Convert, InvokeKind, Prim};
|
||||
|
||||
pub(super) fn const_slots(value: &Const) -> Result<Vec<VerificationType>, DescError> {
|
||||
Ok(match value {
|
||||
Const::Int(_) => vec![VerificationType::Integer],
|
||||
Const::Long(_) => vec![VerificationType::Long, VerificationType::Top],
|
||||
Const::Float(_) => vec![VerificationType::Float],
|
||||
Const::Double(_) => vec![VerificationType::Double, VerificationType::Top],
|
||||
Const::String(_) => vec![VerificationType::Object("java/lang/String".to_string())],
|
||||
Const::Class(_) => vec![VerificationType::Object("java/lang/Class".to_string())],
|
||||
Const::MethodType(_) => vec![VerificationType::Object(
|
||||
"java/lang/invoke/MethodType".to_string(),
|
||||
)],
|
||||
Const::MethodHandle { .. } => vec![VerificationType::Object(
|
||||
"java/lang/invoke/MethodHandle".to_string(),
|
||||
)],
|
||||
Const::Dynamic { descriptor, .. } => field_slots(descriptor)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn invoke(
|
||||
class: &str,
|
||||
method: &str,
|
||||
state: &mut State,
|
||||
kind: InvokeKind,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
) -> Result<(), ClassfileError> {
|
||||
let (params, ret) = method_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
super::stack::pop_slots(
|
||||
state,
|
||||
count_slots(¶ms),
|
||||
class,
|
||||
method,
|
||||
"аргументы вызова",
|
||||
)?;
|
||||
let mut receiver = None;
|
||||
if kind != InvokeKind::Static {
|
||||
receiver = super::stack::pop_slots(state, 1, class, method, "objectref")?.pop();
|
||||
}
|
||||
// `<init>` инициализирует получателя во ВСЕХ его копиях.
|
||||
if name == "<init>" {
|
||||
match receiver {
|
||||
Some(VerificationType::UninitializedThis) => {
|
||||
replace(
|
||||
state,
|
||||
&VerificationType::UninitializedThis,
|
||||
&initialized(class),
|
||||
);
|
||||
}
|
||||
Some(VerificationType::Uninitialized(_)) => {
|
||||
let fresh = VerificationType::Object(owner.to_string());
|
||||
replace_uninit(state, &fresh);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(ret) = ret {
|
||||
super::stack::push(state, ret)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Число слотов всех параметров.
|
||||
pub(super) fn count_slots(params: &[Vec<VerificationType>]) -> usize {
|
||||
params.iter().map(Vec::len).sum()
|
||||
}
|
||||
|
||||
/// Заменить все вхождения типа новым (инициализация через `<init>`).
|
||||
fn replace(state: &mut State, from: &VerificationType, to: &VerificationType) {
|
||||
for slot in state.locals.iter_mut().chain(state.stack.iter_mut()) {
|
||||
if slot == from {
|
||||
*slot = to.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Любой `Uninitialized` (кроме `this`) → тип владельца (созданный `new`).
|
||||
fn replace_uninit(state: &mut State, to: &VerificationType) {
|
||||
for slot in state.locals.iter_mut().chain(state.stack.iter_mut()) {
|
||||
if matches!(slot, VerificationType::Uninitialized(_)) {
|
||||
*slot = to.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialized(class: &str) -> VerificationType {
|
||||
VerificationType::Object(class.to_string())
|
||||
}
|
||||
|
||||
pub(super) fn context(class: &str, method: &str, error: DescError) -> ClassfileError {
|
||||
super::super::invalid(class, method, &error.0)
|
||||
}
|
||||
|
||||
pub(super) fn prim_slots(prim: Prim) -> u32 {
|
||||
match prim {
|
||||
Prim::Int | Prim::Float => 1,
|
||||
Prim::Long | Prim::Double => 2,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn prim_push(prim: Prim) -> Vec<VerificationType> {
|
||||
match VerificationType::of_prim(prim) {
|
||||
value @ (VerificationType::Long | VerificationType::Double) => {
|
||||
vec![value, VerificationType::Top]
|
||||
}
|
||||
value => vec![value],
|
||||
}
|
||||
}
|
||||
|
||||
/// `(слоты аргумента, примитив результата)` для конверсии.
|
||||
pub(super) fn convert_shapes(op: Convert) -> (usize, Prim) {
|
||||
use Convert::{D2F, D2I, D2L, F2D, F2I, F2L, I2B, I2C, I2D, I2F, I2L, I2S, L2D, L2F, L2I};
|
||||
use Prim::{Double, Float, Int, Long};
|
||||
match op {
|
||||
I2L => (1, Long),
|
||||
I2D => (1, Double),
|
||||
I2F | I2B | I2C | I2S => (1, if op == I2F { Float } else { Int }),
|
||||
L2I | L2F => (2, if op == L2F { Float } else { Int }),
|
||||
L2D => (2, Double),
|
||||
F2I | F2L => (1, if op == F2L { Long } else { Int }),
|
||||
F2D => (1, Double),
|
||||
D2I | D2L => (2, if op == D2L { Long } else { Int }),
|
||||
D2F => (2, Float),
|
||||
}
|
||||
}
|
||||
233
crates/noxis-classfile/src/format/frames/xfer/mod.rs
Normal file
233
crates/noxis-classfile/src/format/frames/xfer/mod.rs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
//! Состояние типов и transfer-функции инструкций.
|
||||
//!
|
||||
//! Стек растёт вправо (`push` — в конец). `long`/`double` занимают два слота:
|
||||
//! значение + `Top`. Проверяется только ФОРМА (глубина, категории), не
|
||||
//! соответствие типов — неверные типы честно упадут на верификаторе JVM.
|
||||
|
||||
pub(super) mod calls;
|
||||
pub(super) mod stack;
|
||||
|
||||
use super::merge::descs::{array_element, field_slots, method_slots};
|
||||
use super::{VerificationType, invalid};
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Arith, ArrayKind, BranchCond, Insn, SlotKind};
|
||||
|
||||
use calls::{const_slots, context, convert_shapes, count_slots, invoke, prim_push, prim_slots};
|
||||
use stack::{dup, dup2, local_get, pop_slots, push, store_local, wide_push};
|
||||
|
||||
/// Типы locals + operand stack в одной точке программы.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub(super) struct State {
|
||||
pub locals: Vec<VerificationType>,
|
||||
pub stack: Vec<VerificationType>,
|
||||
}
|
||||
|
||||
/// Применить инструкцию к состоянию (на месте).
|
||||
pub(super) fn transfer(
|
||||
class: &str,
|
||||
method: &str,
|
||||
insn: &Insn,
|
||||
offset: u16,
|
||||
state: &mut State,
|
||||
) -> Result<(), ClassfileError> {
|
||||
match insn {
|
||||
Insn::Mark(label) => Err(invalid(class, method, &format!("маркер {label} не снят"))),
|
||||
Insn::Nop => Ok(()),
|
||||
Insn::AconstNull => push(state, vec![VerificationType::Null]),
|
||||
Insn::Const(value) => {
|
||||
let slots = const_slots(value).map_err(|e| context(class, method, e))?;
|
||||
push(state, slots)
|
||||
}
|
||||
Insn::Load { slot, kind } => {
|
||||
let value = local_get(state, *slot, class, method)?;
|
||||
let _ = kind;
|
||||
push(state, wide_push(value))
|
||||
}
|
||||
Insn::Store { slot, kind } => {
|
||||
let slots = usize::from(*kind == SlotKind::Long || *kind == SlotKind::Double) + 1;
|
||||
let taken = pop_slots(state, slots, class, method, "store")?;
|
||||
store_local(state, *slot, *kind, taken, class, method)
|
||||
}
|
||||
Insn::Iinc { index, .. } => {
|
||||
let current = local_get(state, *index, class, method)?;
|
||||
if current != VerificationType::Integer {
|
||||
return Err(invalid(class, method, "iinc не по int"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Insn::ArrayLoad(kind) => {
|
||||
pop_slots(state, 1, class, method, "индекс массива")?;
|
||||
let array = pop_slots(state, 1, class, method, "массив")?.remove(0);
|
||||
push(state, vec![array_element(&array, *kind)])
|
||||
}
|
||||
Insn::ArrayStore(kind) => {
|
||||
let count = match kind {
|
||||
ArrayKind::Long | ArrayKind::Double => 2,
|
||||
_ => 1,
|
||||
};
|
||||
pop_slots(state, count, class, method, "значение")?;
|
||||
pop_slots(state, 1, class, method, "индекс массива")?;
|
||||
pop_slots(state, 1, class, method, "массив")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::Pop => {
|
||||
let top = pop_slots(state, 1, class, method, "pop")?.remove(0);
|
||||
if top == VerificationType::Top {
|
||||
return Err(invalid(class, method, "pop половины long/double"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Insn::Pop2 => {
|
||||
pop_slots(state, 2, class, method, "pop2")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::Dup => dup(state, class, method, &[1], &[1, 1]),
|
||||
Insn::DupX1 => dup(state, class, method, &[2, 1], &[1, 2, 1]),
|
||||
Insn::DupX2 => dup(state, class, method, &[3, 2, 1], &[1, 3, 2, 1]),
|
||||
Insn::Dup2 => dup2(state, class, method, false, false),
|
||||
Insn::Dup2X1 => dup2(state, class, method, true, false),
|
||||
Insn::Dup2X2 => dup2(state, class, method, true, true),
|
||||
Insn::Swap => {
|
||||
let mut taken = pop_slots(state, 2, class, method, "swap")?;
|
||||
if taken.contains(&VerificationType::Top) {
|
||||
return Err(invalid(class, method, "swap с половиной"));
|
||||
}
|
||||
taken.reverse();
|
||||
push(state, taken)
|
||||
}
|
||||
Insn::Binary { prim, op } => {
|
||||
let slots = prim_slots(*prim);
|
||||
if *op == Arith::Neg {
|
||||
pop_slots(state, slots as usize, class, method, "neg")?;
|
||||
} else if matches!(op, Arith::Shl | Arith::Shr | Arith::Ushr) {
|
||||
// Сдвиг берёт (значение, int), а не два значения.
|
||||
pop_slots(state, slots as usize + 1, class, method, "shift")?;
|
||||
} else {
|
||||
pop_slots(state, 2 * slots as usize, class, method, "arith")?;
|
||||
}
|
||||
push(state, prim_push(*prim))
|
||||
}
|
||||
Insn::Cmp { prim, .. } => {
|
||||
pop_slots(state, 2 * prim_slots(*prim) as usize, class, method, "cmp")?;
|
||||
push(state, vec![VerificationType::Integer])
|
||||
}
|
||||
Insn::Convert(op) => {
|
||||
let (src, dst) = convert_shapes(*op);
|
||||
pop_slots(state, src, class, method, "convert")?;
|
||||
push(state, prim_push(dst))
|
||||
}
|
||||
Insn::Branch(cond, _) => {
|
||||
let count = match cond {
|
||||
BranchCond::Zero(..) | BranchCond::Null { .. } => 1,
|
||||
BranchCond::Int(..) | BranchCond::Ref { .. } => 2,
|
||||
};
|
||||
pop_slots(state, count, class, method, "branch")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::Goto(_) => Ok(()),
|
||||
Insn::Jsr(_) | Insn::Ret(_) => Err(invalid(
|
||||
class,
|
||||
method,
|
||||
"подпрограммы jsr/ret не поддерживаются",
|
||||
)),
|
||||
Insn::TableSwitch { .. } | Insn::LookupSwitch { .. } => {
|
||||
pop_slots(state, 1, class, method, "switch")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::Return(prim) => {
|
||||
if let Some(prim) = prim {
|
||||
pop_slots(state, prim_slots(*prim) as usize, class, method, "return")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Insn::AReturn => {
|
||||
pop_slots(state, 1, class, method, "areturn")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::GetStatic { descriptor, .. } => {
|
||||
let slots = field_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
push(state, slots)
|
||||
}
|
||||
Insn::PutStatic { descriptor, .. } => {
|
||||
let slots = field_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
pop_slots(state, slots.len(), class, method, "putstatic")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::GetField { descriptor, .. } => {
|
||||
pop_slots(state, 1, class, method, "objectref")?;
|
||||
let slots = field_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
push(state, slots)
|
||||
}
|
||||
Insn::PutField { descriptor, .. } => {
|
||||
let slots = field_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
pop_slots(state, slots.len(), class, method, "putfield")?;
|
||||
pop_slots(state, 1, class, method, "objectref")?;
|
||||
Ok(())
|
||||
}
|
||||
Insn::Invoke {
|
||||
kind,
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
..
|
||||
} => invoke(class, method, state, *kind, owner, name, descriptor),
|
||||
Insn::InvokeDynamic { descriptor, .. } => {
|
||||
let (params, ret) = method_slots(descriptor).map_err(|e| context(class, method, e))?;
|
||||
pop_slots(state, count_slots(¶ms), class, method, "invokedynamic")?;
|
||||
if let Some(ret) = ret {
|
||||
push(state, ret)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Insn::New(_) => push(state, vec![VerificationType::Uninitialized(offset)]),
|
||||
Insn::NewArray(kind) => {
|
||||
pop_slots(state, 1, class, method, "newarray")?;
|
||||
let descriptor = match kind {
|
||||
ArrayKind::Boolean => "[Z",
|
||||
ArrayKind::Char => "[C",
|
||||
ArrayKind::Float => "[F",
|
||||
ArrayKind::Double => "[D",
|
||||
ArrayKind::Byte => "[B",
|
||||
ArrayKind::Short => "[S",
|
||||
ArrayKind::Int => "[I",
|
||||
ArrayKind::Long => "[J",
|
||||
ArrayKind::Reference => {
|
||||
return Err(invalid(class, method, "newarray ссылочного типа"));
|
||||
}
|
||||
};
|
||||
push(
|
||||
state,
|
||||
vec![VerificationType::Object(descriptor.to_string())],
|
||||
)
|
||||
}
|
||||
Insn::ANewArray(name) => {
|
||||
pop_slots(state, 1, class, method, "anewarray")?;
|
||||
push(state, vec![VerificationType::Object(format!("[L{name};"))])
|
||||
}
|
||||
Insn::MultiANewArray { descriptor, dims } => {
|
||||
pop_slots(state, *dims as usize, class, method, "multianewarray")?;
|
||||
push(state, vec![VerificationType::Object(descriptor.clone())])
|
||||
}
|
||||
Insn::ArrayLength => {
|
||||
pop_slots(state, 1, class, method, "arraylength")?;
|
||||
push(state, vec![VerificationType::Integer])
|
||||
}
|
||||
Insn::AThrow => {
|
||||
pop_slots(state, 1, class, method, "athrow")?;
|
||||
state.stack.clear();
|
||||
Ok(())
|
||||
}
|
||||
Insn::CheckCast(name) => {
|
||||
pop_slots(state, 1, class, method, "checkcast")?;
|
||||
push(state, vec![VerificationType::Object(name.clone())])
|
||||
}
|
||||
Insn::InstanceOf(_) => {
|
||||
pop_slots(state, 1, class, method, "instanceof")?;
|
||||
push(state, vec![VerificationType::Integer])
|
||||
}
|
||||
Insn::MonitorEnter | Insn::MonitorExit => {
|
||||
pop_slots(state, 1, class, method, "monitor")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
161
crates/noxis-classfile/src/format/frames/xfer/stack.rs
Normal file
161
crates/noxis-classfile/src/format/frames/xfer/stack.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
//! Стек и locals: снятие/укладка слотов, `dup*`.
|
||||
|
||||
use super::super::{VerificationType, invalid};
|
||||
use super::State;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::SlotKind;
|
||||
|
||||
/// Запись в local с учётом ширины (`long`/`double` — парой с `Top`).
|
||||
pub(super) fn store_local(
|
||||
state: &mut State,
|
||||
slot: u16,
|
||||
kind: SlotKind,
|
||||
taken: Vec<VerificationType>,
|
||||
class: &str,
|
||||
method: &str,
|
||||
) -> Result<(), ClassfileError> {
|
||||
let wide = kind == SlotKind::Long || kind == SlotKind::Double;
|
||||
if taken.len() != usize::from(wide) + 1 {
|
||||
return Err(invalid(class, method, "ширина store не совпала"));
|
||||
}
|
||||
let mut taken = taken;
|
||||
local_set(state, slot, taken.remove(0), class, method)?;
|
||||
if wide {
|
||||
local_set(
|
||||
state,
|
||||
slot.checked_add(1)
|
||||
.ok_or_else(|| invalid(class, method, "слот вне u16"))?,
|
||||
VerificationType::Top,
|
||||
class,
|
||||
method,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn push(state: &mut State, slots: Vec<VerificationType>) -> Result<(), ClassfileError> {
|
||||
state.stack.extend(slots);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Снять `n` слотов (снизу вверх); нехватка — честная ошибка.
|
||||
pub(super) fn pop_slots(
|
||||
state: &mut State,
|
||||
n: usize,
|
||||
class: &str,
|
||||
method: &str,
|
||||
what: &str,
|
||||
) -> Result<Vec<VerificationType>, ClassfileError> {
|
||||
if state.stack.len() < n {
|
||||
return Err(invalid(
|
||||
class,
|
||||
method,
|
||||
&format!("переполнение стека вниз в {what}"),
|
||||
));
|
||||
}
|
||||
Ok(state.stack.split_off(state.stack.len() - n))
|
||||
}
|
||||
|
||||
pub(super) fn local_get(
|
||||
state: &State,
|
||||
slot: u16,
|
||||
class: &str,
|
||||
method: &str,
|
||||
) -> Result<VerificationType, ClassfileError> {
|
||||
state
|
||||
.locals
|
||||
.get(slot as usize)
|
||||
.cloned()
|
||||
.ok_or_else(|| invalid(class, method, &format!("чтение слота {slot} вне locals")))
|
||||
}
|
||||
|
||||
pub(super) fn local_set(
|
||||
state: &mut State,
|
||||
slot: u16,
|
||||
value: VerificationType,
|
||||
class: &str,
|
||||
method: &str,
|
||||
) -> Result<(), ClassfileError> {
|
||||
let cell = state
|
||||
.locals
|
||||
.get_mut(slot as usize)
|
||||
.ok_or_else(|| invalid(class, method, &format!("запись слота {slot} вне locals")))?;
|
||||
*cell = value;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Значение local'а целиком: `long`/`double` — парой с `Top`.
|
||||
pub(super) fn wide_push(value: VerificationType) -> Vec<VerificationType> {
|
||||
match value {
|
||||
VerificationType::Long | VerificationType::Double => vec![value, VerificationType::Top],
|
||||
_ => vec![value],
|
||||
}
|
||||
}
|
||||
|
||||
/// `dup*` над значениями-категории-1: снять по позициям `take`, вернуть `put`.
|
||||
pub(super) fn dup(
|
||||
state: &mut State,
|
||||
class: &str,
|
||||
method: &str,
|
||||
take: &[usize],
|
||||
put: &[usize],
|
||||
) -> Result<(), ClassfileError> {
|
||||
let depth = take.iter().max().copied().unwrap_or(0);
|
||||
let taken = pop_slots(state, depth, class, method, "dup")?;
|
||||
if taken.contains(&VerificationType::Top) {
|
||||
return Err(invalid(class, method, "dup над половиной"));
|
||||
}
|
||||
let mut out = Vec::with_capacity(put.len());
|
||||
for position in put {
|
||||
out.push(taken[depth - position].clone());
|
||||
}
|
||||
push(state, out)
|
||||
}
|
||||
|
||||
/// `dup2*`: форма выбирается по вершине (`Top` — вторая половина `long`).
|
||||
pub(super) fn dup2(
|
||||
state: &mut State,
|
||||
class: &str,
|
||||
method: &str,
|
||||
deep: bool,
|
||||
deeper: bool,
|
||||
) -> Result<(), ClassfileError> {
|
||||
let wide = state.stack.last() == Some(&VerificationType::Top);
|
||||
if wide {
|
||||
let depth = if deeper {
|
||||
4
|
||||
} else if deep {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let taken = pop_slots(state, depth, class, method, "dup2")?;
|
||||
if taken[depth - 1] != VerificationType::Top {
|
||||
return Err(invalid(class, method, "dup2: нет половины"));
|
||||
}
|
||||
let value = taken[depth - 2].clone();
|
||||
if !matches!(value, VerificationType::Long | VerificationType::Double) {
|
||||
return Err(invalid(class, method, "dup2: половина без long/double"));
|
||||
}
|
||||
let mut out = vec![value, VerificationType::Top];
|
||||
out.extend(taken);
|
||||
push(state, out)
|
||||
} else {
|
||||
let depth = if deeper {
|
||||
4
|
||||
} else if deep {
|
||||
3
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let taken = pop_slots(state, depth, class, method, "dup2")?;
|
||||
if taken.contains(&VerificationType::Top) {
|
||||
return Err(invalid(class, method, "dup2 над половиной"));
|
||||
}
|
||||
let (head, tail) = taken.split_at(depth - 2);
|
||||
let mut out = tail.to_vec();
|
||||
out.extend_from_slice(head);
|
||||
out.extend_from_slice(tail);
|
||||
push(state, out)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue