structure-check: gate counts code lines, docs and comments in English
Gate ignores blank and full-line comment lines so the 250-line limit measures code bulk, not documentation weight; docs/README.md synced. Translate code comments to English in classfile, core config, namegen, rename assign, constants forms. Expand Plausible word bank with client-ecosystem terms (Module, Setting, Mixin, ...). cargo fmt clean, check.sh green.
This commit is contained in:
parent
ec28ec82e3
commit
39178420e4
42 changed files with 532 additions and 437 deletions
|
|
@ -1,6 +1,6 @@
|
|||
//! Ошибки разбора/записи классовых файлов.
|
||||
//! Errors from class-file parsing/writing.
|
||||
|
||||
/// Ошибка на уровне одного классового файла.
|
||||
/// An error at the level of a single class file.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ClassfileError {
|
||||
#[error("не удалось разобрать классовый файл: {0}")]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! Эмиссия компактных кадров в точках, где их ждёт верификатор.
|
||||
//! Emitting compact frames at the points where the verifier expects them.
|
||||
//!
|
||||
//! Вид кадра выбирается сравнением с предыдущим кадром (`Same`/`Append`/
|
||||
//! `Chop`/`SameLocals`, иначе `Full`) — как у `javac`. Хвостовые `Top`
|
||||
//! при сравнении и записи отрезаются: пустой слот — не тип.
|
||||
//! A frame's kind is chosen by comparing with the previous frame
|
||||
//! (`Same`/`Append`/`Chop`/`SameLocals`, otherwise `Full`) — like `javac`.
|
||||
//! Trailing `Top`s are trimmed both when comparing and when writing: an empty
|
||||
//! slot is not a type.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
|
|
@ -11,10 +12,10 @@ use super::xfer::State;
|
|||
use super::{StackMapFrame, VerificationType};
|
||||
use crate::error::ClassfileError;
|
||||
|
||||
/// Кадры во всех точках `targets` (отсортированы — дельты по порядку).
|
||||
/// Frames at every point in `targets` (sorted — deltas in order).
|
||||
///
|
||||
/// Дельты — в номерах инструкций (как backend после чтения); writer сам
|
||||
/// пересчитает их в байты.
|
||||
/// Deltas are in instruction numbers (like the backend after reading); the writer
|
||||
/// recomputes them into bytes itself.
|
||||
pub(super) fn emit_frames(
|
||||
states: &[Option<State>],
|
||||
targets: &BTreeSet<u32>,
|
||||
|
|
@ -49,8 +50,8 @@ fn select(
|
|||
previous: &[VerificationType],
|
||||
stack: &[VerificationType],
|
||||
) -> StackMapFrame {
|
||||
// Сравнение — на гранулярности переменных (`long` — одна запись,
|
||||
// как у `javac`): иначе половины пар дают `Append{[Top, Long]}`.
|
||||
// Comparison is at variable granularity (`long` — one entry, like `javac`);
|
||||
// otherwise halves would produce `Append{[Top, Long]}`.
|
||||
let current = vars(locals);
|
||||
let base = vars(previous);
|
||||
if current == base {
|
||||
|
|
@ -93,10 +94,10 @@ fn select(
|
|||
}
|
||||
}
|
||||
|
||||
/// Слоты → переменные: `[Long, Top]` — одна запись; хвостовые `Top` — прочь.
|
||||
/// Slots → variables: `[Long, Top]` — one entry; trailing `Top`s — gone.
|
||||
///
|
||||
/// Одиночные `Top` внутри (дыры) сохраняются: в `Full` это легальный тип,
|
||||
/// а в `Append` мы их не пускаем (см. `select`).
|
||||
/// Single `Top`s inside (holes) are kept: in `Full` they are a legal type, and we
|
||||
/// don't allow them in `Append` (see `select`).
|
||||
fn vars(locals: &[VerificationType]) -> Vec<VerificationType> {
|
||||
let mut out = Vec::with_capacity(locals.len());
|
||||
let mut index = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! CFG тела метода: метки, рёбра, точки кадров, диапазоны handler'ов.
|
||||
//! A method body's CFG: labels, edges, frame points, handler ranges.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ use crate::error::ClassfileError;
|
|||
use crate::insn::{Insn, Label};
|
||||
use crate::model::ExceptionHandler;
|
||||
|
||||
/// Метки → номера инструкций без маркеров.
|
||||
/// Labels → instruction numbers, without the markers.
|
||||
pub(super) fn strip_marks<'a>(
|
||||
insns: &'a [Insn],
|
||||
merger: &Merger<'_>,
|
||||
|
|
@ -31,7 +31,7 @@ pub(super) fn strip_marks<'a>(
|
|||
Ok((labels, bare))
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
/// CFG edges without exceptions: fallthrough + branch targets.
|
||||
pub(super) fn successors(
|
||||
insn: &Insn,
|
||||
index: u32,
|
||||
|
|
@ -40,8 +40,8 @@ pub(super) fn successors(
|
|||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<u32>, ClassfileError> {
|
||||
let mut out = Vec::new();
|
||||
// Без продолжения: переходы без возврата и выходы из метода.
|
||||
// `Jsr`/`Ret` сюда не доходят (их отвергает `transfer`), но рёбра есть.
|
||||
// No fallthrough: jumps that don't return and method exits.
|
||||
// `Jsr`/`Ret` never reach here (rejected by `transfer`), but the edges exist.
|
||||
let terminal = matches!(
|
||||
insn,
|
||||
Insn::Goto(..)
|
||||
|
|
@ -67,7 +67,7 @@ pub(super) fn successors(
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
/// Точки, где верификатор требует кадры: цели переходов + входы handler'ов.
|
||||
/// Points where the verifier requires frames: branch targets + handler entries.
|
||||
pub(super) fn frame_targets(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
@ -91,12 +91,12 @@ pub(super) fn frame_targets(
|
|||
Ok(targets)
|
||||
}
|
||||
|
||||
/// Точка handler'а в номерах инструкций: `(start, end, handler, тип перехвата)`.
|
||||
/// A handler point in instruction numbers: `(start, end, handler, catch type)`.
|
||||
///
|
||||
/// Диапазоны полуоткрытые `[start, end)`, как в JVMS.
|
||||
/// Ranges are half-open `[start, end)`, as in the JVMS.
|
||||
pub(super) type HandlerRange = (u32, u32, u32, Option<String>);
|
||||
|
||||
/// Handler'ы в номерах инструкций для рёбер исключений.
|
||||
/// Handlers in instruction numbers, for exception edges.
|
||||
pub(super) fn handler_ranges(
|
||||
table: &[ExceptionHandler],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Разбор дескрипторов типов (`(...)V`, `[L...;`).
|
||||
//! Descriptor parsing (`(...)V`, `[L...;`).
|
||||
|
||||
use super::super::VerificationType;
|
||||
|
||||
/// Ошибка разбора дескриптора (контекст добавляет вызывающий код).
|
||||
/// Descriptor parse error (context is added by the caller).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescError(pub String);
|
||||
|
||||
/// Уложить слоты значения в locals с позиции; вернуть следующую позицию.
|
||||
/// Write value slots into `locals` at position; return next position.
|
||||
pub(crate) fn write_local(
|
||||
locals: &mut [VerificationType],
|
||||
slot: usize,
|
||||
|
|
@ -22,7 +22,7 @@ pub(crate) fn write_local(
|
|||
Ok(slot + width)
|
||||
}
|
||||
|
||||
/// Разобрать дескриптор массива на (глубина, элемент без `;`).
|
||||
/// Parse an array descriptor into (depth, element without `;`).
|
||||
pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
|
||||
let dims = descriptor.bytes().take_while(|&b| b == b'[').count();
|
||||
if dims == 0 {
|
||||
|
|
@ -31,7 +31,7 @@ pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
|
|||
Some((dims, descriptor[dims..].trim_end_matches(';')))
|
||||
}
|
||||
|
||||
/// Слоты одного полевого дескриптора (`long`/`double` — парой с `Top`).
|
||||
/// Slots for a single field descriptor (`long`/`double` — pair with `Top`).
|
||||
pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, DescError> {
|
||||
let mut chars = descriptor.chars();
|
||||
match chars.next() {
|
||||
|
|
@ -57,10 +57,10 @@ pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, Des
|
|||
}
|
||||
}
|
||||
|
||||
/// Слоты параметров и возврата (`None` — `void`).
|
||||
/// Parameter and return slots (`None` — `void`).
|
||||
type MethodSlots = (Vec<Vec<VerificationType>>, Option<Vec<VerificationType>>);
|
||||
|
||||
/// Дескриптор метода: слоты параметров и слоты возврата (`None` — `void`).
|
||||
/// Method descriptor: parameter slots and return slots (`None` — `void`).
|
||||
pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
|
||||
let (params, ret) = descriptor
|
||||
.split_once('(')
|
||||
|
|
@ -128,7 +128,7 @@ pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
|
|||
Ok((out, ret))
|
||||
}
|
||||
|
||||
/// Тип элемента по дескриптору массива в стеке.
|
||||
/// Element type from an array descriptor on the stack.
|
||||
pub(crate) fn array_element(
|
||||
array: &VerificationType,
|
||||
kind: crate::insn::ArrayKind,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Слияние типов (LUB) и разбор дескрипторов.
|
||||
//! Type merging (LUB) and descriptor parsing.
|
||||
//!
|
||||
//! Решётка: `Top` — «пусто» (берётся сосед), `Null` впитывается в ссылку,
|
||||
//! одинаковые — сами в себя, ссылки — наименьший общий предок по иерархии.
|
||||
//! Несочетаемые инициализированные типы (`int` vs ссылка после `astore`
|
||||
//! в handler'е) дают `Top`, как обрезанные кадры `javac`: `Top` в кадре
|
||||
//! принимает любое фактическое значение, поэтому это всегда sound.
|
||||
//! Исключение — неинициализированные типы: их слияние означает сломанный
|
||||
//! поток конструирования, здесь честная ошибка, а не молчаливый `Top`.
|
||||
//! The lattice: `Top` is "empty" (the neighbour wins), `Null` is absorbed into a
|
||||
//! reference, equal types map to themselves, references take the least upper
|
||||
//! bound over the hierarchy. Incompatible initialized types (`int` vs a reference
|
||||
//! after `astore` in a handler) yield `Top`, like `javac`'s truncated frames: a
|
||||
//! `Top` in a frame accepts any actual value, so this is always sound. The
|
||||
//! exception is uninitialized types: merging them means a broken construction
|
||||
//! flow — here an honest error, not a silent `Top`.
|
||||
|
||||
pub(super) mod descs;
|
||||
|
||||
|
|
@ -17,13 +17,13 @@ use crate::model::Method;
|
|||
|
||||
use descs::write_local;
|
||||
|
||||
/// Иерархия классов для LUB: достаточно цепочки суперклассов.
|
||||
/// The class hierarchy for LUB: the superclass chain is enough.
|
||||
pub trait Hierarchy {
|
||||
/// Прямой суперкласс (`None` — `java/lang/Object` или неизвестный).
|
||||
/// The direct superclass (`None` — `java/lang/Object` or unknown).
|
||||
fn superclass(&self, class: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Контекст слияния: имена для ошибок + иерархия для LCA.
|
||||
/// Merge context: names for errors + hierarchy for LCA.
|
||||
pub(super) struct Merger<'h> {
|
||||
pub class: String,
|
||||
pub method: String,
|
||||
|
|
@ -38,7 +38,7 @@ impl Merger<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Влить входящее состояние; `true` — слот изменился.
|
||||
/// Merge an incoming state; `true` — the slot changed.
|
||||
pub fn merge_into(
|
||||
&self,
|
||||
slot: &mut Option<State>,
|
||||
|
|
@ -61,7 +61,7 @@ impl Merger<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Начальное состояние: параметры (+ `this`) поверх `Top`.
|
||||
/// The initial state: parameters (+ `this`) over `Top`.
|
||||
pub fn initial(&self, method: &Method, max_locals: u16) -> Result<State, ClassfileError> {
|
||||
let is_static = method.access_flags & 0x0008 != 0;
|
||||
let (params, _) =
|
||||
|
|
@ -127,19 +127,19 @@ impl Merger<'_> {
|
|||
Ok(first.clone())
|
||||
}
|
||||
(Object(a), Object(b)) => Ok(Object(self.common_superclass(a, b))),
|
||||
// Одинаковые уже разобраны выше; здесь неинициализированные
|
||||
// всегда разные — это сломанный поток конструирования.
|
||||
// Equal ones are handled above; here uninitialized are always
|
||||
// different — that's a broken construction flow.
|
||||
(VerificationType::UninitializedThis | VerificationType::Uninitialized(_), _)
|
||||
| (_, VerificationType::UninitializedThis | VerificationType::Uninitialized(_)) => {
|
||||
Err(self.invalid("слияние неинициализированных типов"))
|
||||
}
|
||||
// Несочетаемые инициализированные (например, `int` из тела `try`
|
||||
// и `Throwable` из handler'а в одном слоте): `Top`, как у `javac`.
|
||||
// Incompatible initialized (e.g. `int` from the `try` body and
|
||||
// `Throwable` from a handler in one slot): `Top`, like `javac`.
|
||||
_ => Ok(Top),
|
||||
}
|
||||
}
|
||||
|
||||
/// Наименьший общий предок: подъём по цепочкам, запасной — `Object`.
|
||||
/// Least common ancestor: climb the chains, fallback — `Object`.
|
||||
fn common_superclass(&self, first: &str, second: &str) -> String {
|
||||
if first == second {
|
||||
return first.to_string();
|
||||
|
|
@ -176,7 +176,7 @@ impl Merger<'_> {
|
|||
"java/lang/Object".to_string()
|
||||
}
|
||||
|
||||
/// LCA массивов: та же размерность + LCA элементов, иначе `Object`.
|
||||
/// Arrays' LCA: same dimensionality + element LCA, otherwise `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));
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! `StackMapTable`: представление фреймов и вычисление после трансформаций.
|
||||
//! `StackMapTable`: frame representation and computation after transformations.
|
||||
//!
|
||||
//! Фреймы — самая дорогая часть любого обфускатора на Rust: JVM-верификатор
|
||||
//! (class version ≥ 50) требует точного `StackMapTable`, иначе класс не грузится.
|
||||
//! Варианты реализации и их цены — `docs/stack.md`.
|
||||
//! Frames are the most expensive part of any Rust obfuscator: the JVM verifier
|
||||
//! (class version ≥ 50) demands an exact `StackMapTable`, or the class won't load.
|
||||
//! Implementation options and their costs — `docs/stack.md`.
|
||||
//!
|
||||
//! Метод: CFG по меткам → worklist с типами (`xfer`) → слияние LUB (`merge`)
|
||||
//! → эмиссия компактных кадров (`emit`). Байтовые смещения берутся тем же
|
||||
//! преобразованием, что использует writer, — иначе кадры разъедутся с байтами.
|
||||
//! Method: CFG over labels → worklist with types (`xfer`) → LUB merge (`merge`)
|
||||
//! → emit compact frames (`emit`). Byte offsets come from the same encoding the
|
||||
//! writer uses — otherwise frames would drift apart from the bytes.
|
||||
|
||||
mod emit;
|
||||
mod flow;
|
||||
|
|
@ -27,7 +27,7 @@ pub use merge::Hierarchy;
|
|||
use merge::Merger;
|
||||
use xfer::{State, transfer};
|
||||
|
||||
/// Тип верификации из JVMS §4.10.1.
|
||||
/// A verification type from JVMS §4.10.1.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VerificationType {
|
||||
Top,
|
||||
|
|
@ -37,14 +37,14 @@ pub enum VerificationType {
|
|||
Long,
|
||||
Null,
|
||||
UninitializedThis,
|
||||
/// Внутреннее имя класса (`java/lang/Object`) или дескриптор массива.
|
||||
/// The class's internal name (`java/lang/Object`) or an array descriptor.
|
||||
Object(String),
|
||||
/// Байтовое смещение инструкции `new`.
|
||||
/// The byte offset of the `new` instruction.
|
||||
Uninitialized(u16),
|
||||
}
|
||||
|
||||
impl VerificationType {
|
||||
/// Тип, соответствующий примитиву.
|
||||
/// The type matching a primitive.
|
||||
pub fn of_prim(p: Prim) -> Self {
|
||||
match p {
|
||||
Prim::Int => VerificationType::Integer,
|
||||
|
|
@ -55,7 +55,7 @@ impl VerificationType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Запись `StackMapTable` (JVMS §4.7.4).
|
||||
/// A `StackMapTable` entry (JVMS §4.7.4).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StackMapFrame {
|
||||
Same {
|
||||
|
|
@ -80,10 +80,11 @@ pub enum StackMapFrame {
|
|||
},
|
||||
}
|
||||
|
||||
/// Вычислить фреймы для тела метода после всех трансформаций.
|
||||
/// Compute the frames for a method body after all transformations.
|
||||
///
|
||||
/// Кадры нужны только классам версии ≥ 50; `jsr`/`ret` отвергаются (такие
|
||||
/// классы старше и кадры им не нужны). Иерархия — для LUB ссылочных типов.
|
||||
/// Frames are needed only for version ≥ 50 classes; `jsr`/`ret` are rejected
|
||||
/// (such classes are older and need no frames). The hierarchy is for the LUB of
|
||||
/// reference types.
|
||||
pub fn compute_frames(
|
||||
class: &str,
|
||||
method: &Method,
|
||||
|
|
@ -112,7 +113,7 @@ pub fn compute_frames(
|
|||
emit::emit_frames(&states, &targets, &initial.locals, &merger)
|
||||
}
|
||||
|
||||
/// Байтовое смещение каждой инструкции — тем же кодированием, что в writer'е.
|
||||
/// The byte offset of each instruction — using the same encoding as the writer.
|
||||
fn layout(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
@ -144,7 +145,7 @@ fn layout(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Основной цикл: состояния текут по рёбрам CFG до неподвижной точки.
|
||||
/// Main loop: states flow along CFG edges to a fixed point.
|
||||
fn run_worklist(
|
||||
merger: &Merger<'_>,
|
||||
bare: &[&Insn],
|
||||
|
|
@ -187,7 +188,7 @@ fn run_worklist(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Влить состояние в инструкцию; `true` — изменилось, продолжить обход.
|
||||
/// Merge a state into an instruction; `true` — changed, keep walking.
|
||||
fn flow_into(
|
||||
merger: &Merger<'_>,
|
||||
states: &mut [Option<State>],
|
||||
|
|
@ -218,8 +219,8 @@ fn flow_into(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
/// Тип исключения на входе в handler (`catch-all` — любое throwable).
|
||||
/// CFG edges without exceptions: fallthrough + branch targets.
|
||||
/// The exception type at a handler's entry (`catch-all` — any throwable).
|
||||
fn catch_type(catch: &Option<String>) -> VerificationType {
|
||||
match catch {
|
||||
Some(name) => VerificationType::Object(name.clone()),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Чтение/запись бинарного формата `.class` (JVMS §4) через backend
|
||||
//! `ristretto_classfile`: обзор кандидатов и решение — `docs/stack.md`.
|
||||
//! Reading/writing the binary `.class` format (JVMS §4) through the
|
||||
//! `ristretto_classfile` backend: candidate review and decision in
|
||||
//! `docs/stack.md`.
|
||||
|
||||
mod frames;
|
||||
mod parse;
|
||||
|
|
@ -10,20 +11,21 @@ pub use frames::{Hierarchy, StackMapFrame, VerificationType, compute_frames};
|
|||
use crate::error::ClassfileError;
|
||||
use crate::model::ClassFile;
|
||||
|
||||
/// Разобрать один `.class` в [`ClassFile`].
|
||||
/// Parse one `.class` into a [`ClassFile`].
|
||||
pub fn parse(bytes: &[u8]) -> Result<ClassFile, ClassfileError> {
|
||||
parse::class::parse_class(bytes)
|
||||
}
|
||||
|
||||
/// Записать [`ClassFile`] в байты, включая пересчёт `Code`/`StackMapTable`.
|
||||
/// Write a [`ClassFile`] to bytes, including `Code`/`StackMapTable` recompute.
|
||||
pub fn write(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> {
|
||||
write::write_class(class)
|
||||
}
|
||||
|
||||
/// Классы, которые не изменялись, должны уезжать в выходной архив «как есть».
|
||||
/// Classes that were not modified must go to the output archive "as-is".
|
||||
///
|
||||
/// Это дешевле и безопаснее, чем round-trip через наш IR: нет риска потерять
|
||||
/// неизвестный атрибут. Реализация зависит от решения по backend (P1).
|
||||
/// This is cheaper and safer than a round-trip through our IR: no risk of
|
||||
/// losing an unknown attribute. The implementation depends on the backend
|
||||
/// decision (P1).
|
||||
pub fn passthrough_needed(class: &ClassFile) -> bool {
|
||||
class.methods.is_empty() && class.fields.is_empty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! Атрибуты backend'а → атрибуты нашего IR.
|
||||
//! Backend attributes → our IR attributes.
|
||||
//!
|
||||
//! `BootstrapMethods`, `EnclosingMethod`, `MethodParameters` и `ConstantValue`
|
||||
//! моделируются явно (в них лежат ссылки пула, переживающие пересборку);
|
||||
//! остальное структурированное, но нам ненужное, уезжает в `Raw` как есть.
|
||||
//! `Code` и `StackMapTable` здесь не обрабатываются — их разбирает вызывающий
|
||||
//! код метода (см. `super::class`).
|
||||
//! `BootstrapMethods`, `EnclosingMethod`, `MethodParameters` and `ConstantValue`
|
||||
//! are modeled explicitly (they hold pool references that survive rebuilding);
|
||||
//! everything else that is structured but unneeded by us goes to `Raw` as-is.
|
||||
//! `Code` and `StackMapTable` are not handled here — the calling method code
|
||||
//! parses them (see `super::class`).
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
|
|
@ -15,7 +15,7 @@ use super::pool::{
|
|||
use crate::error::ClassfileError;
|
||||
use crate::model::{Attribute, BootstrapMethod, InnerClass, MethodParameter, RecordComponent};
|
||||
|
||||
/// Один атрибут backend'а → наш атрибут (кроме `Code`/`StackMapTable`).
|
||||
/// One backend attribute → our attribute (except `Code`/`StackMapTable`).
|
||||
pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribute, ClassfileError> {
|
||||
Ok(match attr {
|
||||
RtAttr::SourceFile {
|
||||
|
|
@ -105,7 +105,7 @@ pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribut
|
|||
})
|
||||
}
|
||||
|
||||
/// Несмоделированный атрибут → `Raw`: имя + байты `info` (без заголовка).
|
||||
/// An unmodeled attribute → `Raw`: name + `info` bytes (without the header).
|
||||
pub(super) fn raw(attr: &RtAttr) -> Result<Attribute, ClassfileError> {
|
||||
let mut bytes = Vec::new();
|
||||
attr.to_bytes(&mut bytes)
|
||||
|
|
@ -131,7 +131,7 @@ fn inner(
|
|||
})
|
||||
}
|
||||
|
||||
/// Метод объемлющего класса (`0` — только класс, без метода).
|
||||
/// The enclosing class's method (`0` — class only, no method).
|
||||
fn enclosing_method(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
@ -194,7 +194,7 @@ fn bootstrap(
|
|||
})
|
||||
}
|
||||
|
||||
/// Индекс пула, где `0` означает отсутствие значения.
|
||||
/// A pool index where `0` means the value is absent.
|
||||
fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
|
|
@ -203,7 +203,7 @@ fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>,
|
|||
}
|
||||
}
|
||||
|
||||
/// Индекс `CONSTANT_Utf8`, где `0` означает отсутствие значения.
|
||||
/// A `CONSTANT_Utf8` index where `0` means the value is absent.
|
||||
fn optional_utf8(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Класс целиком: backend `ClassFile` → наш [`ClassFile`](crate::model::ClassFile).
|
||||
//! The whole class: backend `ClassFile` → our [`ClassFile`](crate::model::ClassFile).
|
||||
|
||||
use ristretto_classfile::attributes::Attribute as RtAttr;
|
||||
use ristretto_classfile::{
|
||||
|
|
@ -11,7 +11,7 @@ use super::pool::{class_name, utf8};
|
|||
use crate::error::ClassfileError;
|
||||
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version};
|
||||
|
||||
/// Разобрать байты `.class` в наш IR.
|
||||
/// Parse `.class` bytes into our 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;
|
||||
|
|
@ -99,7 +99,7 @@ fn convert_attrs(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Индекс суперкласса, где `0` — его отсутствие (`java/lang/Object`).
|
||||
/// The superclass index, where `0` means absent (`java/lang/Object`).
|
||||
fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
|
||||
if index == 0 {
|
||||
Ok(None)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
//! Кадры `StackMapTable` при чтении: вариант в вариант, дельты как есть.
|
||||
//! `StackMapTable` frames on read: variant to variant, deltas as-is.
|
||||
//!
|
||||
//! Backend хранит кадры в номерах инструкций (см. `from_bytes_code_attributes`),
|
||||
//! наш IR — тоже: writer сам пересчитает их в байты при `to_bytes`.
|
||||
//! The backend stores frames in instruction indices (see
|
||||
//! `from_bytes_code_attributes`), and so does our IR: the writer recomputes them
|
||||
//! into bytes on `to_bytes`.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::{StackFrame, VerificationType as RtType};
|
||||
|
|
@ -10,7 +11,7 @@ use super::super::pool::class_name;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::format::{StackMapFrame, VerificationType};
|
||||
|
||||
/// Кадры backend'а → наши (индексные дельты — как есть).
|
||||
/// Backend frames → ours (index deltas — as-is).
|
||||
pub(super) fn to_frames(
|
||||
frames: &[StackFrame],
|
||||
pool: &ConstantPool<'_>,
|
||||
|
|
@ -66,7 +67,7 @@ fn to_frame(frame: &StackFrame, pool: &ConstantPool<'_>) -> Result<StackMapFrame
|
|||
})
|
||||
}
|
||||
|
||||
/// Единственный элемент стека кадра (`same_locals_1_stack_item`).
|
||||
/// The frame's single stack element (`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()) {
|
||||
|
|
@ -84,7 +85,7 @@ fn types(
|
|||
values.iter().map(|value| vtype(value, pool)).collect()
|
||||
}
|
||||
|
||||
/// Тип верификации backend'а → наш тип (имя класса берётся из constant pool).
|
||||
/// Backend verification type → our type (the class name comes from the constant pool).
|
||||
fn vtype(value: &RtType, pool: &ConstantPool<'_>) -> Result<VerificationType, ClassfileError> {
|
||||
Ok(match value {
|
||||
RtType::Top => VerificationType::Top,
|
||||
|
|
@ -99,7 +100,7 @@ fn vtype(value: &RtType, pool: &ConstantPool<'_>) -> Result<VerificationType, Cl
|
|||
})
|
||||
}
|
||||
|
||||
/// Тип перехвата (`0` — catch-all для `finally`/`any`).
|
||||
/// The catch type (`0` — catch-all for `finally`/`any`).
|
||||
pub(super) fn catch_type(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Метки тела метода: где обязаны стоять и как резолвятся.
|
||||
//! Method-body labels: where they must stand and how they resolve.
|
||||
//!
|
||||
//! Backend нумерует инструкции по порядку — метки ставятся прямо на номера,
|
||||
//! без байтовых смещений.
|
||||
//! The backend numbers instructions in order — labels are placed directly on the
|
||||
//! numbers, without byte offsets.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
|
|
@ -11,10 +11,10 @@ use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::Label;
|
||||
|
||||
/// Номера инструкций, на которых обязана стоять метка.
|
||||
/// Instruction numbers that must carry a label.
|
||||
///
|
||||
/// `extra` — позиции отладочных таблиц (уже номерами): вставки пассов
|
||||
/// не должны оставлять их без меток.
|
||||
/// `extra` — debug-table positions (already as numbers): pass insertions must
|
||||
/// not leave them without a label.
|
||||
pub(super) fn label_indexes(
|
||||
code: &[Instruction],
|
||||
exception_table: &[ExceptionTableEntry],
|
||||
|
|
@ -36,7 +36,7 @@ pub(super) fn label_indexes(
|
|||
Ok(indexes)
|
||||
}
|
||||
|
||||
/// Цели ветвлений backend'а: для `if*`/`goto` — абсолютный номер, для `switch` — дельта.
|
||||
/// Backend branch targets: for `if*`/`goto` an absolute number, for `switch` a delta.
|
||||
fn branch_indexes(instruction: &Instruction, index: u16) -> Vec<i32> {
|
||||
let base = i32::from(index);
|
||||
match instruction {
|
||||
|
|
@ -73,7 +73,7 @@ fn once(value: i32) -> std::iter::Once<i32> {
|
|||
std::iter::once(value)
|
||||
}
|
||||
|
||||
/// Превратить отсортированный набор номеров в последовательные метки.
|
||||
/// Turn a sorted set of numbers into sequential labels.
|
||||
pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
|
||||
indexes
|
||||
.iter()
|
||||
|
|
@ -82,7 +82,7 @@ pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Метка для номера инструкции.
|
||||
/// The label for an instruction number.
|
||||
pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label, ClassfileError> {
|
||||
labels
|
||||
.get(&u32::from(index))
|
||||
|
|
@ -90,7 +90,7 @@ pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label,
|
|||
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для инструкции {index}")))
|
||||
}
|
||||
|
||||
/// Позиции отладочных таблиц, которым нужны метки.
|
||||
/// Debug-table positions that need labels.
|
||||
pub(super) fn collect_debug_labels(
|
||||
attributes: &[RtAttr],
|
||||
starts: &[u16],
|
||||
|
|
@ -122,7 +122,7 @@ pub(super) fn collect_debug_labels(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Байт → номер инструкции (только границы инструкций).
|
||||
/// Byte → instruction number (instruction boundaries only).
|
||||
pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileError> {
|
||||
starts
|
||||
.iter()
|
||||
|
|
@ -131,7 +131,7 @@ pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileE
|
|||
.ok_or_else(|| ClassfileError::Parse(format!("смещение {byte} мимо инструкции")))
|
||||
}
|
||||
|
||||
/// Конец диапазона `[start, start+length)`: индекс за концом.
|
||||
/// End of the range `[start, start+length)`: the index past the end.
|
||||
fn end_index(
|
||||
starts: &[u16],
|
||||
start: u16,
|
||||
|
|
@ -148,7 +148,7 @@ fn end_index(
|
|||
byte_to_index(starts, end)
|
||||
}
|
||||
|
||||
/// Метка для байтового смещения отладочной таблицы.
|
||||
/// The label for a debug table's byte offset.
|
||||
pub(super) fn resolve_byte(
|
||||
labels: &BTreeMap<u32, Label>,
|
||||
starts: &[u16],
|
||||
|
|
@ -161,7 +161,7 @@ pub(super) fn resolve_byte(
|
|||
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для смещения {byte}")))
|
||||
}
|
||||
|
||||
/// Метка конца диапазона отладочной таблицы.
|
||||
/// The end label of a debug table's range.
|
||||
pub(super) fn resolve_end(
|
||||
labels: &BTreeMap<u32, Label>,
|
||||
starts: &[u16],
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! `Code` backend'а → тело метода нашего IR: метки, исключения, кадры.
|
||||
//! Backend `Code` → our IR method body: labels, exceptions, frames.
|
||||
//!
|
||||
//! Backend уже перевёл байтовые смещения в номера инструкций — конвертеры
|
||||
//! работают прямо с ними: метки ставятся на целях переходов, границах
|
||||
//! try-блоков и позициях отладочных таблиц; кадры переносятся как есть
|
||||
//! (индексные дельты). Позиции `LineNumberTable`/`LocalVariableTable` —
|
||||
//! тоже метки, поэтому вставки пассов их не ломают (байты досчитает writer).
|
||||
//! The backend already turned byte offsets into instruction indices — the
|
||||
//! converters work directly with them: labels are placed on branch targets,
|
||||
//! try-block boundaries and debug-table positions; frames are carried over as-is
|
||||
//! (index deltas). `LineNumberTable`/`LocalVariableTable` positions are labels
|
||||
//! too, so pass insertions don't break them (the writer recomputes the bytes).
|
||||
|
||||
mod frames;
|
||||
mod labels;
|
||||
|
|
@ -27,7 +27,7 @@ use crate::model::{
|
|||
use frames::{catch_type, to_frames};
|
||||
use labels::{assign, collect_debug_labels, label_indexes, lookup, resolve_byte, resolve_end};
|
||||
|
||||
/// Тело метода целиком: backend `Code` → наш [`Code`].
|
||||
/// The whole method body: backend `Code` → our [`Code`].
|
||||
pub(super) fn convert_code(
|
||||
max_stack: u16,
|
||||
max_locals: u16,
|
||||
|
|
@ -150,7 +150,7 @@ pub(super) fn convert_code(
|
|||
})
|
||||
}
|
||||
|
||||
/// Байтовые смещения инструкций и длина тела метода.
|
||||
/// Instruction byte offsets and the method body length.
|
||||
fn layout(code: &[Instruction]) -> Result<(Vec<u16>, u16), ClassfileError> {
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
let mut offsets = Vec::with_capacity(code.len());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Простые инструкции без ссылок на пул (кроме `ldc`) и без ветвлений.
|
||||
//! Simple instructions with no pool references (except `ldc`) and no branches.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::Instruction;
|
||||
|
|
@ -7,7 +7,7 @@ use super::super::pool::const_from;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Arith, ArrayKind, Const, Convert, Insn, Prim, SlotKind};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
/// `Some` — the instruction is covered; `None` — hand it to the next converters.
|
||||
pub(super) fn convert_basic(
|
||||
instruction: &Instruction,
|
||||
pool: &ConstantPool<'_>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Ветвления, `switch` и возвраты: абсолютные/относительные цели → метки.
|
||||
//! Branches, `switch` and returns: absolute/relative targets → labels.
|
||||
|
||||
use ristretto_classfile::attributes::{Instruction, LookupSwitch, TableSwitch};
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ use super::Resolver;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{BranchCond, Cond, Insn, Label, Prim};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
/// `Some` — the instruction is covered; `None` — hand it to the next converters.
|
||||
pub(super) fn convert_flow(
|
||||
instruction: &Instruction,
|
||||
index: u16,
|
||||
|
|
@ -52,7 +52,7 @@ fn branch(cond: BranchCond, target: u16, resolve: &Resolver<'_>) -> Result<Insn,
|
|||
Ok(Insn::Branch(cond, resolve(target)?))
|
||||
}
|
||||
|
||||
/// Цель `switch` — номер инструкции: база плюс относительная дельта backend'а.
|
||||
/// A `switch` target — an instruction number: base plus the backend's relative delta.
|
||||
fn abs(index: u16, delta: i32, resolve: &Resolver<'_>) -> Result<Label, ClassfileError> {
|
||||
let absolute = i32::from(index)
|
||||
.checked_add(delta)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
//! `Instruction` backend'а → [`Insn`](crate::insn::Insn) нашего IR.
|
||||
//! Backend `Instruction` → [`Insn`](crate::insn::Insn) of our IR.
|
||||
//!
|
||||
//! Смещения ветвлений приходят из backend'а (абсолютные для `if*`/`goto`,
|
||||
//! относительные от начала инструкции для `switch`), поэтому конвертация
|
||||
//! принимает резолвер смещения в [`Label`](crate::insn::Label) — метки
|
||||
//! назначает вызывающий код.
|
||||
//! Branch offsets come from the backend (absolute for `if*`/`goto`, relative to
|
||||
//! the instruction start for `switch`), so conversion takes a resolver from an
|
||||
//! offset to a [`Label`](crate::insn::Label) — the calling code assigns labels.
|
||||
|
||||
mod basic;
|
||||
mod flow;
|
||||
|
|
@ -15,10 +14,10 @@ use ristretto_classfile::attributes::Instruction;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label};
|
||||
|
||||
/// Резолвит абсолютное смещение байткода в метку.
|
||||
/// Resolves an absolute bytecode offset into a label.
|
||||
pub(super) type Resolver<'a> = dyn Fn(u16) -> Result<Label, ClassfileError> + 'a;
|
||||
|
||||
/// Конвертировать одну инструкцию. `index` — её номер в теле метода (нужно `switch`).
|
||||
/// Convert one instruction. `index` — its number in the method body (`switch` needs it).
|
||||
pub(super) fn convert(
|
||||
instruction: &Instruction,
|
||||
index: u16,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Поля, вызовы и создание объектов: индексы пула → строки IR.
|
||||
//! Fields, calls and object creation: pool indices → IR strings.
|
||||
|
||||
use ristretto_classfile::ConstantPool;
|
||||
use ristretto_classfile::attributes::{ArrayType, Instruction};
|
||||
|
|
@ -7,7 +7,7 @@ use super::super::pool::{class_name, dynamic_parts, expect_kind, is_interface, r
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{ArrayKind, Insn, InvokeKind};
|
||||
|
||||
/// `Some` — инструкция покрыта, `None` — отдать следующим конвертерам.
|
||||
/// `Some` — the instruction is covered; `None` — hand it to the next converters.
|
||||
pub(super) fn convert_ref(
|
||||
instruction: &Instruction,
|
||||
pool: &ConstantPool<'_>,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Backend `ristretto_classfile` → наш IR: пул, инструкции, тела методов.
|
||||
//! Backend `ristretto_classfile` → our IR: pool, instructions, method bodies.
|
||||
//!
|
||||
//! Пока ниоткуда не подключён (P1): `format` вернёт `Unimplemented`, пока не
|
||||
//! будут готовы `parse`/`write` классового уровня поверх этих конвертеров.
|
||||
//! Дерево уже разбито по ответственностям, чтобы подключение было одним
|
||||
//! `mod parse` без рефакторинга.
|
||||
//! Not wired in from anywhere yet (P1): `format` returns `Unimplemented` until
|
||||
//! class-level `parse`/`write` are ready on top of these converters. The tree
|
||||
//! is already split by responsibility so wiring is a single `mod parse` with no
|
||||
//! refactoring.
|
||||
|
||||
pub(super) mod attrs;
|
||||
pub(super) mod class;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
//! Разрешение индексов constant pool в строки нашего IR.
|
||||
//! Resolving constant-pool indices into strings of our IR.
|
||||
|
||||
use ristretto_classfile::{Constant, ConstantPool, ReferenceKind};
|
||||
|
||||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Const, InvokeKind};
|
||||
|
||||
/// Имя класса по `CONSTANT_Class`.
|
||||
/// The class name from a `CONSTANT_Class`.
|
||||
pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
|
||||
let name = pool
|
||||
.try_get_class(index)
|
||||
|
|
@ -13,7 +13,7 @@ pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String,
|
|||
Ok(name.to_string())
|
||||
}
|
||||
|
||||
/// `(owner, name, descriptor)` для `CONSTANT_Fieldref`/`_Methodref`.
|
||||
/// `(owner, name, descriptor)` for a `CONSTANT_Fieldref`/`_Methodref`.
|
||||
pub(super) fn ref_parts(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
@ -45,7 +45,7 @@ pub(super) fn ref_parts(
|
|||
Ok((owner, name, descriptor))
|
||||
}
|
||||
|
||||
/// `(name, descriptor)` для `CONSTANT_NameAndType`.
|
||||
/// `(name, descriptor)` for a `CONSTANT_NameAndType`.
|
||||
pub(super) fn name_and_type(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
@ -58,7 +58,7 @@ pub(super) fn name_and_type(
|
|||
Ok((name, descriptor))
|
||||
}
|
||||
|
||||
/// Строка из `CONSTANT_Utf8`.
|
||||
/// The string from a `CONSTANT_Utf8`.
|
||||
pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
|
||||
let value = pool
|
||||
.try_get_utf8(index)
|
||||
|
|
@ -66,7 +66,7 @@ pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, Classf
|
|||
Ok(value.to_string())
|
||||
}
|
||||
|
||||
/// Константа для `ldc`/`ldc_w`/`ldc2_w`.
|
||||
/// The constant for `ldc`/`ldc_w`/`ldc2_w`.
|
||||
pub(super) fn const_from(pool: &ConstantPool<'_>, index: u16) -> Result<Const, ClassfileError> {
|
||||
let constant = pool
|
||||
.try_get(index)
|
||||
|
|
@ -111,7 +111,7 @@ pub(super) fn const_from(pool: &ConstantPool<'_>, index: u16) -> Result<Const, C
|
|||
})
|
||||
}
|
||||
|
||||
/// `(name, descriptor, bootstrap)` для `CONSTANT_InvokeDynamic`.
|
||||
/// `(name, descriptor, bootstrap)` for a `CONSTANT_InvokeDynamic`.
|
||||
pub(super) fn dynamic_parts(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
@ -134,12 +134,12 @@ pub(super) fn dynamic_parts(
|
|||
Ok((name, descriptor, bootstrap))
|
||||
}
|
||||
|
||||
/// Вид вызова по индексу ссылки: интерфейсная или обычная.
|
||||
/// Call kind by reference index: interface or regular.
|
||||
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`.
|
||||
/// JVMS §5.4.3.5: the `reference_kind` number for a `CONSTANT_MethodHandle`.
|
||||
pub(super) fn kind_code(kind: &ReferenceKind) -> u8 {
|
||||
match kind {
|
||||
ReferenceKind::GetField => 1,
|
||||
|
|
@ -154,11 +154,11 @@ pub(super) fn kind_code(kind: &ReferenceKind) -> u8 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Проверка, что `Invoke` действительно ссылается на ожидаемый вид.
|
||||
/// Checks that an `Invoke` really refers to the expected kind.
|
||||
///
|
||||
/// JVM принимает `Methodref` и `InterfaceMethodref` для всех видов вызова,
|
||||
/// кроме `invokeinterface` (там только интерфейсная); строгость сверх JVM
|
||||
/// ломает чтение реальных классов (статические методы интерфейсов).
|
||||
/// The JVM accepts both `Methodref` and `InterfaceMethodref` for every call
|
||||
/// kind except `invokeinterface` (interface reference only there); being
|
||||
/// stricter than the JVM breaks reading real classes (interface static methods).
|
||||
pub(super) fn expect_kind(
|
||||
pool: &ConstantPool<'_>,
|
||||
index: u16,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Атрибуты нашего IR → атрибуты backend'а.
|
||||
//! Our IR attributes → backend attributes.
|
||||
//!
|
||||
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend
|
||||
//! хранит только `name_index`, строка восстанавливается при чтении.
|
||||
//! Attribute names (`SourceFile`, …) are interned into the pool right here — the
|
||||
//! backend stores only `name_index`, and the string is recovered on read.
|
||||
|
||||
use ristretto_classfile::attributes::{
|
||||
Attribute as RtAttr, LineNumber as RtLine, LocalVariableTable as RtLocal,
|
||||
|
|
@ -14,13 +14,13 @@ use crate::error::ClassfileError;
|
|||
use crate::insn::Label;
|
||||
use crate::model::{Attribute, LineNumber, LocalVariable, LocalVariableType};
|
||||
|
||||
/// Позиции таблиц кода: метки → байты текущего layout.
|
||||
/// Code-table positions: labels → bytes of the current 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 хранит номера инструкций и маппит сам.
|
||||
/// `LineNumberTable`: the backend stores instruction numbers and maps them itself.
|
||||
pub(super) fn line_number_table(
|
||||
lines: &[LineNumber],
|
||||
pool: &mut Pool,
|
||||
|
|
@ -43,7 +43,7 @@ pub(super) fn line_number_table(
|
|||
})
|
||||
}
|
||||
|
||||
/// `LocalVariableTable`/`TypeTable` с пересчётом диапазона.
|
||||
/// `LocalVariableTable`/`TypeTable` with the range recomputed.
|
||||
pub(super) fn local_table(
|
||||
vars: &[LocalVariable],
|
||||
pool: &mut Pool,
|
||||
|
|
@ -66,7 +66,7 @@ pub(super) fn local_table(
|
|||
.collect::<Result<_, ClassfileError>>()?,
|
||||
})
|
||||
}
|
||||
/// Та же таблица для типов (`signature` вместо дескриптора).
|
||||
/// The same table for types (`signature` instead of a descriptor).
|
||||
pub(super) fn local_type_table(
|
||||
vars: &[LocalVariableType],
|
||||
pool: &mut Pool,
|
||||
|
|
@ -103,7 +103,7 @@ fn range(
|
|||
Ok((first, length))
|
||||
}
|
||||
|
||||
/// Один наш атрибут → атрибут backend'а.
|
||||
/// One of our attributes → a backend attribute.
|
||||
pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
|
||||
Ok(match attr {
|
||||
Attribute::SourceFile(name) => RtAttr::SourceFile {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Кадры `StackMapTable` при записи: вариант в вариант, дельты по инструкциям.
|
||||
//! `StackMapTable` frames on write: variant to variant, deltas per instruction.
|
||||
//!
|
||||
//! Наш IR хранит `offset_delta` в номерах инструкций; backend при `to_bytes`
|
||||
//! сам пересчитает их в байты. Компактный формат (`Same`/`SameLocals`) годен
|
||||
//! только когда **байтовая** дельта ≤ 63 — иначе нужен расширенный вариант.
|
||||
//! Выбор делаем по байтовой дельте: байты считаются от layout текущего метода.
|
||||
//! Our IR stores `offset_delta` in instruction numbers; the backend recomputes
|
||||
//! them into bytes on `to_bytes`. The compact form (`Same`/`SameLocals`) is valid
|
||||
//! only when the **byte** delta is ≤ 63 — otherwise the extended form is needed.
|
||||
//! We choose by the byte delta: bytes are computed from the current method's layout.
|
||||
|
||||
use ristretto_classfile::attributes::{StackFrame as RtFrame, VerificationType as RtType};
|
||||
|
||||
|
|
@ -11,10 +11,10 @@ use super::super::pool::Pool;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::format::{StackMapFrame, VerificationType};
|
||||
|
||||
/// Байтовое смещение начала инструкции (для расчёта дельт кадров).
|
||||
/// The byte offset of an instruction's start (for computing frame deltas).
|
||||
type ByteOf<'a> = &'a (dyn Fn(u32) -> Result<u16, ClassfileError> + 'a);
|
||||
|
||||
/// Кадры IR → кадры backend'а (индексные дельты — как есть).
|
||||
/// IR frames → backend frames (index deltas — as-is).
|
||||
pub(super) fn convert_frames(
|
||||
frames: &[StackMapFrame],
|
||||
pool: &mut Pool,
|
||||
|
|
@ -32,7 +32,7 @@ pub(super) fn convert_frames(
|
|||
last_instruction + delta + 1
|
||||
};
|
||||
let byte = byte_of(instruction)?;
|
||||
// Байтовая дельта относительно конца предыдущего кадра.
|
||||
// Byte delta relative to the end of the previous frame.
|
||||
let byte_delta = if first {
|
||||
byte
|
||||
} else {
|
||||
|
|
@ -48,7 +48,7 @@ pub(super) fn convert_frames(
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
/// `byte_delta` — истинная байтовая дельта; компактный вид годен лишь при ≤ 63.
|
||||
/// `byte_delta` is the true byte delta; the compact form is valid only when ≤ 63.
|
||||
fn build_frame(
|
||||
frame: &StackMapFrame,
|
||||
pool: &mut Pool,
|
||||
|
|
@ -60,8 +60,8 @@ fn build_frame(
|
|||
Ok(match frame {
|
||||
StackMapFrame::Same { .. } => {
|
||||
if byte_delta <= 63 {
|
||||
// Backend при verify()/to_bytes читает frame_type как дельту по
|
||||
// ИНСТРУКЦИЯМ и сам пересчитает её в байты (≤ 63 — влезает).
|
||||
// The backend, in verify()/to_bytes, reads frame_type as a delta by
|
||||
// INSTRUCTION and recomputes it into bytes itself (≤ 63 fits).
|
||||
RtFrame::SameFrame {
|
||||
frame_type: delta as u8,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Наш [`Insn`](crate::insn::Insn) → `Instruction` backend'а.
|
||||
//! Our [`Insn`](crate::insn::Insn) → the backend's `Instruction`.
|
||||
//!
|
||||
//! Обратная сторона `super::super::parse::insns`: метки резолвятся в
|
||||
//! абсолютные номера инструкций (`switch` — в дельты от себя), константы
|
||||
//! и ссылки уходят в пул через [`Pool`](super::super::pool::Pool).
|
||||
//! The reverse side of `super::super::parse::insns`: labels resolve into
|
||||
//! absolute instruction numbers (`switch` — into deltas from itself), constants
|
||||
//! and references go to the pool via [`Pool`](super::super::pool::Pool).
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use ristretto_classfile::attributes::{Instruction as RtInsn, LookupSwitch, TableSwitch};
|
||||
|
|
@ -13,9 +13,9 @@ use super::{branch, delta, fit_index, ret_op};
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Insn, Label, SlotKind};
|
||||
|
||||
/// Одна инструкция IR → инструкция backend'а (`None` — маркер метки).
|
||||
/// One IR instruction → a backend instruction (`None` — a label marker).
|
||||
///
|
||||
/// `index` — номер инструкции в методе (нужен `switch`-дельтам).
|
||||
/// `index` — the instruction's number in the method (needed for `switch` deltas).
|
||||
pub(crate) fn convert(
|
||||
insn: &Insn,
|
||||
index: u32,
|
||||
|
|
@ -178,7 +178,7 @@ fn store(slot: u16, kind: SlotKind) -> RtInsn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Короткая форма при слоте ≤255, иначе `wide`-форма.
|
||||
/// The short form when the slot is ≤255, otherwise the `wide` form.
|
||||
fn narrow(slot: u16, short: fn(u8) -> RtInsn, wide: fn(u16) -> RtInsn) -> RtInsn {
|
||||
u8::try_from(slot).map(short).unwrap_or_else(|_| wide(slot))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Тело метода нашего IR → атрибут `Code` backend'а.
|
||||
//! Our IR method body → the backend `Code` attribute.
|
||||
//!
|
||||
//! Метки резолвятся в номера инструкций один раз здесь; дальше backend сам
|
||||
//! пересчитывает номера в байтовые смещения при `to_bytes`.
|
||||
//! Labels resolve into instruction numbers once, here; then the backend
|
||||
//! recomputes numbers into byte offsets on `to_bytes`.
|
||||
|
||||
mod frames;
|
||||
pub(crate) mod insns;
|
||||
|
|
@ -22,7 +22,7 @@ use crate::model::{Attribute, Code, ExceptionHandler};
|
|||
|
||||
use frames::convert_frames;
|
||||
|
||||
/// Тело метода → готовый атрибут `Code` (с логическими номерами инструкций).
|
||||
/// Method body → a ready `Code` attribute (with logical instruction numbers).
|
||||
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> {
|
||||
|
|
@ -59,7 +59,7 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
let mut attributes = Vec::with_capacity(code.attributes.len() + 1);
|
||||
for attr in &code.attributes {
|
||||
attributes.push(match attr {
|
||||
// Backend хранит номера строк в индексах и маппит сам.
|
||||
// The backend stores line numbers as indices and maps them itself.
|
||||
Attribute::LineNumberTable(lines) => attrs::line_number_table(lines, pool, &positions)?,
|
||||
Attribute::LocalVariableTable(vars) => attrs::local_table(vars, pool, &positions)?,
|
||||
Attribute::LocalVariableTypeTable(vars) => {
|
||||
|
|
@ -84,7 +84,7 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, Class
|
|||
})
|
||||
}
|
||||
|
||||
/// Метки → номера инструкций; сами маркеры из потока убираются.
|
||||
/// Labels → instruction numbers; the markers themselves are removed from the stream.
|
||||
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());
|
||||
|
|
@ -108,10 +108,10 @@ fn split_marks(insns: &[Insn]) -> Result<(BTreeMap<Label, u32>, Vec<&Insn>), Cla
|
|||
Ok((labels, bare))
|
||||
}
|
||||
|
||||
/// Номер инструкции → байтовое смещение (замер с заглушками целей).
|
||||
/// Instruction number → byte offset (measured with placeholder targets).
|
||||
///
|
||||
/// В карту входит и конец кода (`len` → длина): концы диапазонов указывают
|
||||
/// за последнюю инструкцию.
|
||||
/// The map also includes the code end (`len` → length): range ends point past
|
||||
/// the last instruction.
|
||||
pub(crate) fn measure(insns: &[RtInsn]) -> Result<BTreeMap<u32, u16>, ClassfileError> {
|
||||
let mut map = BTreeMap::new();
|
||||
let mut cursor = Cursor::new(Vec::new());
|
||||
|
|
@ -132,7 +132,7 @@ pub(crate) fn measure(insns: &[RtInsn]) -> Result<BTreeMap<u32, u16>, ClassfileE
|
|||
Ok(map)
|
||||
}
|
||||
|
||||
/// Цели ветвлений не влияют на размер — подменяем текущей позицией для замера.
|
||||
/// Branch targets don't affect size — substitute the current position for sizing.
|
||||
fn smash_targets(insn: &mut RtInsn, position: u16) {
|
||||
match insn {
|
||||
RtInsn::Ifeq(target)
|
||||
|
|
@ -178,7 +178,7 @@ fn entry(
|
|||
})
|
||||
}
|
||||
|
||||
/// Условный переход IR → `if*` backend'а.
|
||||
/// An IR conditional branch → the backend's `if*`.
|
||||
fn branch(cond: BranchCond, target: u32) -> Result<RtInsn, ClassfileError> {
|
||||
let target = fit_index(target)?;
|
||||
Ok(match cond {
|
||||
|
|
@ -211,13 +211,13 @@ fn ret_op(prim: Option<Prim>) -> RtInsn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Номер инструкции обязан влезать в `u16` (метод < 64 КБ кода).
|
||||
/// The instruction number must fit in `u16` (method < 64 KB of code).
|
||||
fn fit_index(index: u32) -> Result<u16, ClassfileError> {
|
||||
u16::try_from(index)
|
||||
.map_err(|_| ClassfileError::Write(format!("номер инструкции {index} вне u16")))
|
||||
}
|
||||
|
||||
/// Дельта `switch`: цель минус позиция самого `switch`.
|
||||
/// A `switch` delta: the target minus the `switch` instruction's own position.
|
||||
fn delta(index: u32, target: u32) -> Result<i32, ClassfileError> {
|
||||
let (index, target) = (to_i32(index)?, to_i32(target)?);
|
||||
target
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Операнды: константы, арифметика, массивы, вызовы.
|
||||
//! Operands: constants, arithmetic, arrays, calls.
|
||||
|
||||
use ristretto_classfile::attributes::{ArrayType, Instruction as RtInsn};
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ use super::super::pool::Pool;
|
|||
use crate::error::ClassfileError;
|
||||
use crate::insn::{Arith, ArrayKind, Const, Convert, InvokeKind, Prim};
|
||||
|
||||
/// Константа: короткие формы (`iconst`, `bipush`) + `ldc_w`/`ldc2_w` через пул.
|
||||
/// A constant: short forms (`iconst`, `bipush`) + `ldc_w`/`ldc2_w` through the pool.
|
||||
pub(super) fn int_const(value: &Const, pool: &mut Pool) -> Result<RtInsn, ClassfileError> {
|
||||
Ok(match value {
|
||||
Const::Int(v) => match *v {
|
||||
|
|
@ -188,7 +188,7 @@ pub(super) fn invoke(
|
|||
})
|
||||
}
|
||||
|
||||
/// Байт `count` для `invokeinterface`: слоты аргументов + `objectref`.
|
||||
/// The `count` byte for `invokeinterface`: argument slots + `objectref`.
|
||||
fn arg_count(descriptor: &str) -> Result<u8, ClassfileError> {
|
||||
let params = descriptor
|
||||
.split_once('(')
|
||||
|
|
@ -229,7 +229,7 @@ fn arg_count(descriptor: &str) -> Result<u8, ClassfileError> {
|
|||
.map_err(|_| ClassfileError::Write(format!("слишком много аргументов в {descriptor}")))
|
||||
}
|
||||
|
||||
/// Пропустить символы до разделителя (включительно); `false` — строка кончилась.
|
||||
/// Skip characters up to the separator (inclusive); `false` — the string ended.
|
||||
fn skip_to(chars: &mut std::str::Chars<'_>, end: char) -> bool {
|
||||
for char in chars {
|
||||
if char == end {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
//! Хелперы сборки `InnerClasses`/`BootstrapMethods`/`MethodParameters`.
|
||||
//! Helpers building `InnerClasses`/`BootstrapMethods`/`MethodParameters`.
|
||||
//!
|
||||
//! Вынесены из `attrs`, чтобы тот держался в лимите 250 строк; логика не
|
||||
//! изменилась — только место. Каждый строит запись backend'а из элемента IR,
|
||||
//! интернируя имена/дескрипторы и резолвя индексы `CONSTANT_Class` через пул.
|
||||
//! Moved out of `attrs` so it stays within the 250-line limit; the logic is
|
||||
//! unchanged — only the location. Each builds a backend entry from an IR
|
||||
//! element, interning names/descriptors and resolving `CONSTANT_Class` indices
|
||||
//! through the pool.
|
||||
|
||||
use ristretto_classfile::MethodAccessFlags;
|
||||
use ristretto_classfile::attributes::{
|
||||
|
|
@ -50,7 +51,7 @@ pub(super) fn bootstrap(
|
|||
})
|
||||
}
|
||||
|
||||
/// Аргумент bootstrap-метода: те же константы, что в `ldc`, плюс `Dynamic`.
|
||||
/// A bootstrap-method argument: the same constants as in `ldc`, plus `Dynamic`.
|
||||
fn bootstrap_arg(arg: &Const, pool: &mut Pool) -> Result<u16, ClassfileError> {
|
||||
match arg {
|
||||
Const::Dynamic {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Наш IR → байты `.class` через backend `ristretto_classfile`.
|
||||
//! Our IR → `.class` bytes through the `ristretto_classfile` backend.
|
||||
//!
|
||||
//! Пул собирается с нуля детерминированным обходом; backend сам пересчитывает
|
||||
//! номера инструкций в байтовые смещения и проверяет результат (`verify`).
|
||||
//! The pool is built from scratch by a deterministic walk; the backend recomputes
|
||||
//! instruction numbers into byte offsets and verifies the result (`verify`).
|
||||
|
||||
mod attrs;
|
||||
pub(crate) mod code;
|
||||
|
|
@ -16,7 +16,7 @@ use ristretto_classfile::{
|
|||
use crate::error::ClassfileError;
|
||||
use crate::model::{ClassFile, Field, Method};
|
||||
|
||||
/// Класс нашего IR → байты `.class`.
|
||||
/// Our IR class → `.class` bytes.
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
//! Сборка constant pool при записи: строки нашего IR → индексы backend'а.
|
||||
//! Building the constant pool on write: our IR strings → backend indices.
|
||||
//!
|
||||
//! Пул строится с нуля детерминированным обходом (классы, поля, методы,
|
||||
//! атрибуты, код), поэтому одинаковый IR даёт одинаковые байты. Индексы
|
||||
//! `Raw`-атрибутов при этом не переносятся — см. `super::attrs`.
|
||||
//! The pool is built from scratch by a deterministic walk (classes, fields,
|
||||
//! methods, attributes, code), so identical IR yields identical bytes. `Raw`
|
||||
//! attribute indices are not carried over — see `super::attrs`.
|
||||
//!
|
||||
//! Backend не дедуплицирует: `add_*` всегда толкает новый элемент. Кэшируем
|
||||
//! семантические ключи сами — иначе `LocalVariableTable` и
|
||||
//! `LocalVariableTypeTable` получат разные `name_index` для одного имени, и
|
||||
//! HotSpot отвергнет класс (`LVTT entry ... does not match any LVT entry`).
|
||||
//! Порядок обхода детерминирован, поэтому кэш меняет только компактность пула.
|
||||
//! Референсы (поле/метод/хендл/динамик) — в [`refs`].
|
||||
//! The backend does not deduplicate: `add_*` always pushes a new entry. We cache
|
||||
//! semantic keys ourselves — otherwise `LocalVariableTable` and
|
||||
//! `LocalVariableTypeTable` would get different `name_index` values for the same
|
||||
//! name, and HotSpot would reject the class (`LVTT entry ... does not match any
|
||||
//! LVT entry`). The walk order is deterministic, so the cache only changes pool
|
||||
//! compactness. References (field/method/handle/dynamic) live in [`refs`].
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ use crate::insn::Const;
|
|||
|
||||
mod refs;
|
||||
|
||||
/// Строитель пула; живёт только внутри одного `write`.
|
||||
/// The pool builder; lives only within a single `write`.
|
||||
pub(crate) struct Pool {
|
||||
pub(super) pool: ConstantPool<'static>,
|
||||
pub(super) utf8s: HashMap<String, u16>,
|
||||
|
|
@ -68,8 +68,8 @@ impl Pool {
|
|||
if let Some(&index) = self.name_types.get(&key) {
|
||||
return Ok(index);
|
||||
}
|
||||
// Собираем из примитивов, а не `add_name_and_type`: последний толкает
|
||||
// собственные `Utf8`-строки мимо нашего кэша (дубли в пуле).
|
||||
// Build from primitives, not `add_name_and_type`: the latter pushes its
|
||||
// own `Utf8` strings past our cache (pool duplicates).
|
||||
let name_index = self.utf8(name)?;
|
||||
let descriptor_index = self.utf8(descriptor)?;
|
||||
let index = self
|
||||
|
|
@ -96,7 +96,7 @@ impl Pool {
|
|||
Ok(index)
|
||||
}
|
||||
|
||||
/// Константа для `ldc`/`ldc2_w`; `dynamic` запрещён (только через bootstrap).
|
||||
/// A constant for `ldc`/`ldc2_w`; `dynamic` is forbidden (bootstrap only).
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
//! Референсы constant pool: `Fieldref`/`Methodref`/`MethodHandle`/`Dynamic`.
|
||||
//! Constant-pool references: `Fieldref`/`Methodref`/`MethodHandle`/`Dynamic`.
|
||||
//!
|
||||
//! Это `impl Pool` из [`super`]; вынесено отдельно, чтобы материнский файл
|
||||
//! держался в лимите структуры. Каждый билдер мемоизирован по семантическому
|
||||
//! ключу (owner/name/descriptor/…) — одинаковая ссылка всегда получает один
|
||||
//! индекс, а пул не раздувается дублями (раздувание иначе расширяет `ldc`
|
||||
//! в `ldc_w` и меняет длину кода при переносе).
|
||||
//! This is an `impl Pool` from [`super`], split out so the parent file stays
|
||||
//! within the structure limit. Each builder is memoized by a semantic key
|
||||
//! (owner/name/descriptor/…) — an identical reference always gets the same index,
|
||||
//! and the pool doesn't bloat with duplicates (bloat would otherwise widen `ldc`
|
||||
//! into `ldc_w` and change code length on relocation).
|
||||
|
||||
use ristretto_classfile::{Constant, ReferenceKind};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Типы операндов инструкций: константы, примитивы, условия, вызовы.
|
||||
//! Instruction operand types: constants, primitives, conditions, calls.
|
||||
|
||||
/// Идентификатор метки внутри одного метода.
|
||||
/// A label identifier within a single method.
|
||||
pub type Label = u32;
|
||||
|
||||
/// Константа для `ldc`/`ldc_w`/`ldc2_w`.
|
||||
/// A constant for `ldc`/`ldc_w`/`ldc2_w`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Const {
|
||||
Int(i32),
|
||||
|
|
@ -11,7 +11,7 @@ pub enum Const {
|
|||
Float(f32),
|
||||
Double(f64),
|
||||
String(String),
|
||||
/// Внутреннее имя класса (`java/lang/String`).
|
||||
/// The class's internal name (`java/lang/String`).
|
||||
Class(String),
|
||||
MethodType(String),
|
||||
MethodHandle {
|
||||
|
|
@ -21,7 +21,7 @@ pub enum Const {
|
|||
descriptor: String,
|
||||
itf: bool,
|
||||
},
|
||||
/// `CONSTANT_Dynamic`: имя, дескриптор, индекс bootstrap-метода.
|
||||
/// `CONSTANT_Dynamic`: name, descriptor, bootstrap-method index.
|
||||
Dynamic {
|
||||
name: String,
|
||||
descriptor: String,
|
||||
|
|
@ -29,7 +29,7 @@ pub enum Const {
|
|||
},
|
||||
}
|
||||
|
||||
/// Размер операнда/результата арифметики.
|
||||
/// Operand/result size of an arithmetic operation.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Prim {
|
||||
Int,
|
||||
|
|
@ -38,7 +38,7 @@ pub enum Prim {
|
|||
Double,
|
||||
}
|
||||
|
||||
/// Арифметическая/битовая операция; тип берётся из [`Prim`].
|
||||
/// An arithmetic/bitwise operation; the type comes from [`Prim`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Arith {
|
||||
Add,
|
||||
|
|
@ -55,7 +55,7 @@ pub enum Arith {
|
|||
Ushr,
|
||||
}
|
||||
|
||||
/// Условие перехода.
|
||||
/// A branch condition.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Cond {
|
||||
Eq,
|
||||
|
|
@ -66,10 +66,10 @@ pub enum Cond {
|
|||
Ge,
|
||||
}
|
||||
|
||||
/// Вид условного перехода (соответствует семействам `if*`).
|
||||
/// Kind of conditional branch (matches the `if*` families).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BranchCond {
|
||||
/// `if<cond>` относительно нуля.
|
||||
/// `if<cond>` against zero.
|
||||
Zero(Cond),
|
||||
/// `if_icmp<cond>`.
|
||||
Int(Cond),
|
||||
|
|
@ -79,7 +79,7 @@ pub enum BranchCond {
|
|||
Null { is_null: bool },
|
||||
}
|
||||
|
||||
/// Вид вызова.
|
||||
/// Kind of call.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum InvokeKind {
|
||||
Virtual,
|
||||
|
|
@ -88,7 +88,7 @@ pub enum InvokeKind {
|
|||
Interface,
|
||||
}
|
||||
|
||||
/// Конверсия типов (`i2l`, `f2b`, ... — JVMS §6.5.i2<b|c|s|l|f|d>).
|
||||
/// Type conversion (`i2l`, `f2b`, ... — JVMS §6.5.i2<b|c|s|l|f|d>).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Convert {
|
||||
I2L,
|
||||
|
|
@ -108,7 +108,7 @@ pub enum Convert {
|
|||
D2F,
|
||||
}
|
||||
|
||||
/// Тип элемента массива для `newarray`/`*aload`/`*astore`.
|
||||
/// Array element type for `newarray`/`*aload`/`*astore`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ArrayKind {
|
||||
Boolean,
|
||||
|
|
@ -122,10 +122,10 @@ pub enum ArrayKind {
|
|||
Reference,
|
||||
}
|
||||
|
||||
/// Тип слота локальной переменной для `*load*`/`*store*`.
|
||||
/// Local-variable slot type for `*load*`/`*store*`.
|
||||
///
|
||||
/// Хранится явно: выбор опкода (`iload` vs `aload`) — подсказка верификатору,
|
||||
/// потерять её при round-trip нельзя.
|
||||
/// Stored explicitly: the opcode choice (`iload` vs `aload`) is a hint to the
|
||||
/// verifier, and losing it on a round-trip is not acceptable.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SlotKind {
|
||||
Int,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//! Инструкции IR: смещения отсутствуют, ветвления адресуют [`Label`].
|
||||
//! IR instructions: no offsets, branches address a [`Label`].
|
||||
//!
|
||||
//! Это принципиально: любой пасс, меняющий порядок/длину инструкций, не обязан
|
||||
//! пересчитывать jump-офсеты — это делает writer один раз в конце пайплайна.
|
||||
//! This is fundamental: any pass that changes instruction order/length need not
|
||||
//! recompute jump offsets — the writer does that once at the end of the pipeline.
|
||||
|
||||
mod kinds;
|
||||
|
||||
|
|
@ -9,32 +9,32 @@ pub use kinds::{
|
|||
Arith, ArrayKind, BranchCond, Cond, Const, Convert, InvokeKind, Label, Prim, SlotKind,
|
||||
};
|
||||
|
||||
/// Мнемоника одной инструкции.
|
||||
/// Mnemonic of a single instruction.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Insn {
|
||||
/// Маркер метки. Ветвления указывают на [`Label`], writer вычисляет смещения.
|
||||
/// Label marker. Branches point at a [`Label`]; the writer computes offsets.
|
||||
Mark(Label),
|
||||
Nop,
|
||||
AconstNull,
|
||||
Const(Const),
|
||||
/// Загрузка локальной переменной (`iload`, `aload`, ...).
|
||||
/// Load a local variable (`iload`, `aload`, ...).
|
||||
Load {
|
||||
slot: u16,
|
||||
kind: SlotKind,
|
||||
},
|
||||
/// Сохранение локальной переменной.
|
||||
/// Store a local variable.
|
||||
Store {
|
||||
slot: u16,
|
||||
kind: SlotKind,
|
||||
},
|
||||
/// `iinc` — увеличение локальной переменной на константу.
|
||||
/// `iinc` — increase a local variable by a constant.
|
||||
Iinc {
|
||||
index: u16,
|
||||
delta: i32,
|
||||
},
|
||||
/// Загрузка элемента массива.
|
||||
/// Load an array element.
|
||||
ArrayLoad(ArrayKind),
|
||||
/// Сохранение элемента массива.
|
||||
/// Store an array element.
|
||||
ArrayStore(ArrayKind),
|
||||
Pop,
|
||||
Pop2,
|
||||
|
|
@ -45,23 +45,23 @@ pub enum Insn {
|
|||
Dup2X1,
|
||||
Dup2X2,
|
||||
Swap,
|
||||
/// `iadd`, `lmul`, `lneg`, ... — тип + операция.
|
||||
/// `iadd`, `lmul`, `lneg`, ... — type + operation.
|
||||
Binary {
|
||||
prim: Prim,
|
||||
op: Arith,
|
||||
},
|
||||
/// Сравнение длин (`lcmp`) и плавающих (`fcmpl`/`fcmpg`).
|
||||
/// Comparison of longs (`lcmp`) and floats (`fcmpl`/`fcmpg`).
|
||||
Cmp {
|
||||
prim: Prim,
|
||||
nan_great: bool,
|
||||
},
|
||||
/// Конверсия типов (`i2l`, `d2f`, ...).
|
||||
/// Type conversion (`i2l`, `d2f`, ...).
|
||||
Convert(Convert),
|
||||
Branch(BranchCond, Label),
|
||||
Goto(Label),
|
||||
/// `jsr` — вызов подпрограммы (только классы версии ≤ 50).
|
||||
/// `jsr` — subroutine call (only version ≤ 50 classes).
|
||||
Jsr(Label),
|
||||
/// `ret` — возврат из подпрограммы (`jsr`/`ret` парны).
|
||||
/// `ret` — return from a subroutine (`jsr`/`ret` come in pairs).
|
||||
Ret(u16),
|
||||
TableSwitch {
|
||||
default: Label,
|
||||
|
|
@ -73,9 +73,9 @@ pub enum Insn {
|
|||
default: Label,
|
||||
pairs: Vec<(i32, Label)>,
|
||||
},
|
||||
/// `return`/`ireturn`/...; `None` — пустой возврат.
|
||||
/// `return`/`ireturn`/...; `None` — void return.
|
||||
Return(Option<Prim>),
|
||||
/// `areturn` — возврат ссылки (у [`Prim`] нет ссылочного типа).
|
||||
/// `areturn` — return a reference ([`Prim`] has no reference type).
|
||||
AReturn,
|
||||
GetStatic {
|
||||
owner: String,
|
||||
|
|
@ -112,7 +112,7 @@ pub enum Insn {
|
|||
New(String),
|
||||
NewArray(ArrayKind),
|
||||
ANewArray(String),
|
||||
/// `multianewarray` — массив любого измерения; `dims` — сколько измерений со стека.
|
||||
/// `multianewarray` — array of any dimension; `dims` — how many dimensions from the stack.
|
||||
MultiANewArray {
|
||||
descriptor: String,
|
||||
dims: u8,
|
||||
|
|
@ -126,7 +126,7 @@ pub enum Insn {
|
|||
}
|
||||
|
||||
impl Insn {
|
||||
/// Метки, на которые ссылается инструкция (для построения CFG).
|
||||
/// Labels the instruction references (for building the CFG).
|
||||
pub fn targets(&self) -> Vec<Label> {
|
||||
match self {
|
||||
Insn::Branch(_, label) | Insn::Goto(label) | Insn::Jsr(label) => vec![*label],
|
||||
|
|
@ -154,7 +154,7 @@ impl Insn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Незакодированный переход (для вставки opaque-предикатов и перестройки CFG).
|
||||
/// An encoded jump target (for inserting opaque predicates and rebuilding the CFG).
|
||||
pub fn branch_target_mut(&mut self) -> Option<&mut Label> {
|
||||
match self {
|
||||
Insn::Branch(_, label) | Insn::Goto(label) => Some(label),
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
//! # noxis-classfile
|
||||
//!
|
||||
//! Собственный IR для классовых файлов JVM: разбор, представление, запись.
|
||||
//! Own IR for JVM class files: parsing, representation, writing.
|
||||
//!
|
||||
//! Почему свой IR, а не работа напрямую с крейтом-парсером: пассам нужна
|
||||
//! стабильная модель (метки вместо смещений, осознанный набор атрибутов),
|
||||
//! а не API третьей стороны, который меняется между версиями.
|
||||
//! Why a custom IR instead of working directly with a parser crate: passes
|
||||
//! need a stable model (labels instead of offsets, a deliberate attribute
|
||||
//! set), not a third-party API that changes between versions.
|
||||
//!
|
||||
//! Статус: модель и билдер готовы, бинарный parse/write — этап P1
|
||||
//! (см. `docs/stack.md`).
|
||||
//! Status: model and builder are ready; binary parse/write is stage P1
|
||||
//! (see `docs/stack.md`).
|
||||
|
||||
pub mod error;
|
||||
pub mod format;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Сборка классов «с нуля»: junk-классы, рантайм-декодеры, аннотации-шум.
|
||||
//! Building classes "from scratch": junk classes, runtime decoders, noise annotations.
|
||||
|
||||
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version};
|
||||
|
||||
/// Строитель класса без бинарного представления на входе.
|
||||
/// A class builder with no binary representation on input.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClassBuilder {
|
||||
version: Version,
|
||||
|
|
@ -16,8 +16,8 @@ pub struct ClassBuilder {
|
|||
}
|
||||
|
||||
impl ClassBuilder {
|
||||
/// Новый класс `public` с указанным суперклассом (`None` = `java/lang/Object`,
|
||||
/// кроме самого `java/lang/Object` — у него суперкласса нет).
|
||||
/// A new `public` class with the given superclass (`None` = `java/lang/Object`,
|
||||
/// except for `java/lang/Object` itself — it has no superclass).
|
||||
pub fn new(this_class: impl Into<String>, super_class: Option<&str>) -> Self {
|
||||
let this_class = this_class.into();
|
||||
let super_class = match super_class {
|
||||
|
|
@ -37,25 +37,25 @@ impl ClassBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Задать версию классового файла.
|
||||
/// Set the class-file version.
|
||||
pub fn version(mut self, version: Version) -> Self {
|
||||
self.version = version;
|
||||
self
|
||||
}
|
||||
|
||||
/// Задать флаги доступа (перезаписывает `ACC_PUBLIC|ACC_SUPER`).
|
||||
/// Set access flags (overwrites `ACC_PUBLIC|ACC_SUPER`).
|
||||
pub fn access_flags(mut self, flags: u16) -> Self {
|
||||
self.access_flags = flags;
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить интерфейс.
|
||||
/// Add an interface.
|
||||
pub fn interface(mut self, name: impl Into<String>) -> Self {
|
||||
self.interfaces.push(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Добавить поле.
|
||||
/// Add a field.
|
||||
pub fn field(mut self, name: impl Into<String>, descriptor: impl Into<String>) -> Self {
|
||||
self.fields.push(Field {
|
||||
access_flags: 0x0001, // ACC_PUBLIC
|
||||
|
|
@ -66,7 +66,7 @@ impl ClassBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Добавить метод без тела (`abstract`/`native` должны выставить флаги сами).
|
||||
/// Add a method without a body (`abstract`/`native` must set their own flags).
|
||||
pub fn method(
|
||||
mut self,
|
||||
access_flags: u16,
|
||||
|
|
@ -83,7 +83,7 @@ impl ClassBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Добавить метод с телом.
|
||||
/// Add a method with a body.
|
||||
pub fn method_with_code(
|
||||
mut self,
|
||||
access_flags: u16,
|
||||
|
|
@ -101,13 +101,13 @@ impl ClassBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Добавить атрибут уровня класса.
|
||||
/// Add a class-level attribute.
|
||||
pub fn attribute(mut self, attribute: Attribute) -> Self {
|
||||
self.attributes.push(attribute);
|
||||
self
|
||||
}
|
||||
|
||||
/// Собрать класс.
|
||||
/// Build the class.
|
||||
pub fn build(self) -> ClassFile {
|
||||
ClassFile {
|
||||
version: self.version,
|
||||
|
|
@ -122,7 +122,7 @@ impl ClassBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Пустое тело метода (`return`), для junk-методов.
|
||||
/// An empty method body (`return`), for junk methods.
|
||||
pub fn empty_code(max_locals: u16) -> Code {
|
||||
Code {
|
||||
max_stack: 0,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Структура «программы» — единица работы пайплайна.
|
||||
//! The "program" structure — the unit of work for the pipeline.
|
||||
|
||||
pub mod builder;
|
||||
pub mod version;
|
||||
|
|
@ -8,7 +8,7 @@ pub use version::Version;
|
|||
use crate::format::StackMapFrame;
|
||||
use crate::insn::{Const, Insn, Label};
|
||||
|
||||
/// Неизвестные/специфичные атрибуты хранятся байтами — round-trip без потерь.
|
||||
/// Unknown/specific attributes are stored as bytes — lossless round-trip.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Attribute {
|
||||
SourceFile(String),
|
||||
|
|
@ -19,26 +19,26 @@ pub enum Attribute {
|
|||
Deprecated,
|
||||
Synthetic,
|
||||
InnerClasses(Vec<InnerClass>),
|
||||
/// `NestHost`: класс, служащий nest-хостом (JVMS §4.7.28).
|
||||
/// `NestHost`: the class acting as nest host (JVMS §4.7.28).
|
||||
NestHost(String),
|
||||
/// `NestMembers`: классы-члены этого nest-хоста (JVMS §4.7.29).
|
||||
/// `NestMembers`: the member classes of this nest host (JVMS §4.7.29).
|
||||
NestMembers(Vec<String>),
|
||||
/// `ConstantValue` поля (`static final`): значение из пула.
|
||||
/// A field's `ConstantValue` (`static final`): a value from the pool.
|
||||
ConstantValue(Const),
|
||||
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы.
|
||||
/// `BootstrapMethods` for `invokedynamic` (lambdas): method + arguments.
|
||||
BootstrapMethods(Vec<BootstrapMethod>),
|
||||
/// `EnclosingMethod`: класс и опциональный метод, содержащие локальный класс.
|
||||
/// `EnclosingMethod`: the class and optional method enclosing a local class.
|
||||
EnclosingMethod {
|
||||
class: String,
|
||||
method: Option<(String, String)>,
|
||||
},
|
||||
/// `MethodParameters`: имена формальных параметров (`None` — безымянный).
|
||||
/// `MethodParameters`: formal parameter names (`None` — unnamed).
|
||||
MethodParameters(Vec<MethodParameter>),
|
||||
/// `Record`: компоненты record-класса.
|
||||
/// `Record`: the record class's components.
|
||||
Record(Vec<RecordComponent>),
|
||||
/// `LocalVariableTypeTable`: сигнатуры generic-локальных (как LVT).
|
||||
/// `LocalVariableTypeTable`: generic local signatures (like LVT).
|
||||
LocalVariableTypeTable(Vec<LocalVariableType>),
|
||||
/// Всё остальное: имя + сырые байты `info` (осторожно с индексами пула).
|
||||
/// Everything else: name + raw `info` bytes (careful with pool indices).
|
||||
Raw {
|
||||
name: String,
|
||||
data: Vec<u8>,
|
||||
|
|
@ -47,14 +47,14 @@ pub enum Attribute {
|
|||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct LineNumber {
|
||||
/// Метка начала (байты вычисляет writer — переживает вставки пассов).
|
||||
/// Start label (the writer computes bytes — survives pass insertions).
|
||||
pub start: Label,
|
||||
pub line: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LocalVariable {
|
||||
/// Метки диапазона видимости (writer пересчитает в байты).
|
||||
/// Visibility-range labels (the writer recomputes them into bytes).
|
||||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub name: String,
|
||||
|
|
@ -70,10 +70,10 @@ pub struct InnerClass {
|
|||
pub access_flags: u16,
|
||||
}
|
||||
|
||||
/// Один `bootstrap`-метод: ссылка вида `MethodHandle` + аргументы-константы.
|
||||
/// One `bootstrap` method: a `MethodHandle`-style reference + const arguments.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct BootstrapMethod {
|
||||
/// Номер `reference_kind` (JVMS §5.4.3.5).
|
||||
/// The `reference_kind` number (JVMS §5.4.3.5).
|
||||
pub kind: u8,
|
||||
pub owner: String,
|
||||
pub name: String,
|
||||
|
|
@ -82,14 +82,14 @@ pub struct BootstrapMethod {
|
|||
pub args: Vec<Const>,
|
||||
}
|
||||
|
||||
/// Формальный параметр метода (`MethodParameters`).
|
||||
/// A method's formal parameter (`MethodParameters`).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MethodParameter {
|
||||
pub name: Option<String>,
|
||||
pub access_flags: u16,
|
||||
}
|
||||
|
||||
/// Компонент `Record`: поле record-класса с атрибутами.
|
||||
/// A `Record` component: a record class field with attributes.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RecordComponent {
|
||||
pub name: String,
|
||||
|
|
@ -97,10 +97,10 @@ pub struct RecordComponent {
|
|||
pub attributes: Vec<Attribute>,
|
||||
}
|
||||
|
||||
/// Строка `LocalVariableTypeTable` (как `LocalVariable`, но с сигнатурой).
|
||||
/// A `LocalVariableTypeTable` entry (like `LocalVariable`, but with a signature).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LocalVariableType {
|
||||
/// Метки диапазона видимости (writer пересчитает в байты).
|
||||
/// Visibility-range labels (the writer recomputes them into bytes).
|
||||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub name: String,
|
||||
|
|
@ -126,18 +126,18 @@ pub struct Method {
|
|||
}
|
||||
|
||||
impl Method {
|
||||
/// `native`/`abstract` тела не имеют.
|
||||
/// `native`/`abstract` methods have no body.
|
||||
pub fn has_code(&self) -> bool {
|
||||
self.code.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
/// Тело метода.
|
||||
/// A method body.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Code {
|
||||
pub max_stack: u16,
|
||||
pub max_locals: u16,
|
||||
/// Маркеры меток + инструкции; ветвления указывают на [`Label`].
|
||||
/// Label markers + instructions; branches point at a [`Label`].
|
||||
pub insns: Vec<Insn>,
|
||||
pub exception_table: Vec<ExceptionHandler>,
|
||||
pub frames: Vec<StackMapFrame>,
|
||||
|
|
@ -145,7 +145,7 @@ pub struct Code {
|
|||
}
|
||||
|
||||
impl Code {
|
||||
/// Все метки, объявленные в теле метода.
|
||||
/// All labels declared in the method body.
|
||||
pub fn labels(&self) -> Vec<Label> {
|
||||
self.insns
|
||||
.iter()
|
||||
|
|
@ -163,11 +163,11 @@ pub struct ExceptionHandler {
|
|||
pub start: Label,
|
||||
pub end: Label,
|
||||
pub handler: Label,
|
||||
/// Внутреннее имя класса или `None` для `any`.
|
||||
/// The class's internal name, or `None` for `any`.
|
||||
pub catch_type: Option<String>,
|
||||
}
|
||||
|
||||
/// Один `.class` в нормализованном виде.
|
||||
/// One `.class` in normalized form.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ClassFile {
|
||||
pub version: Version,
|
||||
|
|
@ -181,34 +181,34 @@ pub struct ClassFile {
|
|||
}
|
||||
|
||||
impl ClassFile {
|
||||
/// Ищет метод по имени и дескриптору.
|
||||
/// Finds a method by name and descriptor.
|
||||
pub fn method(&self, name: &str, descriptor: &str) -> Option<&Method> {
|
||||
self.methods
|
||||
.iter()
|
||||
.find(|m| m.name == name && m.descriptor == descriptor)
|
||||
}
|
||||
|
||||
/// Ищет метод по имени и дескриптору (мутирующий доступ).
|
||||
/// Finds a method by name and descriptor (mutating access).
|
||||
pub fn method_mut(&mut self, name: &str, descriptor: &str) -> Option<&mut Method> {
|
||||
self.methods
|
||||
.iter_mut()
|
||||
.find(|m| m.name == name && m.descriptor == descriptor)
|
||||
}
|
||||
|
||||
/// Признак `ACC_INTERFACE`.
|
||||
/// The `ACC_INTERFACE` flag.
|
||||
pub fn is_interface(&self) -> bool {
|
||||
self.access_flags & 0x0200 != 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Ресурс внутри архива (JSON, properties, картинки, ...).
|
||||
/// A resource inside the archive (JSON, properties, images, ...).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Resource {
|
||||
pub path: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Вся входная программа: классы + ресурсы.
|
||||
/// The whole input program: classes + resources.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct Program {
|
||||
pub classes: Vec<ClassFile>,
|
||||
|
|
@ -216,24 +216,24 @@ pub struct Program {
|
|||
}
|
||||
|
||||
impl Program {
|
||||
/// Класс по внутреннему имени (`pkg/Class`).
|
||||
/// A class by internal name (`pkg/Class`).
|
||||
pub fn class(&self, internal_name: &str) -> Option<&ClassFile> {
|
||||
self.classes.iter().find(|c| c.this_class == internal_name)
|
||||
}
|
||||
|
||||
/// Класс по внутреннему имени (мутирующий доступ).
|
||||
/// A class by internal name (mutating access).
|
||||
pub fn class_mut(&mut self, internal_name: &str) -> Option<&mut ClassFile> {
|
||||
self.classes
|
||||
.iter_mut()
|
||||
.find(|c| c.this_class == internal_name)
|
||||
}
|
||||
|
||||
/// Ресурс по пути внутри архива.
|
||||
/// A resource by its path inside the archive.
|
||||
pub fn resource(&self, path: &str) -> Option<&Resource> {
|
||||
self.resources.iter().find(|r| r.path == path)
|
||||
}
|
||||
|
||||
/// Пустая ли программа.
|
||||
/// Whether the program is empty.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.classes.is_empty() && self.resources.is_empty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//! Версия классового файла (major/minor).
|
||||
//! Class-file version (major/minor).
|
||||
|
||||
/// Версия классового файла.
|
||||
/// The class-file version.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Version {
|
||||
pub minor: u16,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue