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)
|
||||
}
|
||||
}
|
||||
29
crates/noxis-classfile/src/format/mod.rs
Normal file
29
crates/noxis-classfile/src/format/mod.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! Чтение/запись бинарного формата `.class` (JVMS §4) через backend
|
||||
//! `ristretto_classfile`: обзор кандидатов и решение — `docs/stack.md`.
|
||||
|
||||
mod frames;
|
||||
mod parse;
|
||||
pub(crate) mod write;
|
||||
|
||||
pub use frames::{Hierarchy, StackMapFrame, VerificationType, compute_frames};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::model::ClassFile;
|
||||
|
||||
/// Разобрать один `.class` в [`ClassFile`].
|
||||
pub fn parse(bytes: &[u8]) -> Result<ClassFile, ClassfileError> {
|
||||
parse::class::parse_class(bytes)
|
||||
}
|
||||
|
||||
/// Записать [`ClassFile`] в байты, включая пересчёт `Code`/`StackMapTable`.
|
||||
pub fn write(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> {
|
||||
write::write_class(class)
|
||||
}
|
||||
|
||||
/// Классы, которые не изменялись, должны уезжать в выходной архив «как есть».
|
||||
///
|
||||
/// Это дешевле и безопаснее, чем round-trip через наш IR: нет риска потерять
|
||||
/// неизвестный атрибут. Реализация зависит от решения по backend (P1).
|
||||
pub fn passthrough_needed(class: &ClassFile) -> bool {
|
||||
class.methods.is_empty() && class.fields.is_empty()
|
||||
}
|
||||
204
crates/noxis-classfile/src/format/parse/attrs.rs
Normal file
204
crates/noxis-classfile/src/format/parse/attrs.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
//! Атрибуты backend'а → атрибуты нашего IR.
|
||||
//!
|
||||
//! `BootstrapMethods`, `EnclosingMethod`, `MethodParameters` и `ConstantValue`
|
||||
//! моделируются явно (в них лежат ссылки пула, переживающие пересборку);
|
||||
//! остальное структурированное, но нам ненужное, уезжает в `Raw` как есть.
|
||||
//! `Code` и `StackMapTable` здесь не обрабатываются — их разбирает вызывающий
|
||||
//! код метода (см. `super::class`).
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
|
||||
use super::pool::{
|
||||
class_name, const_from, is_interface, kind_code, name_and_type, ref_parts, utf8,
|
||||
};
|
||||
use crate::error::ClassfileError;
|
||||
use crate::model::{Attribute, BootstrapMethod, InnerClass, MethodParameter, RecordComponent};
|
||||
|
||||
/// Один атрибут backend'а → наш атрибут (кроме `Code`/`StackMapTable`).
|
||||
pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribute, ClassfileError> {
|
||||
Ok(match attr {
|
||||
RtAttr::SourceFile {
|
||||
source_file_index, ..
|
||||
} => Attribute::SourceFile(utf8(pool, *source_file_index)?),
|
||||
RtAttr::Signature {
|
||||
signature_index, ..
|
||||
} => Attribute::Signature(utf8(pool, *signature_index)?),
|
||||
RtAttr::Exceptions {
|
||||
exception_indexes, ..
|
||||
} => Attribute::Exceptions(
|
||||
exception_indexes
|
||||
.iter()
|
||||
.map(|index| class_name(pool, *index))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::InnerClasses { classes, .. } => Attribute::InnerClasses(
|
||||
classes
|
||||
.iter()
|
||||
.map(|class| inner(pool, class))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::EnclosingMethod {
|
||||
class_index,
|
||||
method_index,
|
||||
..
|
||||
} => Attribute::EnclosingMethod {
|
||||
class: class_name(pool, *class_index)?,
|
||||
method: enclosing_method(pool, *method_index)?,
|
||||
},
|
||||
RtAttr::MethodParameters { parameters, .. } => Attribute::MethodParameters(
|
||||
parameters
|
||||
.iter()
|
||||
.map(|param| method_param(pool, param))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::BootstrapMethods { methods, .. } => Attribute::BootstrapMethods(
|
||||
methods
|
||||
.iter()
|
||||
.map(|method| bootstrap(pool, method))
|
||||
.collect::<Result<_, _>>()?,
|
||||
),
|
||||
RtAttr::ConstantValue {
|
||||
constant_value_index,
|
||||
..
|
||||
} => Attribute::ConstantValue(const_from(pool, *constant_value_index)?),
|
||||
RtAttr::Deprecated { .. } => Attribute::Deprecated,
|
||||
RtAttr::Synthetic { .. } => Attribute::Synthetic,
|
||||
RtAttr::LineNumberTable { .. }
|
||||
| RtAttr::LocalVariableTable { .. }
|
||||
| RtAttr::LocalVariableTypeTable { .. } => {
|
||||
return Err(ClassfileError::Parse(
|
||||
"таблицы кода разбирает вызывающий код метода".into(),
|
||||
));
|
||||
}
|
||||
RtAttr::Record { records, .. } => Attribute::Record(
|
||||
records
|
||||
.iter()
|
||||
.map(|record| {
|
||||
Ok(RecordComponent {
|
||||
name: utf8(pool, record.name_index)?,
|
||||
descriptor: utf8(pool, record.descriptor_index)?,
|
||||
attributes: record
|
||||
.attributes
|
||||
.iter()
|
||||
.map(|attr| convert(attr, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
),
|
||||
RtAttr::Code { .. } | RtAttr::StackMapTable { .. } => {
|
||||
return Err(ClassfileError::Parse(
|
||||
"Code/StackMapTable разбирает вызывающий код метода".into(),
|
||||
));
|
||||
}
|
||||
other => raw(other)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Несмоделированный атрибут → `Raw`: имя + байты `info` (без заголовка).
|
||||
pub(super) fn raw(attr: &RtAttr) -> Result<Attribute, ClassfileError> {
|
||||
let mut bytes = Vec::new();
|
||||
attr.to_bytes(&mut bytes)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let info = bytes
|
||||
.get(6..)
|
||||
.ok_or_else(|| ClassfileError::Parse("короткая запись атрибута".into()))?;
|
||||
Ok(Attribute::Raw {
|
||||
name: attr.name().to_string(),
|
||||
data: info.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
fn inner(
|
||||
pool: &ConstantPool<'_>,
|
||||
class: &ristretto_classfile::attributes::InnerClass,
|
||||
) -> Result<InnerClass, ClassfileError> {
|
||||
Ok(InnerClass {
|
||||
inner: class_name(pool, class.class_info_index)?,
|
||||
outer: optional_class(pool, class.outer_class_info_index)?,
|
||||
inner_name: optional_utf8(pool, class.name_index)?,
|
||||
access_flags: class.access_flags.bits(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Метод объемлющего класса (`0` — только класс, без метода).
|
||||
fn enclosing_method(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
) -> Result<Option<(String, String)>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(name_and_type(pool, index)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn method_param(
|
||||
pool: &ConstantPool<'_>,
|
||||
param: &ristretto_classfile::attributes::MethodParameter,
|
||||
) -> Result<MethodParameter, ClassfileError> {
|
||||
Ok(MethodParameter {
|
||||
name: optional_utf8(pool, param.name_index)?,
|
||||
access_flags: param.access_flags.bits(),
|
||||
})
|
||||
}
|
||||
|
||||
fn bootstrap(
|
||||
pool: &ConstantPool<'_>,
|
||||
method: &ristretto_classfile::attributes::BootstrapMethod,
|
||||
) -> Result<BootstrapMethod, ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(method.bootstrap_method_ref)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let (kind, owner, name, descriptor, itf) = match constant {
|
||||
ristretto_classfile::Constant::MethodHandle {
|
||||
reference_kind,
|
||||
reference_index,
|
||||
} => {
|
||||
let (owner, name, descriptor) = ref_parts(pool, *reference_index)?;
|
||||
(
|
||||
kind_code(reference_kind),
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
is_interface(pool, *reference_index),
|
||||
)
|
||||
}
|
||||
other => {
|
||||
return Err(ClassfileError::Parse(format!(
|
||||
"bootstrap-ссылка обязана быть MethodHandle, найден {other:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok(BootstrapMethod {
|
||||
kind,
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
itf,
|
||||
args: method
|
||||
.arguments
|
||||
.iter()
|
||||
.map(|index| const_from(pool, *index))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Индекс пула, где `0` означает отсутствие значения.
|
||||
fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(class_name(pool, index)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Индекс `CONSTANT_Utf8`, где `0` означает отсутствие значения.
|
||||
fn optional_utf8(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(utf8(pool, index)?))
|
||||
}
|
||||
}
|
||||
109
crates/noxis-classfile/src/format/parse/class.rs
Normal file
109
crates/noxis-classfile/src/format/parse/class.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//! Класс целиком: backend `ClassFile` → наш [`ClassFile`](crate::model::ClassFile).
|
||||
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
use ristretto_classfile::{
|
||||
ClassFile as RtClass, ConstantPool, Field as RtField, Method as RtMethod,
|
||||
};
|
||||
|
||||
use super::attrs;
|
||||
use super::code::convert_code;
|
||||
use super::pool::{class_name, utf8};
|
||||
use crate::error::ClassfileError;
|
||||
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version};
|
||||
|
||||
/// Разобрать байты `.class` в наш IR.
|
||||
pub(crate) fn parse_class(bytes: &[u8]) -> Result<ClassFile, ClassfileError> {
|
||||
let rt = RtClass::from_bytes(bytes).map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let pool = &rt.constant_pool;
|
||||
let interfaces = rt
|
||||
.interfaces
|
||||
.iter()
|
||||
.map(|index| class_name(pool, *index))
|
||||
.collect::<Result<_, _>>()?;
|
||||
Ok(ClassFile {
|
||||
version: Version {
|
||||
minor: rt.version.minor(),
|
||||
major: rt.version.major(),
|
||||
},
|
||||
access_flags: rt.access_flags.bits(),
|
||||
this_class: class_name(pool, rt.this_class)?,
|
||||
super_class: optional_class(pool, rt.super_class)?,
|
||||
interfaces,
|
||||
fields: rt
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| convert_field(field, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
methods: rt
|
||||
.methods
|
||||
.iter()
|
||||
.map(|method| convert_method(method, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
attributes: convert_attrs(&rt.attributes, pool)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_field(field: &RtField, pool: &ConstantPool<'_>) -> Result<Field, ClassfileError> {
|
||||
Ok(Field {
|
||||
access_flags: field.access_flags.bits(),
|
||||
name: utf8(pool, field.name_index)?,
|
||||
descriptor: utf8(pool, field.descriptor_index)?,
|
||||
attributes: convert_attrs(&field.attributes, pool)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_method(method: &RtMethod, pool: &ConstantPool<'_>) -> Result<Method, ClassfileError> {
|
||||
let mut code: Option<Code> = None;
|
||||
let mut rest: Vec<Attribute> = Vec::new();
|
||||
for attr in &method.attributes {
|
||||
match attr {
|
||||
RtAttr::Code {
|
||||
max_stack,
|
||||
max_locals,
|
||||
code: insns,
|
||||
exception_table,
|
||||
attributes,
|
||||
..
|
||||
} => {
|
||||
if code.is_some() {
|
||||
return Err(ClassfileError::Parse("два Code в одном методе".into()));
|
||||
}
|
||||
code = Some(convert_code(
|
||||
*max_stack,
|
||||
*max_locals,
|
||||
insns,
|
||||
exception_table,
|
||||
attributes,
|
||||
pool,
|
||||
)?);
|
||||
}
|
||||
other => rest.push(attrs::convert(other, pool)?),
|
||||
}
|
||||
}
|
||||
Ok(Method {
|
||||
access_flags: method.access_flags.bits(),
|
||||
name: utf8(pool, method.name_index)?,
|
||||
descriptor: utf8(pool, method.descriptor_index)?,
|
||||
code,
|
||||
attributes: rest,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_attrs(
|
||||
attrs: &[RtAttr],
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Vec<Attribute>, ClassfileError> {
|
||||
attrs
|
||||
.iter()
|
||||
.map(|attr| attrs::convert(attr, pool))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Индекс суперкласса, где `0` — его отсутствие (`java/lang/Object`).
|
||||
fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(class_name(pool, index)?))
|
||||
}
|
||||
}
|
||||
112
crates/noxis-classfile/src/format/parse/code/frames.rs
Normal file
112
crates/noxis-classfile/src/format/parse/code/frames.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
//! Кадры `StackMapTable` при чтении: вариант в вариант, дельты как есть.
|
||||
//!
|
||||
//! Backend хранит кадры в номерах инструкций (см. `from_bytes_code_attributes`),
|
||||
//! наш IR — тоже: writer сам пересчитает их в байты при `to_bytes`.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::{StackFrame, VerificationType as RtType};
|
||||
|
||||
use super::super::pool::class_name;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::format::{StackMapFrame, VerificationType};
|
||||
|
||||
/// Кадры backend'а → наши (индексные дельты — как есть).
|
||||
pub(super) fn to_frames(
|
||||
frames: &[StackFrame],
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Vec<StackMapFrame>, ClassfileError> {
|
||||
frames.iter().map(|frame| to_frame(frame, pool)).collect()
|
||||
}
|
||||
|
||||
fn to_frame(frame: &StackFrame, pool: &ConstantPool<'_>) -> Result<StackMapFrame, ClassfileError> {
|
||||
Ok(match frame {
|
||||
StackFrame::SameFrame { frame_type } => StackMapFrame::Same {
|
||||
offset_delta: u32::from(*frame_type),
|
||||
},
|
||||
StackFrame::SameFrameExtended { offset_delta, .. } => StackMapFrame::Same {
|
||||
offset_delta: u32::from(*offset_delta),
|
||||
},
|
||||
StackFrame::SameLocals1StackItemFrame { frame_type, stack } => StackMapFrame::SameLocals {
|
||||
offset_delta: u32::from(*frame_type) - 64,
|
||||
local: single(stack, pool)?,
|
||||
},
|
||||
StackFrame::SameLocals1StackItemFrameExtended {
|
||||
offset_delta,
|
||||
stack,
|
||||
..
|
||||
} => StackMapFrame::SameLocals {
|
||||
offset_delta: u32::from(*offset_delta),
|
||||
local: single(stack, pool)?,
|
||||
},
|
||||
StackFrame::ChopFrame {
|
||||
frame_type,
|
||||
offset_delta,
|
||||
} => StackMapFrame::Chop {
|
||||
offset_delta: u32::from(*offset_delta),
|
||||
count: 251u8.wrapping_sub(*frame_type),
|
||||
},
|
||||
StackFrame::AppendFrame {
|
||||
offset_delta,
|
||||
locals,
|
||||
..
|
||||
} => StackMapFrame::Append {
|
||||
offset_delta: u32::from(*offset_delta),
|
||||
locals: types(locals, pool)?,
|
||||
},
|
||||
StackFrame::FullFrame {
|
||||
offset_delta,
|
||||
locals,
|
||||
stack,
|
||||
..
|
||||
} => StackMapFrame::Full {
|
||||
offset_delta: u32::from(*offset_delta),
|
||||
locals: types(locals, pool)?,
|
||||
stack: types(stack, pool)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Единственный элемент стека кадра (`same_locals_1_stack_item`).
|
||||
fn single(stack: &[RtType], pool: &ConstantPool<'_>) -> Result<VerificationType, ClassfileError> {
|
||||
let mut types = types(stack, pool)?.into_iter();
|
||||
match (types.next(), types.next()) {
|
||||
(Some(first), None) => Ok(first),
|
||||
_ => Err(ClassfileError::Parse(
|
||||
"в same_locals-кадре не один элемент стека".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn types(
|
||||
values: &[RtType],
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Vec<VerificationType>, ClassfileError> {
|
||||
values.iter().map(|value| vtype(value, pool)).collect()
|
||||
}
|
||||
|
||||
/// Тип верификации backend'а → наш тип (имя класса берётся из constant pool).
|
||||
fn vtype(value: &RtType, pool: &ConstantPool<'_>) -> Result<VerificationType, ClassfileError> {
|
||||
Ok(match value {
|
||||
RtType::Top => VerificationType::Top,
|
||||
RtType::Integer => VerificationType::Integer,
|
||||
RtType::Float => VerificationType::Float,
|
||||
RtType::Double => VerificationType::Double,
|
||||
RtType::Long => VerificationType::Long,
|
||||
RtType::Null => VerificationType::Null,
|
||||
RtType::UninitializedThis => VerificationType::UninitializedThis,
|
||||
RtType::Object { cpool_index } => VerificationType::Object(class_name(pool, *cpool_index)?),
|
||||
RtType::Uninitialized { offset } => VerificationType::Uninitialized(*offset),
|
||||
})
|
||||
}
|
||||
|
||||
/// Тип перехвата (`0` — catch-all для `finally`/`any`).
|
||||
pub(super) fn catch_type(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(class_name(pool, index)?))
|
||||
}
|
||||
}
|
||||
187
crates/noxis-classfile/src/format/parse/code/labels.rs
Normal file
187
crates/noxis-classfile/src/format/parse/code/labels.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
//! Метки тела метода: где обязаны стоять и как резолвятся.
|
||||
//!
|
||||
//! Backend нумерует инструкции по порядку — метки ставятся прямо на номера,
|
||||
//! без байтовых смещений.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::Label;
|
||||
|
||||
/// Номера инструкций, на которых обязана стоять метка.
|
||||
///
|
||||
/// `extra` — позиции отладочных таблиц (уже номерами): вставки пассов
|
||||
/// не должны оставлять их без меток.
|
||||
pub(super) fn label_indexes(
|
||||
code: &[Instruction],
|
||||
exception_table: &[ExceptionTableEntry],
|
||||
extra: &BTreeSet<u32>,
|
||||
) -> Result<BTreeSet<u32>, ClassfileError> {
|
||||
let mut indexes = BTreeSet::from([0u32]);
|
||||
for (position, instruction) in code.iter().enumerate() {
|
||||
let index = to_index(position)?;
|
||||
for target in branch_indexes(instruction, index) {
|
||||
indexes.insert(to_index_checked(target)?);
|
||||
}
|
||||
}
|
||||
for entry in exception_table {
|
||||
indexes.insert(u32::from(entry.range_pc.start));
|
||||
indexes.insert(u32::from(entry.range_pc.end));
|
||||
indexes.insert(u32::from(entry.handler_pc));
|
||||
}
|
||||
indexes.extend(extra.iter().copied());
|
||||
Ok(indexes)
|
||||
}
|
||||
|
||||
/// Цели ветвлений backend'а: для `if*`/`goto` — абсолютный номер, для `switch` — дельта.
|
||||
fn branch_indexes(instruction: &Instruction, index: u16) -> Vec<i32> {
|
||||
let base = i32::from(index);
|
||||
match instruction {
|
||||
Instruction::Tableswitch(table) => once(base + table.default)
|
||||
.chain(table.offsets.iter().map(|target| base + target))
|
||||
.collect(),
|
||||
Instruction::Lookupswitch(lookup) => once(base + lookup.default)
|
||||
.chain(lookup.pairs.values().map(|target| base + target))
|
||||
.collect(),
|
||||
Instruction::Ifeq(target)
|
||||
| Instruction::Ifne(target)
|
||||
| Instruction::Iflt(target)
|
||||
| Instruction::Ifge(target)
|
||||
| Instruction::Ifgt(target)
|
||||
| Instruction::Ifle(target)
|
||||
| Instruction::If_icmpeq(target)
|
||||
| Instruction::If_icmpne(target)
|
||||
| Instruction::If_icmplt(target)
|
||||
| Instruction::If_icmpge(target)
|
||||
| Instruction::If_icmpgt(target)
|
||||
| Instruction::If_icmple(target)
|
||||
| Instruction::If_acmpeq(target)
|
||||
| Instruction::If_acmpne(target)
|
||||
| Instruction::Ifnull(target)
|
||||
| Instruction::Ifnonnull(target)
|
||||
| Instruction::Goto(target)
|
||||
| Instruction::Jsr(target) => vec![i32::from(*target)],
|
||||
Instruction::Goto_w(target) | Instruction::Jsr_w(target) => vec![*target],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn once(value: i32) -> std::iter::Once<i32> {
|
||||
std::iter::once(value)
|
||||
}
|
||||
|
||||
/// Превратить отсортированный набор номеров в последовательные метки.
|
||||
pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
|
||||
indexes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(position, index)| (*index, position as u32))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Метка для номера инструкции.
|
||||
pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label, ClassfileError> {
|
||||
labels
|
||||
.get(&u32::from(index))
|
||||
.copied()
|
||||
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для инструкции {index}")))
|
||||
}
|
||||
|
||||
/// Позиции отладочных таблиц, которым нужны метки.
|
||||
pub(super) fn collect_debug_labels(
|
||||
attributes: &[RtAttr],
|
||||
starts: &[u16],
|
||||
code_length: u16,
|
||||
extra: &mut BTreeSet<u32>,
|
||||
) -> Result<(), ClassfileError> {
|
||||
for attr in attributes {
|
||||
match attr {
|
||||
RtAttr::LineNumberTable { line_numbers, .. } => {
|
||||
for line in line_numbers {
|
||||
extra.insert(u32::from(line.start_pc));
|
||||
}
|
||||
}
|
||||
RtAttr::LocalVariableTable { variables, .. } => {
|
||||
for var in variables {
|
||||
extra.insert(byte_to_index(starts, var.start_pc)?);
|
||||
extra.insert(end_index(starts, var.start_pc, var.length, code_length)?);
|
||||
}
|
||||
}
|
||||
RtAttr::LocalVariableTypeTable { variable_types, .. } => {
|
||||
for var in variable_types {
|
||||
extra.insert(byte_to_index(starts, var.start_pc)?);
|
||||
extra.insert(end_index(starts, var.start_pc, var.length, code_length)?);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Байт → номер инструкции (только границы инструкций).
|
||||
pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileError> {
|
||||
starts
|
||||
.iter()
|
||||
.position(|&start| start == byte)
|
||||
.and_then(|position| u32::try_from(position).ok())
|
||||
.ok_or_else(|| ClassfileError::Parse(format!("смещение {byte} мимо инструкции")))
|
||||
}
|
||||
|
||||
/// Конец диапазона `[start, start+length)`: индекс за концом.
|
||||
fn end_index(
|
||||
starts: &[u16],
|
||||
start: u16,
|
||||
length: u16,
|
||||
code_length: u16,
|
||||
) -> Result<u32, ClassfileError> {
|
||||
let end = start
|
||||
.checked_add(length)
|
||||
.ok_or_else(|| ClassfileError::Parse("диапазон вне метода".into()))?;
|
||||
if end == code_length {
|
||||
return u32::try_from(starts.len())
|
||||
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()));
|
||||
}
|
||||
byte_to_index(starts, end)
|
||||
}
|
||||
|
||||
/// Метка для байтового смещения отладочной таблицы.
|
||||
pub(super) fn resolve_byte(
|
||||
labels: &BTreeMap<u32, Label>,
|
||||
starts: &[u16],
|
||||
byte: u16,
|
||||
) -> Result<Label, ClassfileError> {
|
||||
let index = byte_to_index(starts, byte)?;
|
||||
labels
|
||||
.get(&index)
|
||||
.copied()
|
||||
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для смещения {byte}")))
|
||||
}
|
||||
|
||||
/// Метка конца диапазона отладочной таблицы.
|
||||
pub(super) fn resolve_end(
|
||||
labels: &BTreeMap<u32, Label>,
|
||||
starts: &[u16],
|
||||
start: u16,
|
||||
length: u16,
|
||||
code_length: u16,
|
||||
) -> Result<Label, ClassfileError> {
|
||||
let index = end_index(starts, start, length, code_length)?;
|
||||
labels
|
||||
.get(&index)
|
||||
.copied()
|
||||
.ok_or_else(|| ClassfileError::Parse(format!("нет метки конца {index}")))
|
||||
}
|
||||
|
||||
fn to_index(position: usize) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(position)
|
||||
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))
|
||||
}
|
||||
|
||||
fn to_index_checked(index: i32) -> Result<u32, ClassfileError> {
|
||||
u32::try_from(index)
|
||||
.map_err(|_| ClassfileError::Parse(format!("номер инструкции {index} вне тела метода")))
|
||||
}
|
||||
169
crates/noxis-classfile/src/format/parse/code/mod.rs
Normal file
169
crates/noxis-classfile/src/format/parse/code/mod.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
//! `Code` backend'а → тело метода нашего IR: метки, исключения, кадры.
|
||||
//!
|
||||
//! Backend уже перевёл байтовые смещения в номера инструкций — конвертеры
|
||||
//! работают прямо с ними: метки ставятся на целях переходов, границах
|
||||
//! try-блоков и позициях отладочных таблиц; кадры переносятся как есть
|
||||
//! (индексные дельты). Позиции `LineNumberTable`/`LocalVariableTable` —
|
||||
//! тоже метки, поэтому вставки пассов их не ломают (байты досчитает writer).
|
||||
|
||||
mod frames;
|
||||
mod labels;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::Cursor;
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
|
||||
|
||||
use super::attrs;
|
||||
use super::insns::convert;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::Insn;
|
||||
use crate::model::{
|
||||
Attribute, Code, ExceptionHandler, LineNumber, LocalVariable, LocalVariableType,
|
||||
};
|
||||
|
||||
use frames::{catch_type, to_frames};
|
||||
use labels::{assign, collect_debug_labels, label_indexes, lookup, resolve_byte, resolve_end};
|
||||
|
||||
/// Тело метода целиком: backend `Code` → наш [`Code`].
|
||||
pub(super) fn convert_code(
|
||||
max_stack: u16,
|
||||
max_locals: u16,
|
||||
code: &[Instruction],
|
||||
exception_table: &[ExceptionTableEntry],
|
||||
attributes: &[RtAttr],
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Code, ClassfileError> {
|
||||
let (starts, code_length) = layout(code)?;
|
||||
let mut extra = BTreeSet::new();
|
||||
collect_debug_labels(attributes, &starts, code_length, &mut extra)?;
|
||||
let labels = assign(&label_indexes(code, exception_table, &extra)?);
|
||||
let resolve = |index: u16| lookup(&labels, index);
|
||||
|
||||
let mut insns = Vec::with_capacity(code.len() + labels.len());
|
||||
for (position, instruction) in code.iter().enumerate() {
|
||||
let index = u16::try_from(position)
|
||||
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))?;
|
||||
if let Some(label) = labels.get(&u32::from(index)) {
|
||||
insns.push(Insn::Mark(*label));
|
||||
}
|
||||
insns.push(convert(instruction, index, pool, &resolve)?);
|
||||
}
|
||||
let end = u32::try_from(code.len())
|
||||
.map_err(|_| ClassfileError::Parse("в теле метода больше 65535 инструкций".into()))?;
|
||||
if let Some(label) = labels.get(&end) {
|
||||
insns.push(Insn::Mark(*label));
|
||||
}
|
||||
|
||||
let exception_table = exception_table
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
Ok(ExceptionHandler {
|
||||
start: lookup(&labels, entry.range_pc.start)?,
|
||||
end: lookup(&labels, entry.range_pc.end)?,
|
||||
handler: lookup(&labels, entry.handler_pc)?,
|
||||
catch_type: catch_type(pool, entry.catch_type)?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?;
|
||||
|
||||
let mut frames = Vec::new();
|
||||
let mut rest = Vec::new();
|
||||
for attr in attributes {
|
||||
match attr {
|
||||
RtAttr::StackMapTable { frames: found, .. } => {
|
||||
if !frames.is_empty() {
|
||||
return Err(ClassfileError::Parse(
|
||||
"два StackMapTable в одном Code".into(),
|
||||
));
|
||||
}
|
||||
frames = to_frames(found, pool)?;
|
||||
}
|
||||
RtAttr::LineNumberTable { line_numbers, .. } => rest.push(Attribute::LineNumberTable(
|
||||
line_numbers
|
||||
.iter()
|
||||
.map(|line| {
|
||||
Ok(LineNumber {
|
||||
start: lookup(&labels, line.start_pc)?,
|
||||
line: line.line_number,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
)),
|
||||
RtAttr::LocalVariableTable { variables, .. } => {
|
||||
rest.push(Attribute::LocalVariableTable(
|
||||
variables
|
||||
.iter()
|
||||
.map(|var| {
|
||||
Ok(LocalVariable {
|
||||
start: resolve_byte(&labels, &starts, var.start_pc)?,
|
||||
end: resolve_end(
|
||||
&labels,
|
||||
&starts,
|
||||
var.start_pc,
|
||||
var.length,
|
||||
code_length,
|
||||
)?,
|
||||
name: super::pool::utf8(pool, var.name_index)?,
|
||||
descriptor: super::pool::utf8(pool, var.descriptor_index)?,
|
||||
index: var.index,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
))
|
||||
}
|
||||
RtAttr::LocalVariableTypeTable { variable_types, .. } => {
|
||||
rest.push(Attribute::LocalVariableTypeTable(
|
||||
variable_types
|
||||
.iter()
|
||||
.map(|var| {
|
||||
Ok(LocalVariableType {
|
||||
start: resolve_byte(&labels, &starts, var.start_pc)?,
|
||||
end: resolve_end(
|
||||
&labels,
|
||||
&starts,
|
||||
var.start_pc,
|
||||
var.length,
|
||||
code_length,
|
||||
)?,
|
||||
name: super::pool::utf8(pool, var.name_index)?,
|
||||
signature: super::pool::utf8(pool, var.signature_index)?,
|
||||
index: var.index,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
));
|
||||
}
|
||||
other => rest.push(attrs::convert(other, pool)?),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Code {
|
||||
max_stack,
|
||||
max_locals,
|
||||
insns,
|
||||
exception_table,
|
||||
frames,
|
||||
attributes: rest,
|
||||
})
|
||||
}
|
||||
|
||||
/// Байтовые смещения инструкций и длина тела метода.
|
||||
fn layout(code: &[Instruction]) -> Result<(Vec<u16>, u16), ClassfileError> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
let mut offsets = Vec::with_capacity(code.len());
|
||||
for instruction in code {
|
||||
offsets.push(tell(&cursor)?);
|
||||
instruction
|
||||
.to_bytes(&mut cursor)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
}
|
||||
Ok((offsets, tell(&cursor)?))
|
||||
}
|
||||
|
||||
fn tell(cursor: &Cursor<Vec<u8>>) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(cursor.position())
|
||||
.map_err(|_| ClassfileError::Parse("тело метода длиннее 64 КБ".into()))
|
||||
}
|
||||
203
crates/noxis-classfile/src/format/parse/insns/basic.rs
Normal file
203
crates/noxis-classfile/src/format/parse/insns/basic.rs
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
//! Простые инструкции без ссылок на пул (кроме `ldc`) и без ветвлений.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Instruction;
|
||||
|
||||
use super::super::pool::const_from;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Arith, ArrayKind, Const, Convert, Insn, Prim, SlotKind};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
pub(super) fn convert_basic(
|
||||
instruction: &Instruction,
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Option<Insn>, ClassfileError> {
|
||||
Ok(Some(match instruction {
|
||||
Instruction::Nop => Insn::Nop,
|
||||
Instruction::Aconst_null => Insn::AconstNull,
|
||||
Instruction::Iconst_m1 => Insn::Const(Const::Int(-1)),
|
||||
Instruction::Iconst_0 => Insn::Const(Const::Int(0)),
|
||||
Instruction::Iconst_1 => Insn::Const(Const::Int(1)),
|
||||
Instruction::Iconst_2 => Insn::Const(Const::Int(2)),
|
||||
Instruction::Iconst_3 => Insn::Const(Const::Int(3)),
|
||||
Instruction::Iconst_4 => Insn::Const(Const::Int(4)),
|
||||
Instruction::Iconst_5 => Insn::Const(Const::Int(5)),
|
||||
Instruction::Lconst_0 => Insn::Const(Const::Long(0)),
|
||||
Instruction::Lconst_1 => Insn::Const(Const::Long(1)),
|
||||
Instruction::Fconst_0 => Insn::Const(Const::Float(0.0)),
|
||||
Instruction::Fconst_1 => Insn::Const(Const::Float(1.0)),
|
||||
Instruction::Fconst_2 => Insn::Const(Const::Float(2.0)),
|
||||
Instruction::Dconst_0 => Insn::Const(Const::Double(0.0)),
|
||||
Instruction::Dconst_1 => Insn::Const(Const::Double(1.0)),
|
||||
Instruction::Bipush(v) => Insn::Const(Const::Int(i32::from(*v))),
|
||||
Instruction::Sipush(v) => Insn::Const(Const::Int(i32::from(*v))),
|
||||
Instruction::Ldc(i) => Insn::Const(const_from(pool, u16::from(*i))?),
|
||||
Instruction::Ldc_w(i) | Instruction::Ldc2_w(i) => Insn::Const(const_from(pool, *i)?),
|
||||
Instruction::Iload(i) => load(u16::from(*i), SlotKind::Int),
|
||||
Instruction::Lload(i) => load(u16::from(*i), SlotKind::Long),
|
||||
Instruction::Fload(i) => load(u16::from(*i), SlotKind::Float),
|
||||
Instruction::Dload(i) => load(u16::from(*i), SlotKind::Double),
|
||||
Instruction::Aload(i) => load(u16::from(*i), SlotKind::Ref),
|
||||
Instruction::Iload_w(i) => load(*i, SlotKind::Int),
|
||||
Instruction::Lload_w(i) => load(*i, SlotKind::Long),
|
||||
Instruction::Fload_w(i) => load(*i, SlotKind::Float),
|
||||
Instruction::Dload_w(i) => load(*i, SlotKind::Double),
|
||||
Instruction::Aload_w(i) => load(*i, SlotKind::Ref),
|
||||
Instruction::Iload_0 => load(0, SlotKind::Int),
|
||||
Instruction::Iload_1 => load(1, SlotKind::Int),
|
||||
Instruction::Iload_2 => load(2, SlotKind::Int),
|
||||
Instruction::Iload_3 => load(3, SlotKind::Int),
|
||||
Instruction::Lload_0 => load(0, SlotKind::Long),
|
||||
Instruction::Lload_1 => load(1, SlotKind::Long),
|
||||
Instruction::Lload_2 => load(2, SlotKind::Long),
|
||||
Instruction::Lload_3 => load(3, SlotKind::Long),
|
||||
Instruction::Fload_0 => load(0, SlotKind::Float),
|
||||
Instruction::Fload_1 => load(1, SlotKind::Float),
|
||||
Instruction::Fload_2 => load(2, SlotKind::Float),
|
||||
Instruction::Fload_3 => load(3, SlotKind::Float),
|
||||
Instruction::Dload_0 => load(0, SlotKind::Double),
|
||||
Instruction::Dload_1 => load(1, SlotKind::Double),
|
||||
Instruction::Dload_2 => load(2, SlotKind::Double),
|
||||
Instruction::Dload_3 => load(3, SlotKind::Double),
|
||||
Instruction::Aload_0 => load(0, SlotKind::Ref),
|
||||
Instruction::Aload_1 => load(1, SlotKind::Ref),
|
||||
Instruction::Aload_2 => load(2, SlotKind::Ref),
|
||||
Instruction::Aload_3 => load(3, SlotKind::Ref),
|
||||
Instruction::Istore(i) => store(u16::from(*i), SlotKind::Int),
|
||||
Instruction::Lstore(i) => store(u16::from(*i), SlotKind::Long),
|
||||
Instruction::Fstore(i) => store(u16::from(*i), SlotKind::Float),
|
||||
Instruction::Dstore(i) => store(u16::from(*i), SlotKind::Double),
|
||||
Instruction::Astore(i) => store(u16::from(*i), SlotKind::Ref),
|
||||
Instruction::Istore_w(i) => store(*i, SlotKind::Int),
|
||||
Instruction::Lstore_w(i) => store(*i, SlotKind::Long),
|
||||
Instruction::Fstore_w(i) => store(*i, SlotKind::Float),
|
||||
Instruction::Dstore_w(i) => store(*i, SlotKind::Double),
|
||||
Instruction::Astore_w(i) => store(*i, SlotKind::Ref),
|
||||
Instruction::Istore_0 => store(0, SlotKind::Int),
|
||||
Instruction::Istore_1 => store(1, SlotKind::Int),
|
||||
Instruction::Istore_2 => store(2, SlotKind::Int),
|
||||
Instruction::Istore_3 => store(3, SlotKind::Int),
|
||||
Instruction::Lstore_0 => store(0, SlotKind::Long),
|
||||
Instruction::Lstore_1 => store(1, SlotKind::Long),
|
||||
Instruction::Lstore_2 => store(2, SlotKind::Long),
|
||||
Instruction::Lstore_3 => store(3, SlotKind::Long),
|
||||
Instruction::Fstore_0 => store(0, SlotKind::Float),
|
||||
Instruction::Fstore_1 => store(1, SlotKind::Float),
|
||||
Instruction::Fstore_2 => store(2, SlotKind::Float),
|
||||
Instruction::Fstore_3 => store(3, SlotKind::Float),
|
||||
Instruction::Dstore_0 => store(0, SlotKind::Double),
|
||||
Instruction::Dstore_1 => store(1, SlotKind::Double),
|
||||
Instruction::Dstore_2 => store(2, SlotKind::Double),
|
||||
Instruction::Dstore_3 => store(3, SlotKind::Double),
|
||||
Instruction::Astore_0 => store(0, SlotKind::Ref),
|
||||
Instruction::Astore_1 => store(1, SlotKind::Ref),
|
||||
Instruction::Astore_2 => store(2, SlotKind::Ref),
|
||||
Instruction::Astore_3 => store(3, SlotKind::Ref),
|
||||
Instruction::Iinc(i, d) => Insn::Iinc {
|
||||
index: u16::from(*i),
|
||||
delta: i32::from(*d),
|
||||
},
|
||||
Instruction::Iinc_w(i, d) => Insn::Iinc {
|
||||
index: *i,
|
||||
delta: i32::from(*d),
|
||||
},
|
||||
Instruction::Baload => Insn::ArrayLoad(ArrayKind::Byte),
|
||||
Instruction::Caload => Insn::ArrayLoad(ArrayKind::Char),
|
||||
Instruction::Saload => Insn::ArrayLoad(ArrayKind::Short),
|
||||
Instruction::Iaload => Insn::ArrayLoad(ArrayKind::Int),
|
||||
Instruction::Laload => Insn::ArrayLoad(ArrayKind::Long),
|
||||
Instruction::Faload => Insn::ArrayLoad(ArrayKind::Float),
|
||||
Instruction::Daload => Insn::ArrayLoad(ArrayKind::Double),
|
||||
Instruction::Aaload => Insn::ArrayLoad(ArrayKind::Reference),
|
||||
Instruction::Bastore => Insn::ArrayStore(ArrayKind::Byte),
|
||||
Instruction::Castore => Insn::ArrayStore(ArrayKind::Char),
|
||||
Instruction::Sastore => Insn::ArrayStore(ArrayKind::Short),
|
||||
Instruction::Iastore => Insn::ArrayStore(ArrayKind::Int),
|
||||
Instruction::Lastore => Insn::ArrayStore(ArrayKind::Long),
|
||||
Instruction::Fastore => Insn::ArrayStore(ArrayKind::Float),
|
||||
Instruction::Dastore => Insn::ArrayStore(ArrayKind::Double),
|
||||
Instruction::Aastore => Insn::ArrayStore(ArrayKind::Reference),
|
||||
Instruction::Pop => Insn::Pop,
|
||||
Instruction::Pop2 => Insn::Pop2,
|
||||
Instruction::Dup => Insn::Dup,
|
||||
Instruction::Dup_x1 => Insn::DupX1,
|
||||
Instruction::Dup_x2 => Insn::DupX2,
|
||||
Instruction::Dup2 => Insn::Dup2,
|
||||
Instruction::Dup2_x1 => Insn::Dup2X1,
|
||||
Instruction::Dup2_x2 => Insn::Dup2X2,
|
||||
Instruction::Swap => Insn::Swap,
|
||||
Instruction::Iadd => binary(Prim::Int, Arith::Add),
|
||||
Instruction::Isub => binary(Prim::Int, Arith::Sub),
|
||||
Instruction::Imul => binary(Prim::Int, Arith::Mul),
|
||||
Instruction::Idiv => binary(Prim::Int, Arith::Div),
|
||||
Instruction::Irem => binary(Prim::Int, Arith::Rem),
|
||||
Instruction::Ineg => binary(Prim::Int, Arith::Neg),
|
||||
Instruction::Iand => binary(Prim::Int, Arith::And),
|
||||
Instruction::Ior => binary(Prim::Int, Arith::Or),
|
||||
Instruction::Ixor => binary(Prim::Int, Arith::Xor),
|
||||
Instruction::Ishl => binary(Prim::Int, Arith::Shl),
|
||||
Instruction::Ishr => binary(Prim::Int, Arith::Shr),
|
||||
Instruction::Iushr => binary(Prim::Int, Arith::Ushr),
|
||||
Instruction::Ladd => binary(Prim::Long, Arith::Add),
|
||||
Instruction::Lsub => binary(Prim::Long, Arith::Sub),
|
||||
Instruction::Lmul => binary(Prim::Long, Arith::Mul),
|
||||
Instruction::Ldiv => binary(Prim::Long, Arith::Div),
|
||||
Instruction::Lrem => binary(Prim::Long, Arith::Rem),
|
||||
Instruction::Lneg => binary(Prim::Long, Arith::Neg),
|
||||
Instruction::Land => binary(Prim::Long, Arith::And),
|
||||
Instruction::Lor => binary(Prim::Long, Arith::Or),
|
||||
Instruction::Lxor => binary(Prim::Long, Arith::Xor),
|
||||
Instruction::Lshl => binary(Prim::Long, Arith::Shl),
|
||||
Instruction::Lshr => binary(Prim::Long, Arith::Shr),
|
||||
Instruction::Lushr => binary(Prim::Long, Arith::Ushr),
|
||||
Instruction::Fadd => binary(Prim::Float, Arith::Add),
|
||||
Instruction::Fsub => binary(Prim::Float, Arith::Sub),
|
||||
Instruction::Fmul => binary(Prim::Float, Arith::Mul),
|
||||
Instruction::Fdiv => binary(Prim::Float, Arith::Div),
|
||||
Instruction::Frem => binary(Prim::Float, Arith::Rem),
|
||||
Instruction::Fneg => binary(Prim::Float, Arith::Neg),
|
||||
Instruction::Dadd => binary(Prim::Double, Arith::Add),
|
||||
Instruction::Dsub => binary(Prim::Double, Arith::Sub),
|
||||
Instruction::Dmul => binary(Prim::Double, Arith::Mul),
|
||||
Instruction::Ddiv => binary(Prim::Double, Arith::Div),
|
||||
Instruction::Drem => binary(Prim::Double, Arith::Rem),
|
||||
Instruction::Dneg => binary(Prim::Double, Arith::Neg),
|
||||
Instruction::Lcmp => cmp(Prim::Long, false),
|
||||
Instruction::Fcmpl => cmp(Prim::Float, false),
|
||||
Instruction::Fcmpg => cmp(Prim::Float, true),
|
||||
Instruction::Dcmpl => cmp(Prim::Double, false),
|
||||
Instruction::Dcmpg => cmp(Prim::Double, true),
|
||||
Instruction::I2l => Insn::Convert(Convert::I2L),
|
||||
Instruction::I2f => Insn::Convert(Convert::I2F),
|
||||
Instruction::I2d => Insn::Convert(Convert::I2D),
|
||||
Instruction::I2b => Insn::Convert(Convert::I2B),
|
||||
Instruction::I2c => Insn::Convert(Convert::I2C),
|
||||
Instruction::I2s => Insn::Convert(Convert::I2S),
|
||||
Instruction::L2i => Insn::Convert(Convert::L2I),
|
||||
Instruction::L2f => Insn::Convert(Convert::L2F),
|
||||
Instruction::L2d => Insn::Convert(Convert::L2D),
|
||||
Instruction::F2i => Insn::Convert(Convert::F2I),
|
||||
Instruction::F2l => Insn::Convert(Convert::F2L),
|
||||
Instruction::F2d => Insn::Convert(Convert::F2D),
|
||||
Instruction::D2i => Insn::Convert(Convert::D2I),
|
||||
Instruction::D2l => Insn::Convert(Convert::D2L),
|
||||
Instruction::D2f => Insn::Convert(Convert::D2F),
|
||||
_ => return Ok(None),
|
||||
}))
|
||||
}
|
||||
|
||||
fn load(slot: u16, kind: SlotKind) -> Insn {
|
||||
Insn::Load { slot, kind }
|
||||
}
|
||||
|
||||
fn store(slot: u16, kind: SlotKind) -> Insn {
|
||||
Insn::Store { slot, kind }
|
||||
}
|
||||
|
||||
fn binary(prim: Prim, op: Arith) -> Insn {
|
||||
Insn::Binary { prim, op }
|
||||
}
|
||||
|
||||
fn cmp(prim: Prim, nan_great: bool) -> Insn {
|
||||
Insn::Cmp { prim, nan_great }
|
||||
}
|
||||
98
crates/noxis-classfile/src/format/parse/insns/flow.rs
Normal file
98
crates/noxis-classfile/src/format/parse/insns/flow.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
//! Ветвления, `switch` и возвраты: абсолютные/относительные цели → метки.
|
||||
|
||||
use ristretto_classfile::attributes::{Instruction, LookupSwitch, TableSwitch};
|
||||
|
||||
use super::Resolver;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{BranchCond, Cond, Insn, Label, Prim};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
pub(super) fn convert_flow(
|
||||
instruction: &Instruction,
|
||||
index: u16,
|
||||
resolve: &Resolver<'_>,
|
||||
) -> Result<Option<Insn>, ClassfileError> {
|
||||
let rel = |delta: i32| -> Result<Label, ClassfileError> { abs(index, delta, resolve) };
|
||||
Ok(Some(match instruction {
|
||||
Instruction::Ifeq(t) => branch(BranchCond::Zero(Cond::Eq), *t, resolve)?,
|
||||
Instruction::Ifne(t) => branch(BranchCond::Zero(Cond::Ne), *t, resolve)?,
|
||||
Instruction::Iflt(t) => branch(BranchCond::Zero(Cond::Lt), *t, resolve)?,
|
||||
Instruction::Ifge(t) => branch(BranchCond::Zero(Cond::Ge), *t, resolve)?,
|
||||
Instruction::Ifgt(t) => branch(BranchCond::Zero(Cond::Gt), *t, resolve)?,
|
||||
Instruction::Ifle(t) => branch(BranchCond::Zero(Cond::Le), *t, resolve)?,
|
||||
Instruction::If_icmpeq(t) => branch(BranchCond::Int(Cond::Eq), *t, resolve)?,
|
||||
Instruction::If_icmpne(t) => branch(BranchCond::Int(Cond::Ne), *t, resolve)?,
|
||||
Instruction::If_icmplt(t) => branch(BranchCond::Int(Cond::Lt), *t, resolve)?,
|
||||
Instruction::If_icmpge(t) => branch(BranchCond::Int(Cond::Ge), *t, resolve)?,
|
||||
Instruction::If_icmpgt(t) => branch(BranchCond::Int(Cond::Gt), *t, resolve)?,
|
||||
Instruction::If_icmple(t) => branch(BranchCond::Int(Cond::Le), *t, resolve)?,
|
||||
Instruction::If_acmpeq(t) => branch(BranchCond::Ref { eq: true }, *t, resolve)?,
|
||||
Instruction::If_acmpne(t) => branch(BranchCond::Ref { eq: false }, *t, resolve)?,
|
||||
Instruction::Ifnull(t) => branch(BranchCond::Null { is_null: true }, *t, resolve)?,
|
||||
Instruction::Ifnonnull(t) => branch(BranchCond::Null { is_null: false }, *t, resolve)?,
|
||||
Instruction::Goto(t) => Insn::Goto(resolve(*t)?),
|
||||
Instruction::Goto_w(t) => Insn::Goto(resolve(wide(*t)?)?),
|
||||
Instruction::Jsr(t) => Insn::Jsr(resolve(*t)?),
|
||||
Instruction::Jsr_w(t) => Insn::Jsr(resolve(wide(*t)?)?),
|
||||
Instruction::Ret(i) => Insn::Ret(u16::from(*i)),
|
||||
Instruction::Ret_w(i) => Insn::Ret(*i),
|
||||
Instruction::Tableswitch(s) => switch_table(s, &rel)?,
|
||||
Instruction::Lookupswitch(s) => switch_lookup(s, &rel)?,
|
||||
Instruction::Return => Insn::Return(None),
|
||||
Instruction::Ireturn => Insn::Return(Some(Prim::Int)),
|
||||
Instruction::Lreturn => Insn::Return(Some(Prim::Long)),
|
||||
Instruction::Freturn => Insn::Return(Some(Prim::Float)),
|
||||
Instruction::Dreturn => Insn::Return(Some(Prim::Double)),
|
||||
Instruction::Areturn => Insn::AReturn,
|
||||
_ => return Ok(None),
|
||||
}))
|
||||
}
|
||||
|
||||
fn branch(cond: BranchCond, target: u16, resolve: &Resolver<'_>) -> Result<Insn, ClassfileError> {
|
||||
Ok(Insn::Branch(cond, resolve(target)?))
|
||||
}
|
||||
|
||||
/// Цель `switch` — номер инструкции: база плюс относительная дельта backend'а.
|
||||
fn abs(index: u16, delta: i32, resolve: &Resolver<'_>) -> Result<Label, ClassfileError> {
|
||||
let absolute = i32::from(index)
|
||||
.checked_add(delta)
|
||||
.ok_or_else(|| ClassfileError::Parse("переполнение смещения ветвления".into()))?;
|
||||
let index = u16::try_from(absolute)
|
||||
.map_err(|_| ClassfileError::Parse("смещение ветвления вне диапазона u16".into()))?;
|
||||
resolve(index)
|
||||
}
|
||||
|
||||
fn wide(target: i32) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(target)
|
||||
.map_err(|_| ClassfileError::Parse("широкий переход вне диапазона u16".into()))
|
||||
}
|
||||
|
||||
fn switch_table(
|
||||
table: &TableSwitch,
|
||||
rel: &dyn Fn(i32) -> Result<Label, ClassfileError>,
|
||||
) -> Result<Insn, ClassfileError> {
|
||||
Ok(Insn::TableSwitch {
|
||||
default: rel(table.default)?,
|
||||
low: table.low,
|
||||
high: table.high,
|
||||
targets: table
|
||||
.offsets
|
||||
.iter()
|
||||
.map(|offset| rel(*offset))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn switch_lookup(
|
||||
lookup: &LookupSwitch,
|
||||
rel: &dyn Fn(i32) -> Result<Label, ClassfileError>,
|
||||
) -> Result<Insn, ClassfileError> {
|
||||
Ok(Insn::LookupSwitch {
|
||||
default: rel(lookup.default)?,
|
||||
pairs: lookup
|
||||
.pairs
|
||||
.iter()
|
||||
.map(|(key, offset)| Ok((*key, rel(*offset)?)))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
48
crates/noxis-classfile/src/format/parse/insns/mod.rs
Normal file
48
crates/noxis-classfile/src/format/parse/insns/mod.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//! `Instruction` backend'а → [`Insn`](crate::insn::Insn) нашего IR.
|
||||
//!
|
||||
//! Смещения ветвлений приходят из backend'а (абсолютные для `if*`/`goto`,
|
||||
//! относительные от начала инструкции для `switch`), поэтому конвертация
|
||||
//! принимает резолвер смещения в [`Label`](crate::insn::Label) — метки
|
||||
//! назначает вызывающий код.
|
||||
|
||||
mod basic;
|
||||
mod flow;
|
||||
mod refs;
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Instruction;
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label};
|
||||
|
||||
/// Резолвит абсолютное смещение байткода в метку.
|
||||
pub(super) type Resolver<'a> = dyn Fn(u16) -> Result<Label, ClassfileError> + 'a;
|
||||
|
||||
/// Конвертировать одну инструкцию. `index` — её номер в теле метода (нужно `switch`).
|
||||
pub(super) fn convert(
|
||||
instruction: &Instruction,
|
||||
index: u16,
|
||||
pool: &ConstantPool<'_>,
|
||||
resolve: &Resolver<'_>,
|
||||
) -> Result<Insn, ClassfileError> {
|
||||
if let Some(insn) = basic::convert_basic(instruction, pool)? {
|
||||
return Ok(insn);
|
||||
}
|
||||
if let Some(insn) = flow::convert_flow(instruction, index, resolve)? {
|
||||
return Ok(insn);
|
||||
}
|
||||
if let Some(insn) = refs::convert_ref(instruction, pool)? {
|
||||
return Ok(insn);
|
||||
}
|
||||
match instruction {
|
||||
Instruction::Breakpoint | Instruction::Impdep1 | Instruction::Impdep2 => Err(
|
||||
ClassfileError::Parse("зарезервированный опкод в коде метода".into()),
|
||||
),
|
||||
Instruction::Wide => Err(ClassfileError::Parse(
|
||||
"wide обязан быть развёрнут парсером backend'а".into(),
|
||||
)),
|
||||
other => Err(ClassfileError::Parse(format!(
|
||||
"неизвестная инструкция backend'а: {other:?}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
102
crates/noxis-classfile/src/format/parse/insns/refs.rs
Normal file
102
crates/noxis-classfile/src/format/parse/insns/refs.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
//! Поля, вызовы и создание объектов: индексы пула → строки IR.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::{ArrayType, Instruction};
|
||||
|
||||
use super::super::pool::{class_name, dynamic_parts, expect_kind, is_interface, ref_parts};
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{ArrayKind, Insn, InvokeKind};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
pub(super) fn convert_ref(
|
||||
instruction: &Instruction,
|
||||
pool: &ConstantPool<'_>,
|
||||
) -> Result<Option<Insn>, ClassfileError> {
|
||||
Ok(Some(match instruction {
|
||||
Instruction::Getstatic(i) => {
|
||||
let (owner, name, descriptor) = ref_parts(pool, *i)?;
|
||||
Insn::GetStatic {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
}
|
||||
}
|
||||
Instruction::Putstatic(i) => {
|
||||
let (owner, name, descriptor) = ref_parts(pool, *i)?;
|
||||
Insn::PutStatic {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
}
|
||||
}
|
||||
Instruction::Getfield(i) => {
|
||||
let (owner, name, descriptor) = ref_parts(pool, *i)?;
|
||||
Insn::GetField {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
}
|
||||
}
|
||||
Instruction::Putfield(i) => {
|
||||
let (owner, name, descriptor) = ref_parts(pool, *i)?;
|
||||
Insn::PutField {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
}
|
||||
}
|
||||
Instruction::Invokevirtual(i) => invoke(pool, *i, InvokeKind::Virtual)?,
|
||||
Instruction::Invokespecial(i) => invoke(pool, *i, InvokeKind::Special)?,
|
||||
Instruction::Invokestatic(i) => invoke(pool, *i, InvokeKind::Static)?,
|
||||
Instruction::Invokeinterface(i, _) => invoke(pool, *i, InvokeKind::Interface)?,
|
||||
Instruction::Invokedynamic(i) => dynamic(pool, *i)?,
|
||||
Instruction::New(i) => Insn::New(class_name(pool, *i)?),
|
||||
Instruction::Newarray(t) => Insn::NewArray(array_kind(t.clone())),
|
||||
Instruction::Anewarray(i) => Insn::ANewArray(class_name(pool, *i)?),
|
||||
Instruction::Multianewarray(i, dims) => Insn::MultiANewArray {
|
||||
descriptor: class_name(pool, *i)?,
|
||||
dims: *dims,
|
||||
},
|
||||
Instruction::Arraylength => Insn::ArrayLength,
|
||||
Instruction::Athrow => Insn::AThrow,
|
||||
Instruction::Checkcast(i) => Insn::CheckCast(class_name(pool, *i)?),
|
||||
Instruction::Instanceof(i) => Insn::InstanceOf(class_name(pool, *i)?),
|
||||
Instruction::Monitorenter => Insn::MonitorEnter,
|
||||
Instruction::Monitorexit => Insn::MonitorExit,
|
||||
_ => return Ok(None),
|
||||
}))
|
||||
}
|
||||
|
||||
fn invoke(pool: &ConstantPool<'_>, index: u16, kind: InvokeKind) -> Result<Insn, ClassfileError> {
|
||||
expect_kind(pool, index, kind)?;
|
||||
let (owner, name, descriptor) = ref_parts(pool, index)?;
|
||||
Ok(Insn::Invoke {
|
||||
kind,
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
itf: is_interface(pool, index),
|
||||
})
|
||||
}
|
||||
|
||||
fn dynamic(pool: &ConstantPool<'_>, index: u16) -> Result<Insn, ClassfileError> {
|
||||
let (name, descriptor, bootstrap) = dynamic_parts(pool, index)?;
|
||||
Ok(Insn::InvokeDynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
})
|
||||
}
|
||||
|
||||
fn array_kind(kind: ArrayType) -> ArrayKind {
|
||||
match kind {
|
||||
ArrayType::Boolean => ArrayKind::Boolean,
|
||||
ArrayType::Char => ArrayKind::Char,
|
||||
ArrayType::Float => ArrayKind::Float,
|
||||
ArrayType::Double => ArrayKind::Double,
|
||||
ArrayType::Byte => ArrayKind::Byte,
|
||||
ArrayType::Short => ArrayKind::Short,
|
||||
ArrayType::Int => ArrayKind::Int,
|
||||
ArrayType::Long => ArrayKind::Long,
|
||||
}
|
||||
}
|
||||
12
crates/noxis-classfile/src/format/parse/mod.rs
Normal file
12
crates/noxis-classfile/src/format/parse/mod.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//! Backend `ristretto_classfile` → наш IR: пул, инструкции, тела методов.
|
||||
//!
|
||||
//! Пока ниоткуда не подключён (P1): `format` вернёт `Unimplemented`, пока не
|
||||
//! будут готовы `parse`/`write` классового уровня поверх этих конвертеров.
|
||||
//! Дерево уже разбито по ответственностям, чтобы подключение было одним
|
||||
//! `mod parse` без рефакторинга.
|
||||
|
||||
pub(super) mod attrs;
|
||||
pub(super) mod class;
|
||||
pub(super) mod code;
|
||||
pub(super) mod insns;
|
||||
pub(super) mod pool;
|
||||
184
crates/noxis-classfile/src/format/parse/pool.rs
Normal file
184
crates/noxis-classfile/src/format/parse/pool.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
//! Разрешение индексов constant pool в строки нашего IR.
|
||||
|
||||
use ristretto_classfile::{Constant, ConstantPool, ReferenceKind};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, InvokeKind};
|
||||
|
||||
/// Имя класса по `CONSTANT_Class`.
|
||||
pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
|
||||
let name = pool
|
||||
.try_get_class(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
Ok(name.to_string())
|
||||
}
|
||||
|
||||
/// `(owner, name, descriptor)` для `CONSTANT_Fieldref`/`_Methodref`.
|
||||
pub(super) fn ref_parts(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
) -> Result<(String, String, String), ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let (class_index, name_and_type_index) = match constant {
|
||||
Constant::FieldRef {
|
||||
class_index,
|
||||
name_and_type_index,
|
||||
}
|
||||
| Constant::MethodRef {
|
||||
class_index,
|
||||
name_and_type_index,
|
||||
}
|
||||
| Constant::InterfaceMethodRef {
|
||||
class_index,
|
||||
name_and_type_index,
|
||||
} => (*class_index, *name_and_type_index),
|
||||
other => {
|
||||
return Err(ClassfileError::Parse(format!(
|
||||
"ожидалась ссылка на поле/метод, найден {other:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let owner = class_name(pool, class_index)?;
|
||||
let (name, descriptor) = name_and_type(pool, name_and_type_index)?;
|
||||
Ok((owner, name, descriptor))
|
||||
}
|
||||
|
||||
/// `(name, descriptor)` для `CONSTANT_NameAndType`.
|
||||
pub(super) fn name_and_type(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
) -> Result<(String, String), ClassfileError> {
|
||||
let (name_index, descriptor_index) = pool
|
||||
.try_get_name_and_type(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let name = utf8(pool, *name_index)?;
|
||||
let descriptor = utf8(pool, *descriptor_index)?;
|
||||
Ok((name, descriptor))
|
||||
}
|
||||
|
||||
/// Строка из `CONSTANT_Utf8`.
|
||||
pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
|
||||
let value = pool
|
||||
.try_get_utf8(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
Ok(value.to_string())
|
||||
}
|
||||
|
||||
/// Константа для `ldc`/`ldc_w`/`ldc2_w`.
|
||||
pub(super) fn const_from(pool: &ConstantPool<'_>, index: u16) -> Result<Const, ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
Ok(match constant {
|
||||
Constant::Integer(v) => Const::Int(*v),
|
||||
Constant::Float(v) => Const::Float(*v),
|
||||
Constant::Long(v) => Const::Long(*v),
|
||||
Constant::Double(v) => Const::Double(*v),
|
||||
Constant::String(v) => Const::String(utf8(pool, *v)?),
|
||||
Constant::Class(v) => Const::Class(utf8(pool, *v)?),
|
||||
Constant::MethodType(v) => Const::MethodType(utf8(pool, *v)?),
|
||||
Constant::MethodHandle {
|
||||
reference_kind,
|
||||
reference_index,
|
||||
} => Const::MethodHandle {
|
||||
kind: kind_code(reference_kind),
|
||||
owner: ref_parts(pool, *reference_index)?.0,
|
||||
name: ref_parts(pool, *reference_index)?.1,
|
||||
descriptor: ref_parts(pool, *reference_index)?.2,
|
||||
itf: matches!(
|
||||
pool.try_get(*reference_index),
|
||||
Ok(Constant::InterfaceMethodRef { .. })
|
||||
),
|
||||
},
|
||||
Constant::Dynamic {
|
||||
bootstrap_method_attr_index,
|
||||
name_and_type_index,
|
||||
} => {
|
||||
let (name, descriptor) = name_and_type(pool, *name_and_type_index)?;
|
||||
Const::Dynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap: *bootstrap_method_attr_index,
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ClassfileError::Parse(format!(
|
||||
"константа {other:?} недопустима в ldc"
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// `(name, descriptor, bootstrap)` для `CONSTANT_InvokeDynamic`.
|
||||
pub(super) fn dynamic_parts(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
) -> Result<(String, String, u16), ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let (bootstrap, name_and_type_index) = match constant {
|
||||
Constant::InvokeDynamic {
|
||||
bootstrap_method_attr_index,
|
||||
name_and_type_index,
|
||||
} => (*bootstrap_method_attr_index, *name_and_type_index),
|
||||
other => {
|
||||
return Err(ClassfileError::Parse(format!(
|
||||
"ожидалась InvokeDynamic, найден {other:?}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
let (name, descriptor) = name_and_type(pool, name_and_type_index)?;
|
||||
Ok((name, descriptor, bootstrap))
|
||||
}
|
||||
|
||||
/// Вид вызова по индексу ссылки: интерфейсная или обычная.
|
||||
pub(super) fn is_interface(pool: &ConstantPool<'_>, index: u16) -> bool {
|
||||
matches!(pool.try_get(index), Ok(Constant::InterfaceMethodRef { .. }))
|
||||
}
|
||||
|
||||
/// JVMS §5.4.3.5: номер `reference_kind` для `CONSTANT_MethodHandle`.
|
||||
pub(super) fn kind_code(kind: &ReferenceKind) -> u8 {
|
||||
match kind {
|
||||
ReferenceKind::GetField => 1,
|
||||
ReferenceKind::GetStatic => 2,
|
||||
ReferenceKind::PutField => 3,
|
||||
ReferenceKind::PutStatic => 4,
|
||||
ReferenceKind::InvokeVirtual => 5,
|
||||
ReferenceKind::InvokeStatic => 6,
|
||||
ReferenceKind::InvokeSpecial => 7,
|
||||
ReferenceKind::NewInvokeSpecial => 8,
|
||||
ReferenceKind::InvokeInterface => 9,
|
||||
}
|
||||
}
|
||||
|
||||
/// Проверка, что `Invoke` действительно ссылается на ожидаемый вид.
|
||||
///
|
||||
/// JVM принимает `Methodref` и `InterfaceMethodref` для всех видов вызова,
|
||||
/// кроме `invokeinterface` (там только интерфейсная); строгость сверх JVM
|
||||
/// ломает чтение реальных классов (статические методы интерфейсов).
|
||||
pub(super) fn expect_kind(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
kind: InvokeKind,
|
||||
) -> Result<(), ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(index)
|
||||
.map_err(|e| ClassfileError::Parse(e.to_string()))?;
|
||||
let expected = match kind {
|
||||
InvokeKind::Interface => matches!(constant, Constant::InterfaceMethodRef { .. }),
|
||||
_ => matches!(
|
||||
constant,
|
||||
Constant::MethodRef { .. } | Constant::InterfaceMethodRef { .. }
|
||||
),
|
||||
};
|
||||
if expected {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ClassfileError::Parse(format!(
|
||||
"вызов {kind:?} указывает на несовместимую ссылку"
|
||||
)))
|
||||
}
|
||||
}
|
||||
250
crates/noxis-classfile/src/format/write/attrs.rs
Normal file
250
crates/noxis-classfile/src/format/write/attrs.rs
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
//! Атрибуты нашего IR → атрибуты backend'а.
|
||||
//!
|
||||
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend
|
||||
//! хранит только `name_index`, строка восстанавливается при чтении.
|
||||
|
||||
use ristretto_classfile::MethodAccessFlags;
|
||||
use ristretto_classfile::attributes::{
|
||||
Attribute as RtAttr, BootstrapMethod as RtBsm, InnerClass as RtInner, LineNumber as RtLine,
|
||||
LocalVariableTable as RtLocal, LocalVariableTypeTable as RtLocalType,
|
||||
MethodParameter as RtParam, NestedClassAccessFlags, Record as RtRecord,
|
||||
};
|
||||
|
||||
use super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, Label};
|
||||
use crate::model::{
|
||||
Attribute, BootstrapMethod, InnerClass, LineNumber, LocalVariable, LocalVariableType,
|
||||
MethodParameter,
|
||||
};
|
||||
|
||||
/// Позиции таблиц кода: метки → байты текущего layout.
|
||||
pub(super) struct Positions<'a> {
|
||||
pub resolve: &'a dyn Fn(Label) -> Result<u32, ClassfileError>,
|
||||
pub byte_of: &'a dyn Fn(u32) -> Result<u16, ClassfileError>,
|
||||
}
|
||||
|
||||
/// `LineNumberTable`: backend хранит номера инструкций и маппит сам.
|
||||
pub(super) fn line_number_table(
|
||||
lines: &[LineNumber],
|
||||
pool: &mut Pool,
|
||||
positions: &Positions<'_>,
|
||||
) -> Result<RtAttr, ClassfileError> {
|
||||
Ok(RtAttr::LineNumberTable {
|
||||
name_index: pool.utf8("LineNumberTable")?,
|
||||
line_numbers: lines
|
||||
.iter()
|
||||
.map(|line| {
|
||||
let index = (positions.resolve)(line.start)?;
|
||||
Ok(RtLine {
|
||||
start_pc: u16::try_from(index).map_err(|_| {
|
||||
ClassfileError::Write(format!("номер инструкции {index} вне u16"))
|
||||
})?,
|
||||
line_number: line.line,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// `LocalVariableTable`/`TypeTable` с пересчётом диапазона.
|
||||
pub(super) fn local_table(
|
||||
vars: &[LocalVariable],
|
||||
pool: &mut Pool,
|
||||
positions: &Positions<'_>,
|
||||
) -> Result<RtAttr, ClassfileError> {
|
||||
Ok(RtAttr::LocalVariableTable {
|
||||
name_index: pool.utf8("LocalVariableTable")?,
|
||||
variables: vars
|
||||
.iter()
|
||||
.map(|var| {
|
||||
let (start_pc, length) = range(var.start, var.end, positions)?;
|
||||
Ok(RtLocal {
|
||||
start_pc,
|
||||
length,
|
||||
name_index: pool.utf8(&var.name)?,
|
||||
descriptor_index: pool.utf8(&var.descriptor)?,
|
||||
index: var.index,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
})
|
||||
}
|
||||
/// Та же таблица для типов (`signature` вместо дескриптора).
|
||||
pub(super) fn local_type_table(
|
||||
vars: &[LocalVariableType],
|
||||
pool: &mut Pool,
|
||||
positions: &Positions<'_>,
|
||||
) -> Result<RtAttr, ClassfileError> {
|
||||
Ok(RtAttr::LocalVariableTypeTable {
|
||||
name_index: pool.utf8("LocalVariableTypeTable")?,
|
||||
variable_types: vars
|
||||
.iter()
|
||||
.map(|var| {
|
||||
let (start_pc, length) = range(var.start, var.end, positions)?;
|
||||
Ok(RtLocalType {
|
||||
start_pc,
|
||||
length,
|
||||
name_index: pool.utf8(&var.name)?,
|
||||
signature_index: pool.utf8(&var.signature)?,
|
||||
index: var.index,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn range(
|
||||
start: Label,
|
||||
end: Label,
|
||||
positions: &Positions<'_>,
|
||||
) -> Result<(u16, u16), ClassfileError> {
|
||||
let first = (positions.byte_of)((positions.resolve)(start)?)?;
|
||||
let last = (positions.byte_of)((positions.resolve)(end)?)?;
|
||||
let length = last
|
||||
.checked_sub(first)
|
||||
.ok_or_else(|| ClassfileError::Write("конец раньше начала".into()))?;
|
||||
Ok((first, length))
|
||||
}
|
||||
|
||||
/// Один наш атрибут → атрибут backend'а.
|
||||
pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
|
||||
Ok(match attr {
|
||||
Attribute::SourceFile(name) => RtAttr::SourceFile {
|
||||
name_index: pool.utf8("SourceFile")?,
|
||||
source_file_index: pool.utf8(name)?,
|
||||
},
|
||||
Attribute::Signature(value) => RtAttr::Signature {
|
||||
name_index: pool.utf8("Signature")?,
|
||||
signature_index: pool.utf8(value)?,
|
||||
},
|
||||
Attribute::Exceptions(names) => RtAttr::Exceptions {
|
||||
name_index: pool.utf8("Exceptions")?,
|
||||
exception_indexes: names
|
||||
.iter()
|
||||
.map(|name| pool.class(name))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::Deprecated => RtAttr::Deprecated {
|
||||
name_index: pool.utf8("Deprecated")?,
|
||||
},
|
||||
Attribute::Synthetic => RtAttr::Synthetic {
|
||||
name_index: pool.utf8("Synthetic")?,
|
||||
},
|
||||
Attribute::InnerClasses(classes) => RtAttr::InnerClasses {
|
||||
name_index: pool.utf8("InnerClasses")?,
|
||||
classes: classes
|
||||
.iter()
|
||||
.map(|class| inner(class, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::ConstantValue(value) => RtAttr::ConstantValue {
|
||||
name_index: pool.utf8("ConstantValue")?,
|
||||
constant_value_index: pool.constant(value)?,
|
||||
},
|
||||
Attribute::BootstrapMethods(methods) => RtAttr::BootstrapMethods {
|
||||
name_index: pool.utf8("BootstrapMethods")?,
|
||||
methods: methods
|
||||
.iter()
|
||||
.map(|method| bootstrap(method, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::EnclosingMethod { class, method } => RtAttr::EnclosingMethod {
|
||||
name_index: pool.utf8("EnclosingMethod")?,
|
||||
class_index: pool.class(class)?,
|
||||
method_index: match method {
|
||||
None => 0,
|
||||
Some((name, descriptor)) => pool.name_and_type(name, descriptor)?,
|
||||
},
|
||||
},
|
||||
Attribute::MethodParameters(params) => RtAttr::MethodParameters {
|
||||
name_index: pool.utf8("MethodParameters")?,
|
||||
parameters: params
|
||||
.iter()
|
||||
.map(|param| method_param(param, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
},
|
||||
Attribute::Raw { name, data } => RtAttr::Unknown {
|
||||
name_index: pool.utf8(name)?,
|
||||
info: data.clone(),
|
||||
},
|
||||
Attribute::LineNumberTable(_)
|
||||
| Attribute::LocalVariableTable(_)
|
||||
| Attribute::LocalVariableTypeTable(_) => {
|
||||
return Err(ClassfileError::Write(
|
||||
"таблицы кода пишет вызывающий код метода".into(),
|
||||
));
|
||||
}
|
||||
Attribute::Record(records) => RtAttr::Record {
|
||||
name_index: pool.utf8("Record")?,
|
||||
records: records
|
||||
.iter()
|
||||
.map(|record| {
|
||||
Ok(RtRecord {
|
||||
name_index: pool.utf8(&record.name)?,
|
||||
descriptor_index: pool.utf8(&record.descriptor)?,
|
||||
attributes: record
|
||||
.attributes
|
||||
.iter()
|
||||
.map(|attr| convert(attr, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
})
|
||||
.collect::<Result<_, ClassfileError>>()?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn inner(class: &InnerClass, pool: &mut Pool) -> Result<RtInner, ClassfileError> {
|
||||
Ok(RtInner {
|
||||
class_info_index: pool.class(&class.inner)?,
|
||||
outer_class_info_index: match &class.outer {
|
||||
None => 0,
|
||||
Some(name) => pool.class(name)?,
|
||||
},
|
||||
name_index: match &class.inner_name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: NestedClassAccessFlags::from_bits_truncate(class.access_flags),
|
||||
})
|
||||
}
|
||||
|
||||
fn bootstrap(method: &BootstrapMethod, pool: &mut Pool) -> Result<RtBsm, ClassfileError> {
|
||||
Ok(RtBsm {
|
||||
bootstrap_method_ref: pool.method_handle(
|
||||
method.kind,
|
||||
&method.owner,
|
||||
&method.name,
|
||||
&method.descriptor,
|
||||
method.itf,
|
||||
)?,
|
||||
arguments: method
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| bootstrap_arg(arg, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Аргумент bootstrap-метода: те же константы, что в `ldc`, плюс `Dynamic`.
|
||||
fn bootstrap_arg(arg: &Const, pool: &mut Pool) -> Result<u16, ClassfileError> {
|
||||
match arg {
|
||||
Const::Dynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
} => pool.dynamic(*bootstrap, name, descriptor),
|
||||
_ => pool.constant(arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn method_param(param: &MethodParameter, pool: &mut Pool) -> Result<RtParam, ClassfileError> {
|
||||
Ok(RtParam {
|
||||
name_index: match ¶m.name {
|
||||
None => 0,
|
||||
Some(name) => pool.utf8(name)?,
|
||||
},
|
||||
access_flags: MethodAccessFlags::from_bits_truncate(param.access_flags),
|
||||
})
|
||||
}
|
||||
111
crates/noxis-classfile/src/format/write/code/frames.rs
Normal file
111
crates/noxis-classfile/src/format/write/code/frames.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
//! Кадры `StackMapTable` при записи: вариант в вариант, дельты как есть.
|
||||
//!
|
||||
//! Наш IR хранит кадры в номерах инструкций (как backend после чтения) —
|
||||
//! backend сам пересчитает их в байты при `to_bytes`.
|
||||
|
||||
use ristretto_classfile::attributes::{StackFrame as RtFrame, VerificationType as RtType};
|
||||
|
||||
use super::super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::format::{StackMapFrame, VerificationType};
|
||||
|
||||
/// Кадры IR → кадры backend'а (индексные дельты — как есть).
|
||||
pub(super) fn convert_frames(
|
||||
frames: &[StackMapFrame],
|
||||
pool: &mut Pool,
|
||||
) -> Result<Vec<RtFrame>, ClassfileError> {
|
||||
frames
|
||||
.iter()
|
||||
.map(|frame| build_frame(frame, pool))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build_frame(frame: &StackMapFrame, pool: &mut Pool) -> Result<RtFrame, ClassfileError> {
|
||||
let delta = frame_delta(frame);
|
||||
let offset = u16::try_from(delta)
|
||||
.map_err(|_| ClassfileError::Write(format!("дельта кадра {delta} вне u16")))?;
|
||||
Ok(match frame {
|
||||
StackMapFrame::Same { .. } => {
|
||||
if delta <= 63 {
|
||||
RtFrame::SameFrame {
|
||||
frame_type: delta as u8,
|
||||
}
|
||||
} else {
|
||||
RtFrame::SameFrameExtended {
|
||||
frame_type: 251,
|
||||
offset_delta: offset,
|
||||
}
|
||||
}
|
||||
}
|
||||
StackMapFrame::SameLocals { local, .. } => {
|
||||
let stack = vec![vtype(local, pool)?];
|
||||
if delta <= 63 {
|
||||
RtFrame::SameLocals1StackItemFrame {
|
||||
frame_type: 64 + delta as u8,
|
||||
stack,
|
||||
}
|
||||
} else {
|
||||
RtFrame::SameLocals1StackItemFrameExtended {
|
||||
frame_type: 247,
|
||||
offset_delta: offset,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
StackMapFrame::Chop { count, .. } => RtFrame::ChopFrame {
|
||||
frame_type: 251u8.wrapping_sub(*count),
|
||||
offset_delta: offset,
|
||||
},
|
||||
StackMapFrame::Append { locals, .. } => {
|
||||
let count = u8::try_from(locals.len()).map_err(|_| {
|
||||
ClassfileError::Write(format!("append-кадр на {} local'ов", locals.len()))
|
||||
})?;
|
||||
if !(1..=3).contains(&count) {
|
||||
return Err(ClassfileError::Write(format!(
|
||||
"append-кадр на {count} local'ов (нужно 1–3)"
|
||||
)));
|
||||
}
|
||||
RtFrame::AppendFrame {
|
||||
frame_type: 251 + count,
|
||||
offset_delta: offset,
|
||||
locals: types(locals, pool)?,
|
||||
}
|
||||
}
|
||||
StackMapFrame::Full { locals, stack, .. } => RtFrame::FullFrame {
|
||||
frame_type: 255,
|
||||
offset_delta: offset,
|
||||
locals: types(locals, pool)?,
|
||||
stack: types(stack, pool)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn frame_delta(frame: &StackMapFrame) -> u32 {
|
||||
match *frame {
|
||||
StackMapFrame::Same { offset_delta } => offset_delta,
|
||||
StackMapFrame::SameLocals { offset_delta, .. } => offset_delta,
|
||||
StackMapFrame::Chop { offset_delta, .. } => offset_delta,
|
||||
StackMapFrame::Append { offset_delta, .. } => offset_delta,
|
||||
StackMapFrame::Full { offset_delta, .. } => offset_delta,
|
||||
}
|
||||
}
|
||||
|
||||
fn types(values: &[VerificationType], pool: &mut Pool) -> Result<Vec<RtType>, ClassfileError> {
|
||||
values.iter().map(|value| vtype(value, pool)).collect()
|
||||
}
|
||||
|
||||
fn vtype(value: &VerificationType, pool: &mut Pool) -> Result<RtType, ClassfileError> {
|
||||
Ok(match value {
|
||||
VerificationType::Top => RtType::Top,
|
||||
VerificationType::Integer => RtType::Integer,
|
||||
VerificationType::Float => RtType::Float,
|
||||
VerificationType::Double => RtType::Double,
|
||||
VerificationType::Long => RtType::Long,
|
||||
VerificationType::Null => RtType::Null,
|
||||
VerificationType::UninitializedThis => RtType::UninitializedThis,
|
||||
VerificationType::Object(name) => RtType::Object {
|
||||
cpool_index: pool.class(name)?,
|
||||
},
|
||||
VerificationType::Uninitialized(offset) => RtType::Uninitialized { offset: *offset },
|
||||
})
|
||||
}
|
||||
197
crates/noxis-classfile/src/format/write/code/insns.rs
Normal file
197
crates/noxis-classfile/src/format/write/code/insns.rs
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
//! Наш [`Insn`](crate::insn::Insn) → `Instruction` backend'а.
|
||||
//!
|
||||
//! Обратная сторона `super::super::parse::insns`: метки резолвятся в
|
||||
//! абсолютные номера инструкций (`switch` — в дельты от себя), константы
|
||||
//! и ссылки уходят в пул через [`Pool`](super::super::pool::Pool).
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use ristretto_classfile::attributes::{Instruction as RtInsn, LookupSwitch, TableSwitch};
|
||||
|
||||
use super::super::pool::Pool;
|
||||
use super::ops::{array_load, array_store, array_type, binary, cmp, convert_op, int_const, invoke};
|
||||
use super::{branch, delta, fit_index, ret_op};
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label, SlotKind};
|
||||
|
||||
/// Одна инструкция IR → инструкция backend'а (`None` — маркер метки).
|
||||
///
|
||||
/// `index` — номер инструкции в методе (нужен `switch`-дельтам).
|
||||
pub(crate) fn convert(
|
||||
insn: &Insn,
|
||||
index: u32,
|
||||
pool: &mut Pool,
|
||||
resolve: &impl Fn(Label) -> Result<u32, ClassfileError>,
|
||||
) -> Result<Option<RtInsn>, ClassfileError> {
|
||||
Ok(Some(match insn {
|
||||
Insn::Mark(_) => return Ok(None),
|
||||
Insn::Nop => RtInsn::Nop,
|
||||
Insn::AconstNull => RtInsn::Aconst_null,
|
||||
Insn::Const(value) => int_const(value, pool)?,
|
||||
Insn::Load { slot, kind } => load(*slot, *kind),
|
||||
Insn::Store { slot, kind } => store(*slot, *kind),
|
||||
Insn::Iinc { index, delta } => iinc(*index, *delta)?,
|
||||
Insn::ArrayLoad(kind) => array_load(*kind),
|
||||
Insn::ArrayStore(kind) => array_store(*kind),
|
||||
Insn::Pop => RtInsn::Pop,
|
||||
Insn::Pop2 => RtInsn::Pop2,
|
||||
Insn::Dup => RtInsn::Dup,
|
||||
Insn::DupX1 => RtInsn::Dup_x1,
|
||||
Insn::DupX2 => RtInsn::Dup_x2,
|
||||
Insn::Dup2 => RtInsn::Dup2,
|
||||
Insn::Dup2X1 => RtInsn::Dup2_x1,
|
||||
Insn::Dup2X2 => RtInsn::Dup2_x2,
|
||||
Insn::Swap => RtInsn::Swap,
|
||||
Insn::Binary { prim, op } => binary(*prim, *op)?,
|
||||
Insn::Cmp { prim, nan_great } => cmp(*prim, *nan_great)?,
|
||||
Insn::Convert(op) => convert_op(*op),
|
||||
Insn::Branch(cond, target) => branch(*cond, resolve(*target)?)?,
|
||||
Insn::Goto(target) => RtInsn::Goto(fit_index(resolve(*target)?)?),
|
||||
Insn::Jsr(target) => RtInsn::Jsr(fit_index(resolve(*target)?)?),
|
||||
Insn::Ret(slot) => ret(*slot),
|
||||
Insn::TableSwitch {
|
||||
default,
|
||||
low,
|
||||
high,
|
||||
targets,
|
||||
} => RtInsn::Tableswitch(Box::new(TableSwitch {
|
||||
default: delta(index, resolve(*default)?)?,
|
||||
low: *low,
|
||||
high: *high,
|
||||
offsets: targets
|
||||
.iter()
|
||||
.map(|target| delta(index, resolve(*target)?))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})),
|
||||
Insn::LookupSwitch { default, pairs } => RtInsn::Lookupswitch(Box::new(LookupSwitch {
|
||||
default: delta(index, resolve(*default)?)?,
|
||||
pairs: pairs
|
||||
.iter()
|
||||
.map(|(key, target)| Ok((*key, delta(index, resolve(*target)?)?)))
|
||||
.collect::<Result<IndexMap<_, _>, ClassfileError>>()?,
|
||||
})),
|
||||
Insn::Return(prim) => ret_op(*prim),
|
||||
Insn::AReturn => RtInsn::Areturn,
|
||||
Insn::GetStatic {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
} => RtInsn::Getstatic(pool.field_ref(owner, name, descriptor)?),
|
||||
Insn::PutStatic {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
} => RtInsn::Putstatic(pool.field_ref(owner, name, descriptor)?),
|
||||
Insn::GetField {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
} => RtInsn::Getfield(pool.field_ref(owner, name, descriptor)?),
|
||||
Insn::PutField {
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
} => RtInsn::Putfield(pool.field_ref(owner, name, descriptor)?),
|
||||
Insn::Invoke {
|
||||
kind,
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
..
|
||||
} => invoke(*kind, owner, name, descriptor, pool)?,
|
||||
Insn::InvokeDynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
} => RtInsn::Invokedynamic(pool.invoke_dynamic(*bootstrap, name, descriptor)?),
|
||||
Insn::New(name) => RtInsn::New(pool.class(name)?),
|
||||
Insn::NewArray(kind) => RtInsn::Newarray(array_type(*kind)?),
|
||||
Insn::ANewArray(name) => RtInsn::Anewarray(pool.class(name)?),
|
||||
Insn::MultiANewArray { descriptor, dims } => {
|
||||
RtInsn::Multianewarray(pool.class(descriptor)?, *dims)
|
||||
}
|
||||
Insn::ArrayLength => RtInsn::Arraylength,
|
||||
Insn::AThrow => RtInsn::Athrow,
|
||||
Insn::CheckCast(name) => RtInsn::Checkcast(pool.class(name)?),
|
||||
Insn::InstanceOf(name) => RtInsn::Instanceof(pool.class(name)?),
|
||||
Insn::MonitorEnter => RtInsn::Monitorenter,
|
||||
Insn::MonitorExit => RtInsn::Monitorexit,
|
||||
}))
|
||||
}
|
||||
|
||||
fn load(slot: u16, kind: SlotKind) -> RtInsn {
|
||||
match (kind, slot) {
|
||||
(SlotKind::Int, 0) => RtInsn::Iload_0,
|
||||
(SlotKind::Int, 1) => RtInsn::Iload_1,
|
||||
(SlotKind::Int, 2) => RtInsn::Iload_2,
|
||||
(SlotKind::Int, 3) => RtInsn::Iload_3,
|
||||
(SlotKind::Long, 0) => RtInsn::Lload_0,
|
||||
(SlotKind::Long, 1) => RtInsn::Lload_1,
|
||||
(SlotKind::Long, 2) => RtInsn::Lload_2,
|
||||
(SlotKind::Long, 3) => RtInsn::Lload_3,
|
||||
(SlotKind::Float, 0) => RtInsn::Fload_0,
|
||||
(SlotKind::Float, 1) => RtInsn::Fload_1,
|
||||
(SlotKind::Float, 2) => RtInsn::Fload_2,
|
||||
(SlotKind::Float, 3) => RtInsn::Fload_3,
|
||||
(SlotKind::Double, 0) => RtInsn::Dload_0,
|
||||
(SlotKind::Double, 1) => RtInsn::Dload_1,
|
||||
(SlotKind::Double, 2) => RtInsn::Dload_2,
|
||||
(SlotKind::Double, 3) => RtInsn::Dload_3,
|
||||
(SlotKind::Ref, 0) => RtInsn::Aload_0,
|
||||
(SlotKind::Ref, 1) => RtInsn::Aload_1,
|
||||
(SlotKind::Ref, 2) => RtInsn::Aload_2,
|
||||
(SlotKind::Ref, 3) => RtInsn::Aload_3,
|
||||
(SlotKind::Int, s) => narrow(s, RtInsn::Iload, RtInsn::Iload_w),
|
||||
(SlotKind::Long, s) => narrow(s, RtInsn::Lload, RtInsn::Lload_w),
|
||||
(SlotKind::Float, s) => narrow(s, RtInsn::Fload, RtInsn::Fload_w),
|
||||
(SlotKind::Double, s) => narrow(s, RtInsn::Dload, RtInsn::Dload_w),
|
||||
(SlotKind::Ref, s) => narrow(s, RtInsn::Aload, RtInsn::Aload_w),
|
||||
}
|
||||
}
|
||||
|
||||
fn store(slot: u16, kind: SlotKind) -> RtInsn {
|
||||
match (kind, slot) {
|
||||
(SlotKind::Int, 0) => RtInsn::Istore_0,
|
||||
(SlotKind::Int, 1) => RtInsn::Istore_1,
|
||||
(SlotKind::Int, 2) => RtInsn::Istore_2,
|
||||
(SlotKind::Int, 3) => RtInsn::Istore_3,
|
||||
(SlotKind::Long, 0) => RtInsn::Lstore_0,
|
||||
(SlotKind::Long, 1) => RtInsn::Lstore_1,
|
||||
(SlotKind::Long, 2) => RtInsn::Lstore_2,
|
||||
(SlotKind::Long, 3) => RtInsn::Lstore_3,
|
||||
(SlotKind::Float, 0) => RtInsn::Fstore_0,
|
||||
(SlotKind::Float, 1) => RtInsn::Fstore_1,
|
||||
(SlotKind::Float, 2) => RtInsn::Fstore_2,
|
||||
(SlotKind::Float, 3) => RtInsn::Fstore_3,
|
||||
(SlotKind::Double, 0) => RtInsn::Dstore_0,
|
||||
(SlotKind::Double, 1) => RtInsn::Dstore_1,
|
||||
(SlotKind::Double, 2) => RtInsn::Dstore_2,
|
||||
(SlotKind::Double, 3) => RtInsn::Dstore_3,
|
||||
(SlotKind::Ref, 0) => RtInsn::Astore_0,
|
||||
(SlotKind::Ref, 1) => RtInsn::Astore_1,
|
||||
(SlotKind::Ref, 2) => RtInsn::Astore_2,
|
||||
(SlotKind::Ref, 3) => RtInsn::Astore_3,
|
||||
(SlotKind::Int, s) => narrow(s, RtInsn::Istore, RtInsn::Istore_w),
|
||||
(SlotKind::Long, s) => narrow(s, RtInsn::Lstore, RtInsn::Lstore_w),
|
||||
(SlotKind::Float, s) => narrow(s, RtInsn::Fstore, RtInsn::Fstore_w),
|
||||
(SlotKind::Double, s) => narrow(s, RtInsn::Dstore, RtInsn::Dstore_w),
|
||||
(SlotKind::Ref, s) => narrow(s, RtInsn::Astore, RtInsn::Astore_w),
|
||||
}
|
||||
}
|
||||
|
||||
/// Короткая форма при слоте ≤255, иначе `wide`-форма.
|
||||
fn narrow(slot: u16, short: fn(u8) -> RtInsn, wide: fn(u16) -> RtInsn) -> RtInsn {
|
||||
u8::try_from(slot).map(short).unwrap_or_else(|_| wide(slot))
|
||||
}
|
||||
|
||||
fn ret(slot: u16) -> RtInsn {
|
||||
narrow(slot, RtInsn::Ret, RtInsn::Ret_w)
|
||||
}
|
||||
|
||||
fn iinc(index: u16, delta: i32) -> Result<RtInsn, ClassfileError> {
|
||||
if let (Ok(i), Ok(d)) = (u8::try_from(index), i8::try_from(delta)) {
|
||||
return Ok(RtInsn::Iinc(i, d));
|
||||
}
|
||||
let wide_delta = i16::try_from(delta)
|
||||
.map_err(|_| ClassfileError::Write(format!("приращение iinc {delta} не влезает в i16")))?;
|
||||
Ok(RtInsn::Iinc_w(index, wide_delta))
|
||||
}
|
||||
231
crates/noxis-classfile/src/format/write/code/mod.rs
Normal file
231
crates/noxis-classfile/src/format/write/code/mod.rs
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
//! Тело метода нашего IR → атрибут `Code` backend'а.
|
||||
//!
|
||||
//! Метки резолвятся в номера инструкций один раз здесь; дальше backend сам
|
||||
//! пересчитывает номера в байтовые смещения при `to_bytes`.
|
||||
|
||||
mod frames;
|
||||
pub(crate) mod insns;
|
||||
mod ops;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Cursor;
|
||||
|
||||
use ristretto_classfile::attributes::{
|
||||
Attribute as RtAttr, ExceptionTableEntry as RtEntry, Instruction as RtInsn,
|
||||
};
|
||||
|
||||
use super::attrs;
|
||||
use super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{BranchCond, Cond, Insn, Label, Prim};
|
||||
use crate::model::{Attribute, Code, ExceptionHandler};
|
||||
|
||||
use frames::convert_frames;
|
||||
|
||||
/// Тело метода → готовый атрибут `Code` (с логическими номерами инструкций).
|
||||
pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
|
||||
let (labels, bare) = split_marks(&code.insns)?;
|
||||
let resolve = |label: Label| -> Result<u32, ClassfileError> {
|
||||
labels
|
||||
.get(&label)
|
||||
.copied()
|
||||
.ok_or_else(|| ClassfileError::Write(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(|_| ClassfileError::Write("в методе больше 4 млрд инструкций".into()))?;
|
||||
if let Some(rt) = insns::convert(insn, index, pool, &resolve)? {
|
||||
rt_insns.push(rt);
|
||||
}
|
||||
}
|
||||
let exception_table = code
|
||||
.exception_table
|
||||
.iter()
|
||||
.map(|handler| entry(handler, &resolve, pool))
|
||||
.collect::<Result<_, _>>()?;
|
||||
let to_byte = measure(&rt_insns)?;
|
||||
let byte_of = |index: u32| -> Result<u16, ClassfileError> {
|
||||
to_byte
|
||||
.get(&index)
|
||||
.copied()
|
||||
.ok_or_else(|| ClassfileError::Write(format!("нет байта для инструкции {index}")))
|
||||
};
|
||||
let positions = attrs::Positions {
|
||||
resolve: &resolve,
|
||||
byte_of: &byte_of,
|
||||
};
|
||||
let frames = convert_frames(&code.frames, pool)?;
|
||||
let mut attributes = Vec::with_capacity(code.attributes.len() + 1);
|
||||
for attr in &code.attributes {
|
||||
attributes.push(match attr {
|
||||
// Backend хранит номера строк в индексах и маппит сам.
|
||||
Attribute::LineNumberTable(lines) => attrs::line_number_table(lines, pool, &positions)?,
|
||||
Attribute::LocalVariableTable(vars) => attrs::local_table(vars, pool, &positions)?,
|
||||
Attribute::LocalVariableTypeTable(vars) => {
|
||||
attrs::local_type_table(vars, pool, &positions)?
|
||||
}
|
||||
other => attrs::convert(other, pool)?,
|
||||
});
|
||||
}
|
||||
if !frames.is_empty() {
|
||||
attributes.push(RtAttr::StackMapTable {
|
||||
name_index: pool.utf8("StackMapTable")?,
|
||||
frames,
|
||||
});
|
||||
}
|
||||
Ok(RtAttr::Code {
|
||||
name_index: pool.utf8("Code")?,
|
||||
max_stack: code.max_stack,
|
||||
max_locals: code.max_locals,
|
||||
code: rt_insns,
|
||||
exception_table,
|
||||
attributes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Метки → номера инструкций; сами маркеры из потока убираются.
|
||||
fn split_marks(insns: &[Insn]) -> Result<(BTreeMap<Label, u32>, Vec<&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(|_| {
|
||||
ClassfileError::Write("в методе больше 4 млрд инструкций".into())
|
||||
})?;
|
||||
if let Some(prev) = labels.insert(*label, index)
|
||||
&& prev != index
|
||||
{
|
||||
return Err(ClassfileError::Write(format!(
|
||||
"метка {label} объявлена дважды"
|
||||
)));
|
||||
}
|
||||
}
|
||||
_ => bare.push(insn),
|
||||
}
|
||||
}
|
||||
Ok((labels, bare))
|
||||
}
|
||||
|
||||
/// Номер инструкции → байтовое смещение (замер с заглушками целей).
|
||||
///
|
||||
/// В карту входит и конец кода (`len` → длина): концы диапазонов указывают
|
||||
/// за последнюю инструкцию.
|
||||
pub(crate) fn measure(insns: &[RtInsn]) -> Result<BTreeMap<u32, u16>, ClassfileError> {
|
||||
let mut map = BTreeMap::new();
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
for (index, insn) in insns.iter().enumerate() {
|
||||
let position = tell(&cursor)?;
|
||||
let index = u32::try_from(index)
|
||||
.map_err(|_| ClassfileError::Write("в методе больше 4 млрд инструкций".into()))?;
|
||||
map.insert(index, position);
|
||||
let mut sizing = insn.clone();
|
||||
smash_targets(&mut sizing, position);
|
||||
sizing
|
||||
.to_bytes(&mut cursor)
|
||||
.map_err(|e| ClassfileError::Write(e.to_string()))?;
|
||||
}
|
||||
let end = u32::try_from(insns.len())
|
||||
.map_err(|_| ClassfileError::Write("в методе больше 4 млрд инструкций".into()))?;
|
||||
map.insert(end, tell(&cursor)?);
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/// Цели ветвлений не влияют на размер — подменяем текущей позицией для замера.
|
||||
fn smash_targets(insn: &mut RtInsn, position: u16) {
|
||||
match insn {
|
||||
RtInsn::Ifeq(target)
|
||||
| RtInsn::Ifne(target)
|
||||
| RtInsn::Iflt(target)
|
||||
| RtInsn::Ifge(target)
|
||||
| RtInsn::Ifgt(target)
|
||||
| RtInsn::Ifle(target)
|
||||
| RtInsn::If_icmpeq(target)
|
||||
| RtInsn::If_icmpne(target)
|
||||
| RtInsn::If_icmplt(target)
|
||||
| RtInsn::If_icmpge(target)
|
||||
| RtInsn::If_icmpgt(target)
|
||||
| RtInsn::If_icmple(target)
|
||||
| RtInsn::If_acmpeq(target)
|
||||
| RtInsn::If_acmpne(target)
|
||||
| RtInsn::Goto(target)
|
||||
| RtInsn::Jsr(target)
|
||||
| RtInsn::Ifnull(target)
|
||||
| RtInsn::Ifnonnull(target) => *target = position,
|
||||
RtInsn::Goto_w(target) | RtInsn::Jsr_w(target) => *target = i32::from(position),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn tell(cursor: &Cursor<Vec<u8>>) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(cursor.position())
|
||||
.map_err(|_| ClassfileError::Write("тело метода длиннее 64 КБ".into()))
|
||||
}
|
||||
|
||||
fn entry(
|
||||
handler: &ExceptionHandler,
|
||||
resolve: &impl Fn(Label) -> Result<u32, ClassfileError>,
|
||||
pool: &mut Pool,
|
||||
) -> Result<RtEntry, ClassfileError> {
|
||||
Ok(RtEntry {
|
||||
range_pc: fit_index(resolve(handler.start)?)?..fit_index(resolve(handler.end)?)?,
|
||||
handler_pc: fit_index(resolve(handler.handler)?)?,
|
||||
catch_type: match &handler.catch_type {
|
||||
None => 0,
|
||||
Some(name) => pool.class(name)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/// Условный переход IR → `if*` backend'а.
|
||||
fn branch(cond: BranchCond, target: u32) -> Result<RtInsn, ClassfileError> {
|
||||
let target = fit_index(target)?;
|
||||
Ok(match cond {
|
||||
BranchCond::Zero(Cond::Eq) => RtInsn::Ifeq(target),
|
||||
BranchCond::Zero(Cond::Ne) => RtInsn::Ifne(target),
|
||||
BranchCond::Zero(Cond::Lt) => RtInsn::Iflt(target),
|
||||
BranchCond::Zero(Cond::Ge) => RtInsn::Ifge(target),
|
||||
BranchCond::Zero(Cond::Gt) => RtInsn::Ifgt(target),
|
||||
BranchCond::Zero(Cond::Le) => RtInsn::Ifle(target),
|
||||
BranchCond::Int(Cond::Eq) => RtInsn::If_icmpeq(target),
|
||||
BranchCond::Int(Cond::Ne) => RtInsn::If_icmpne(target),
|
||||
BranchCond::Int(Cond::Lt) => RtInsn::If_icmplt(target),
|
||||
BranchCond::Int(Cond::Ge) => RtInsn::If_icmpge(target),
|
||||
BranchCond::Int(Cond::Gt) => RtInsn::If_icmpgt(target),
|
||||
BranchCond::Int(Cond::Le) => RtInsn::If_icmple(target),
|
||||
BranchCond::Ref { eq: true } => RtInsn::If_acmpeq(target),
|
||||
BranchCond::Ref { eq: false } => RtInsn::If_acmpne(target),
|
||||
BranchCond::Null { is_null: true } => RtInsn::Ifnull(target),
|
||||
BranchCond::Null { is_null: false } => RtInsn::Ifnonnull(target),
|
||||
})
|
||||
}
|
||||
|
||||
fn ret_op(prim: Option<Prim>) -> RtInsn {
|
||||
match prim {
|
||||
None => RtInsn::Return,
|
||||
Some(Prim::Int) => RtInsn::Ireturn,
|
||||
Some(Prim::Long) => RtInsn::Lreturn,
|
||||
Some(Prim::Float) => RtInsn::Freturn,
|
||||
Some(Prim::Double) => RtInsn::Dreturn,
|
||||
}
|
||||
}
|
||||
|
||||
/// Номер инструкции обязан влезать в `u16` (метод < 64 КБ кода).
|
||||
fn fit_index(index: u32) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(index)
|
||||
.map_err(|_| ClassfileError::Write(format!("номер инструкции {index} вне u16")))
|
||||
}
|
||||
|
||||
/// Дельта `switch`: цель минус позиция самого `switch`.
|
||||
fn delta(index: u32, target: u32) -> Result<i32, ClassfileError> {
|
||||
let (index, target) = (to_i32(index)?, to_i32(target)?);
|
||||
target
|
||||
.checked_sub(index)
|
||||
.ok_or_else(|| ClassfileError::Write(format!("переполнение дельты {index}->{target}")))
|
||||
}
|
||||
|
||||
fn to_i32(value: u32) -> Result<i32, ClassfileError> {
|
||||
i32::try_from(value)
|
||||
.map_err(|_| ClassfileError::Write(format!("номер инструкции {value} вне i32")))
|
||||
}
|
||||
244
crates/noxis-classfile/src/format/write/code/ops.rs
Normal file
244
crates/noxis-classfile/src/format/write/code/ops.rs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
//! Операнды: константы, арифметика, массивы, вызовы.
|
||||
|
||||
use ristretto_classfile::attributes::{ArrayType, Instruction as RtInsn};
|
||||
|
||||
use super::super::pool::Pool;
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Arith, ArrayKind, Const, Convert, InvokeKind, Prim};
|
||||
|
||||
/// Константа: короткие формы (`iconst`, `bipush`) + `ldc_w`/`ldc2_w` через пул.
|
||||
pub(super) fn int_const(value: &Const, pool: &mut Pool) -> Result<RtInsn, ClassfileError> {
|
||||
Ok(match value {
|
||||
Const::Int(v) => match *v {
|
||||
-1 => RtInsn::Iconst_m1,
|
||||
0 => RtInsn::Iconst_0,
|
||||
1 => RtInsn::Iconst_1,
|
||||
2 => RtInsn::Iconst_2,
|
||||
3 => RtInsn::Iconst_3,
|
||||
4 => RtInsn::Iconst_4,
|
||||
5 => RtInsn::Iconst_5,
|
||||
v if i8::try_from(v).is_ok() => RtInsn::Bipush(v as i8),
|
||||
v if i16::try_from(v).is_ok() => RtInsn::Sipush(v as i16),
|
||||
_ => RtInsn::Ldc_w(pool.constant(value)?),
|
||||
},
|
||||
Const::Long(0) => RtInsn::Lconst_0,
|
||||
Const::Long(1) => RtInsn::Lconst_1,
|
||||
Const::Long(_) => RtInsn::Ldc2_w(pool.constant(value)?),
|
||||
Const::Float(v) if *v == 0.0 => RtInsn::Fconst_0,
|
||||
Const::Float(v) if *v == 1.0 => RtInsn::Fconst_1,
|
||||
Const::Float(v) if *v == 2.0 => RtInsn::Fconst_2,
|
||||
Const::Float(_) => RtInsn::Ldc_w(pool.constant(value)?),
|
||||
Const::Double(v) if *v == 0.0 => RtInsn::Dconst_0,
|
||||
Const::Double(v) if *v == 1.0 => RtInsn::Dconst_1,
|
||||
Const::Double(_) => RtInsn::Ldc2_w(pool.constant(value)?),
|
||||
Const::Dynamic {
|
||||
name,
|
||||
descriptor,
|
||||
bootstrap,
|
||||
} => RtInsn::Ldc2_w(pool.dynamic(*bootstrap, name, descriptor)?),
|
||||
_ => RtInsn::Ldc_w(pool.constant(value)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn array_load(kind: ArrayKind) -> RtInsn {
|
||||
match kind {
|
||||
ArrayKind::Boolean | ArrayKind::Byte => RtInsn::Baload,
|
||||
ArrayKind::Char => RtInsn::Caload,
|
||||
ArrayKind::Short => RtInsn::Saload,
|
||||
ArrayKind::Int => RtInsn::Iaload,
|
||||
ArrayKind::Long => RtInsn::Laload,
|
||||
ArrayKind::Float => RtInsn::Faload,
|
||||
ArrayKind::Double => RtInsn::Daload,
|
||||
ArrayKind::Reference => RtInsn::Aaload,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn array_store(kind: ArrayKind) -> RtInsn {
|
||||
match kind {
|
||||
ArrayKind::Boolean | ArrayKind::Byte => RtInsn::Bastore,
|
||||
ArrayKind::Char => RtInsn::Castore,
|
||||
ArrayKind::Short => RtInsn::Sastore,
|
||||
ArrayKind::Int => RtInsn::Iastore,
|
||||
ArrayKind::Long => RtInsn::Lastore,
|
||||
ArrayKind::Float => RtInsn::Fastore,
|
||||
ArrayKind::Double => RtInsn::Dastore,
|
||||
ArrayKind::Reference => RtInsn::Aastore,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn array_type(kind: ArrayKind) -> Result<ArrayType, ClassfileError> {
|
||||
Ok(match kind {
|
||||
ArrayKind::Boolean => ArrayType::Boolean,
|
||||
ArrayKind::Char => ArrayType::Char,
|
||||
ArrayKind::Float => ArrayType::Float,
|
||||
ArrayKind::Double => ArrayType::Double,
|
||||
ArrayKind::Byte => ArrayType::Byte,
|
||||
ArrayKind::Short => ArrayType::Short,
|
||||
ArrayKind::Int => ArrayType::Int,
|
||||
ArrayKind::Long => ArrayType::Long,
|
||||
ArrayKind::Reference => {
|
||||
return Err(ClassfileError::Write(
|
||||
"newarray не создаёт ссылочные массивы (нужен anewarray)".into(),
|
||||
));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn binary(prim: Prim, op: Arith) -> Result<RtInsn, ClassfileError> {
|
||||
use Arith::{Add, And, Div, Mul, Neg, Or, Rem, Shl, Shr, Sub, Ushr, Xor};
|
||||
Ok(match (prim, op) {
|
||||
(Prim::Int, Add) => RtInsn::Iadd,
|
||||
(Prim::Int, Sub) => RtInsn::Isub,
|
||||
(Prim::Int, Mul) => RtInsn::Imul,
|
||||
(Prim::Int, Div) => RtInsn::Idiv,
|
||||
(Prim::Int, Rem) => RtInsn::Irem,
|
||||
(Prim::Int, Neg) => RtInsn::Ineg,
|
||||
(Prim::Int, And) => RtInsn::Iand,
|
||||
(Prim::Int, Or) => RtInsn::Ior,
|
||||
(Prim::Int, Xor) => RtInsn::Ixor,
|
||||
(Prim::Int, Shl) => RtInsn::Ishl,
|
||||
(Prim::Int, Shr) => RtInsn::Ishr,
|
||||
(Prim::Int, Ushr) => RtInsn::Iushr,
|
||||
(Prim::Long, Add) => RtInsn::Ladd,
|
||||
(Prim::Long, Sub) => RtInsn::Lsub,
|
||||
(Prim::Long, Mul) => RtInsn::Lmul,
|
||||
(Prim::Long, Div) => RtInsn::Ldiv,
|
||||
(Prim::Long, Rem) => RtInsn::Lrem,
|
||||
(Prim::Long, Neg) => RtInsn::Lneg,
|
||||
(Prim::Long, And) => RtInsn::Land,
|
||||
(Prim::Long, Or) => RtInsn::Lor,
|
||||
(Prim::Long, Xor) => RtInsn::Lxor,
|
||||
(Prim::Long, Shl) => RtInsn::Lshl,
|
||||
(Prim::Long, Shr) => RtInsn::Lshr,
|
||||
(Prim::Long, Ushr) => RtInsn::Lushr,
|
||||
(Prim::Float, Add) => RtInsn::Fadd,
|
||||
(Prim::Float, Sub) => RtInsn::Fsub,
|
||||
(Prim::Float, Mul) => RtInsn::Fmul,
|
||||
(Prim::Float, Div) => RtInsn::Fdiv,
|
||||
(Prim::Float, Rem) => RtInsn::Frem,
|
||||
(Prim::Float, Neg) => RtInsn::Fneg,
|
||||
(Prim::Double, Add) => RtInsn::Dadd,
|
||||
(Prim::Double, Sub) => RtInsn::Dsub,
|
||||
(Prim::Double, Mul) => RtInsn::Dmul,
|
||||
(Prim::Double, Div) => RtInsn::Ddiv,
|
||||
(Prim::Double, Rem) => RtInsn::Drem,
|
||||
(Prim::Double, Neg) => RtInsn::Dneg,
|
||||
(prim, op) => {
|
||||
return Err(ClassfileError::Write(format!(
|
||||
"недопустимая комбинация {prim:?}/{op:?}"
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn cmp(prim: Prim, nan_great: bool) -> Result<RtInsn, ClassfileError> {
|
||||
Ok(match (prim, nan_great) {
|
||||
(Prim::Long, false) => RtInsn::Lcmp,
|
||||
(Prim::Float, false) => RtInsn::Fcmpl,
|
||||
(Prim::Float, true) => RtInsn::Fcmpg,
|
||||
(Prim::Double, false) => RtInsn::Dcmpl,
|
||||
(Prim::Double, true) => RtInsn::Dcmpg,
|
||||
(prim, nan_great) => {
|
||||
return Err(ClassfileError::Write(format!(
|
||||
"недопустимое сравнение {prim:?}/{nan_great}"
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn convert_op(op: Convert) -> RtInsn {
|
||||
match op {
|
||||
Convert::I2L => RtInsn::I2l,
|
||||
Convert::I2F => RtInsn::I2f,
|
||||
Convert::I2D => RtInsn::I2d,
|
||||
Convert::I2B => RtInsn::I2b,
|
||||
Convert::I2C => RtInsn::I2c,
|
||||
Convert::I2S => RtInsn::I2s,
|
||||
Convert::L2I => RtInsn::L2i,
|
||||
Convert::L2F => RtInsn::L2f,
|
||||
Convert::L2D => RtInsn::L2d,
|
||||
Convert::F2I => RtInsn::F2i,
|
||||
Convert::F2L => RtInsn::F2l,
|
||||
Convert::F2D => RtInsn::F2d,
|
||||
Convert::D2I => RtInsn::D2i,
|
||||
Convert::D2L => RtInsn::D2l,
|
||||
Convert::D2F => RtInsn::D2f,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn invoke(
|
||||
kind: InvokeKind,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
pool: &mut Pool,
|
||||
) -> Result<RtInsn, ClassfileError> {
|
||||
Ok(match kind {
|
||||
InvokeKind::Virtual => {
|
||||
RtInsn::Invokevirtual(pool.method_ref(owner, name, descriptor, kind)?)
|
||||
}
|
||||
InvokeKind::Special => {
|
||||
RtInsn::Invokespecial(pool.method_ref(owner, name, descriptor, kind)?)
|
||||
}
|
||||
InvokeKind::Static => RtInsn::Invokestatic(pool.method_ref(owner, name, descriptor, kind)?),
|
||||
InvokeKind::Interface => {
|
||||
let index = pool.method_ref(owner, name, descriptor, kind)?;
|
||||
RtInsn::Invokeinterface(index, arg_count(descriptor)?)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Байт `count` для `invokeinterface`: слоты аргументов + `objectref`.
|
||||
fn arg_count(descriptor: &str) -> Result<u8, ClassfileError> {
|
||||
let params = descriptor
|
||||
.split_once('(')
|
||||
.and_then(|(_, rest)| rest.split_once(')'))
|
||||
.map(|(params, _)| params)
|
||||
.ok_or_else(|| ClassfileError::Write(format!("кривой дескриптор {descriptor}")))?;
|
||||
let mut slots = 1u32;
|
||||
let mut chars = params.chars();
|
||||
while let Some(char) = chars.next() {
|
||||
match char {
|
||||
'B' | 'C' | 'F' | 'I' | 'S' | 'Z' => slots += 1,
|
||||
'J' | 'D' => slots += 2,
|
||||
'L' => {
|
||||
if !skip_to(chars.by_ref(), ';') {
|
||||
return Err(bad_descriptor(descriptor));
|
||||
}
|
||||
slots += 1;
|
||||
}
|
||||
'[' => {
|
||||
while chars.as_str().starts_with('[') {
|
||||
chars.next();
|
||||
}
|
||||
match chars.next() {
|
||||
Some('L') => {
|
||||
if !skip_to(chars.by_ref(), ';') {
|
||||
return Err(bad_descriptor(descriptor));
|
||||
}
|
||||
}
|
||||
Some(_) => {}
|
||||
None => return Err(bad_descriptor(descriptor)),
|
||||
}
|
||||
slots += 1;
|
||||
}
|
||||
_ => return Err(bad_descriptor(descriptor)),
|
||||
}
|
||||
}
|
||||
u8::try_from(slots)
|
||||
.map_err(|_| ClassfileError::Write(format!("слишком много аргументов в {descriptor}")))
|
||||
}
|
||||
|
||||
/// Пропустить символы до разделителя (включительно); `false` — строка кончилась.
|
||||
fn skip_to(chars: &mut std::str::Chars<'_>, end: char) -> bool {
|
||||
for char in chars {
|
||||
if char == end {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn bad_descriptor(descriptor: &str) -> ClassfileError {
|
||||
ClassfileError::Write(format!("кривой дескриптор {descriptor}"))
|
||||
}
|
||||
93
crates/noxis-classfile/src/format/write/mod.rs
Normal file
93
crates/noxis-classfile/src/format/write/mod.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
//! Наш IR → байты `.class` через backend `ristretto_classfile`.
|
||||
//!
|
||||
//! Пул собирается с нуля детерминированным обходом; backend сам пересчитывает
|
||||
//! номера инструкций в байтовые смещения и проверяет результат (`verify`).
|
||||
|
||||
mod attrs;
|
||||
pub(crate) mod code;
|
||||
pub(crate) mod pool;
|
||||
|
||||
use ristretto_classfile::{
|
||||
ClassAccessFlags, ClassFile as RtClass, Field as RtField, FieldAccessFlags, FieldType,
|
||||
Method as RtMethod, MethodAccessFlags, Version as RtVersion,
|
||||
};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::model::{ClassFile, Field, Method};
|
||||
|
||||
/// Класс нашего IR → байты `.class`.
|
||||
pub(crate) fn write_class(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> {
|
||||
let mut pool = pool::Pool::new();
|
||||
let version = RtVersion::from(class.version.major, class.version.minor)
|
||||
.map_err(|e| ClassfileError::Write(e.to_string()))?;
|
||||
let this_class = pool.class(&class.this_class)?;
|
||||
let super_class = match &class.super_class {
|
||||
None => 0,
|
||||
Some(name) => pool.class(name)?,
|
||||
};
|
||||
let mut interfaces = Vec::with_capacity(class.interfaces.len());
|
||||
for interface in &class.interfaces {
|
||||
interfaces.push(pool.class(interface)?);
|
||||
}
|
||||
let mut fields = Vec::with_capacity(class.fields.len());
|
||||
for field in &class.fields {
|
||||
fields.push(convert_field(field, &mut pool)?);
|
||||
}
|
||||
let mut methods = Vec::with_capacity(class.methods.len());
|
||||
for method in &class.methods {
|
||||
methods.push(convert_method(method, &mut pool)?);
|
||||
}
|
||||
let mut attributes = Vec::with_capacity(class.attributes.len());
|
||||
for attr in &class.attributes {
|
||||
attributes.push(attrs::convert(attr, &mut pool)?);
|
||||
}
|
||||
let rt = RtClass {
|
||||
version,
|
||||
constant_pool: pool.finish(),
|
||||
access_flags: ClassAccessFlags::from_bits_truncate(class.access_flags),
|
||||
this_class,
|
||||
super_class,
|
||||
interfaces,
|
||||
fields,
|
||||
methods,
|
||||
attributes,
|
||||
code_source_url: None,
|
||||
};
|
||||
rt.verify()
|
||||
.map_err(|e| ClassfileError::Write(format!("собранный класс не верифицируется: {e}")))?;
|
||||
let mut bytes = Vec::new();
|
||||
rt.to_bytes(&mut bytes)
|
||||
.map_err(|e| ClassfileError::Write(e.to_string()))?;
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
fn convert_field(field: &Field, pool: &mut pool::Pool) -> Result<RtField, ClassfileError> {
|
||||
Ok(RtField {
|
||||
access_flags: FieldAccessFlags::from_bits_truncate(field.access_flags),
|
||||
name_index: pool.utf8(&field.name)?,
|
||||
descriptor_index: pool.utf8(&field.descriptor)?,
|
||||
field_type: FieldType::parse(&field.descriptor)
|
||||
.map_err(|e| ClassfileError::Write(format!("кривой дескриптор поля: {e}")))?,
|
||||
attributes: field
|
||||
.attributes
|
||||
.iter()
|
||||
.map(|attr| attrs::convert(attr, pool))
|
||||
.collect::<Result<_, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_method(method: &Method, pool: &mut pool::Pool) -> Result<RtMethod, ClassfileError> {
|
||||
let mut attributes = Vec::with_capacity(method.attributes.len() + 1);
|
||||
for attr in &method.attributes {
|
||||
attributes.push(attrs::convert(attr, pool)?);
|
||||
}
|
||||
if let Some(code) = &method.code {
|
||||
attributes.push(code::convert_code(code, pool)?);
|
||||
}
|
||||
Ok(RtMethod {
|
||||
access_flags: MethodAccessFlags::from_bits_truncate(method.access_flags),
|
||||
name_index: pool.utf8(&method.name)?,
|
||||
descriptor_index: pool.utf8(&method.descriptor)?,
|
||||
attributes,
|
||||
})
|
||||
}
|
||||
155
crates/noxis-classfile/src/format/write/pool.rs
Normal file
155
crates/noxis-classfile/src/format/write/pool.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
//! Сборка constant pool при записи: строки нашего IR → индексы backend'а.
|
||||
//!
|
||||
//! Пул строится с нуля детерминированным обходом (классы, поля, методы,
|
||||
//! атрибуты, код), поэтому одинаковый IR даёт одинаковые байты. Индексы
|
||||
//! `Raw`-атрибутов при этом не переносятся — см. `super::attrs`.
|
||||
|
||||
use ristretto_classfile::{ConstantPool, ReferenceKind};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, InvokeKind};
|
||||
|
||||
/// Строитель пула; живёт только внутри одного `write`.
|
||||
pub(crate) struct Pool {
|
||||
pool: ConstantPool<'static>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub(crate) fn new() -> Self {
|
||||
Pool {
|
||||
pool: ConstantPool::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn utf8(&mut self, value: &str) -> Result<u16, ClassfileError> {
|
||||
self.pool.add_utf8(value).map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn name_and_type(
|
||||
&mut self,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
self.pool
|
||||
.add_name_and_type(name, descriptor)
|
||||
.map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn class(&mut self, name: &str) -> Result<u16, ClassfileError> {
|
||||
self.pool.add_class(name).map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn field_ref(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
let owner = self.class(owner)?;
|
||||
self.pool
|
||||
.add_field_ref(owner, name, descriptor)
|
||||
.map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn method_ref(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
kind: InvokeKind,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
let owner = self.class(owner)?;
|
||||
match kind {
|
||||
InvokeKind::Interface => self
|
||||
.pool
|
||||
.add_interface_method_ref(owner, name, descriptor)
|
||||
.map_err(write_err),
|
||||
_ => self
|
||||
.pool
|
||||
.add_method_ref(owner, name, descriptor)
|
||||
.map_err(write_err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Константа для `ldc`/`ldc2_w`; `dynamic` запрещён (только через bootstrap).
|
||||
pub(super) fn constant(&mut self, value: &Const) -> Result<u16, ClassfileError> {
|
||||
match value {
|
||||
Const::Int(v) => self.pool.add_integer(*v).map_err(write_err),
|
||||
Const::Float(v) => self.pool.add_float(*v).map_err(write_err),
|
||||
Const::Long(v) => self.pool.add_long(*v).map_err(write_err),
|
||||
Const::Double(v) => self.pool.add_double(*v).map_err(write_err),
|
||||
Const::String(v) => self.pool.add_string(v).map_err(write_err),
|
||||
Const::Class(v) => self.class(v),
|
||||
Const::MethodType(v) => self.pool.add_method_type(v).map_err(write_err),
|
||||
Const::MethodHandle {
|
||||
kind,
|
||||
owner,
|
||||
name,
|
||||
descriptor,
|
||||
itf,
|
||||
} => self.method_handle(*kind, owner, name, descriptor, *itf),
|
||||
Const::Dynamic { .. } => Err(ClassfileError::Write(
|
||||
"CONSTANT_Dynamic пишется только через BootstrapMethods".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn method_handle(
|
||||
&mut self,
|
||||
kind: u8,
|
||||
owner: &str,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
itf: bool,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
let reference_kind = ReferenceKind::try_from(kind).map_err(|e| {
|
||||
ClassfileError::Write(format!("некорректный reference_kind {kind}: {e}"))
|
||||
})?;
|
||||
let reference = if itf {
|
||||
self.method_ref(owner, name, descriptor, InvokeKind::Interface)?
|
||||
} else if matches!(
|
||||
reference_kind,
|
||||
ReferenceKind::GetField
|
||||
| ReferenceKind::GetStatic
|
||||
| ReferenceKind::PutField
|
||||
| ReferenceKind::PutStatic
|
||||
) {
|
||||
self.field_ref(owner, name, descriptor)?
|
||||
} else {
|
||||
self.method_ref(owner, name, descriptor, InvokeKind::Static)?
|
||||
};
|
||||
self.pool
|
||||
.add_method_handle(reference_kind, reference)
|
||||
.map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn invoke_dynamic(
|
||||
&mut self,
|
||||
bootstrap: u16,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
self.pool
|
||||
.add_invoke_dynamic(bootstrap, name, descriptor)
|
||||
.map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn dynamic(
|
||||
&mut self,
|
||||
bootstrap: u16,
|
||||
name: &str,
|
||||
descriptor: &str,
|
||||
) -> Result<u16, ClassfileError> {
|
||||
self.pool
|
||||
.add_dynamic(bootstrap, name, descriptor)
|
||||
.map_err(write_err)
|
||||
}
|
||||
|
||||
pub(super) fn finish(self) -> ConstantPool<'static> {
|
||||
self.pool
|
||||
}
|
||||
}
|
||||
|
||||
fn write_err(error: ristretto_classfile::Error) -> ClassfileError {
|
||||
ClassfileError::Write(error.to_string())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue