diff --git a/crates/noxis-classfile/src/error.rs b/crates/noxis-classfile/src/error.rs index 28f9689..9066044 100644 --- a/crates/noxis-classfile/src/error.rs +++ b/crates/noxis-classfile/src/error.rs @@ -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}")] diff --git a/crates/noxis-classfile/src/format/frames/emit.rs b/crates/noxis-classfile/src/format/frames/emit.rs index 3715578..9bb758f 100644 --- a/crates/noxis-classfile/src/format/frames/emit.rs +++ b/crates/noxis-classfile/src/format/frames/emit.rs @@ -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], targets: &BTreeSet, @@ -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 { let mut out = Vec::with_capacity(locals.len()); let mut index = 0; diff --git a/crates/noxis-classfile/src/format/frames/flow.rs b/crates/noxis-classfile/src/format/frames/flow.rs index 7a10222..2c98496 100644 --- a/crates/noxis-classfile/src/format/frames/flow.rs +++ b/crates/noxis-classfile/src/format/frames/flow.rs @@ -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, 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, @@ -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); -/// Handler'ы в номерах инструкций для рёбер исключений. +/// Handlers in instruction numbers, for exception edges. pub(super) fn handler_ranges( table: &[ExceptionHandler], labels: &BTreeMap, diff --git a/crates/noxis-classfile/src/format/frames/merge/descs.rs b/crates/noxis-classfile/src/format/frames/merge/descs.rs index dd9c243..eee664a 100644 --- a/crates/noxis-classfile/src/format/frames/merge/descs.rs +++ b/crates/noxis-classfile/src/format/frames/merge/descs.rs @@ -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, DescError> { let mut chars = descriptor.chars(); match chars.next() { @@ -57,10 +57,10 @@ pub(crate) fn field_slots(descriptor: &str) -> Result, Des } } -/// Слоты параметров и возврата (`None` — `void`). +/// Parameter and return slots (`None` — `void`). type MethodSlots = (Vec>, Option>); -/// Дескриптор метода: слоты параметров и слоты возврата (`None` — `void`). +/// Method descriptor: parameter slots and return slots (`None` — `void`). pub(crate) fn method_slots(descriptor: &str) -> Result { let (params, ret) = descriptor .split_once('(') @@ -128,7 +128,7 @@ pub(crate) fn method_slots(descriptor: &str) -> Result { Ok((out, ret)) } -/// Тип элемента по дескриптору массива в стеке. +/// Element type from an array descriptor on the stack. pub(crate) fn array_element( array: &VerificationType, kind: crate::insn::ArrayKind, diff --git a/crates/noxis-classfile/src/format/frames/merge/mod.rs b/crates/noxis-classfile/src/format/frames/merge/mod.rs index 28c3c20..7da31a0 100644 --- a/crates/noxis-classfile/src/format/frames/merge/mod.rs +++ b/crates/noxis-classfile/src/format/frames/merge/mod.rs @@ -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; } -/// Контекст слияния: имена для ошибок + иерархия для 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, @@ -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 { 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)); diff --git a/crates/noxis-classfile/src/format/frames/mod.rs b/crates/noxis-classfile/src/format/frames/mod.rs index 07eec95..b640065 100644 --- a/crates/noxis-classfile/src/format/frames/mod.rs +++ b/crates/noxis-classfile/src/format/frames/mod.rs @@ -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, @@ -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], @@ -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) -> VerificationType { match catch { Some(name) => VerificationType::Object(name.clone()), diff --git a/crates/noxis-classfile/src/format/mod.rs b/crates/noxis-classfile/src/format/mod.rs index 36826d1..7b692e9 100644 --- a/crates/noxis-classfile/src/format/mod.rs +++ b/crates/noxis-classfile/src/format/mod.rs @@ -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 { parse::class::parse_class(bytes) } -/// Записать [`ClassFile`] в байты, включая пересчёт `Code`/`StackMapTable`. +/// Write a [`ClassFile`] to bytes, including `Code`/`StackMapTable` recompute. pub fn write(class: &ClassFile) -> Result, 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() } diff --git a/crates/noxis-classfile/src/format/parse/attrs.rs b/crates/noxis-classfile/src/format/parse/attrs.rs index 8dc3c94..361e97c 100644 --- a/crates/noxis-classfile/src/format/parse/attrs.rs +++ b/crates/noxis-classfile/src/format/parse/attrs.rs @@ -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 { Ok(match attr { RtAttr::SourceFile { @@ -105,7 +105,7 @@ pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result Result { 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, ClassfileError> { if index == 0 { Ok(None) @@ -203,7 +203,7 @@ fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result, } } -/// Индекс `CONSTANT_Utf8`, где `0` означает отсутствие значения. +/// A `CONSTANT_Utf8` index where `0` means the value is absent. fn optional_utf8(pool: &ConstantPool<'_>, index: u16) -> Result, ClassfileError> { if index == 0 { Ok(None) diff --git a/crates/noxis-classfile/src/format/parse/class.rs b/crates/noxis-classfile/src/format/parse/class.rs index 8a629e7..a9fdc86 100644 --- a/crates/noxis-classfile/src/format/parse/class.rs +++ b/crates/noxis-classfile/src/format/parse/class.rs @@ -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 { 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, ClassfileError> { if index == 0 { Ok(None) diff --git a/crates/noxis-classfile/src/format/parse/code/frames.rs b/crates/noxis-classfile/src/format/parse/code/frames.rs index 09a6ab3..3296d9f 100644 --- a/crates/noxis-classfile/src/format/parse/code/frames.rs +++ b/crates/noxis-classfile/src/format/parse/code/frames.rs @@ -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) -> Result { 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 { Ok(match value { RtType::Top => VerificationType::Top, @@ -99,7 +100,7 @@ fn vtype(value: &RtType, pool: &ConstantPool<'_>) -> Result, index: u16, diff --git a/crates/noxis-classfile/src/format/parse/code/labels.rs b/crates/noxis-classfile/src/format/parse/code/labels.rs index 0352a8b..5048591 100644 --- a/crates/noxis-classfile/src/format/parse/code/labels.rs +++ b/crates/noxis-classfile/src/format/parse/code/labels.rs @@ -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 { let base = i32::from(index); match instruction { @@ -73,7 +73,7 @@ fn once(value: i32) -> std::iter::Once { std::iter::once(value) } -/// Превратить отсортированный набор номеров в последовательные метки. +/// Turn a sorted set of numbers into sequential labels. pub(super) fn assign(indexes: &BTreeSet) -> BTreeMap { indexes .iter() @@ -82,7 +82,7 @@ pub(super) fn assign(indexes: &BTreeSet) -> BTreeMap { .collect() } -/// Метка для номера инструкции. +/// The label for an instruction number. pub(super) fn lookup(labels: &BTreeMap, index: u16) -> Result { labels .get(&u32::from(index)) @@ -90,7 +90,7 @@ pub(super) fn lookup(labels: &BTreeMap, index: u16) -> Result Result { starts .iter() @@ -131,7 +131,7 @@ pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result, 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, starts: &[u16], diff --git a/crates/noxis-classfile/src/format/parse/code/mod.rs b/crates/noxis-classfile/src/format/parse/code/mod.rs index c6c4ebd..ed4efe9 100644 --- a/crates/noxis-classfile/src/format/parse/code/mod.rs +++ b/crates/noxis-classfile/src/format/parse/code/mod.rs @@ -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), ClassfileError> { let mut cursor = Cursor::new(Vec::new()); let mut offsets = Vec::with_capacity(code.len()); diff --git a/crates/noxis-classfile/src/format/parse/insns/basic.rs b/crates/noxis-classfile/src/format/parse/insns/basic.rs index f1f7025..5c19653 100644 --- a/crates/noxis-classfile/src/format/parse/insns/basic.rs +++ b/crates/noxis-classfile/src/format/parse/insns/basic.rs @@ -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<'_>, diff --git a/crates/noxis-classfile/src/format/parse/insns/flow.rs b/crates/noxis-classfile/src/format/parse/insns/flow.rs index cfb3249..6eb8a1a 100644 --- a/crates/noxis-classfile/src/format/parse/insns/flow.rs +++ b/crates/noxis-classfile/src/format/parse/insns/flow.rs @@ -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) -> Result { let absolute = i32::from(index) .checked_add(delta) diff --git a/crates/noxis-classfile/src/format/parse/insns/mod.rs b/crates/noxis-classfile/src/format/parse/insns/mod.rs index 93e9efc..da1c923 100644 --- a/crates/noxis-classfile/src/format/parse/insns/mod.rs +++ b/crates/noxis-classfile/src/format/parse/insns/mod.rs @@ -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 + 'a; -/// Конвертировать одну инструкцию. `index` — её номер в теле метода (нужно `switch`). +/// Convert one instruction. `index` — its number in the method body (`switch` needs it). pub(super) fn convert( instruction: &Instruction, index: u16, diff --git a/crates/noxis-classfile/src/format/parse/insns/refs.rs b/crates/noxis-classfile/src/format/parse/insns/refs.rs index de2dc67..3fb89ed 100644 --- a/crates/noxis-classfile/src/format/parse/insns/refs.rs +++ b/crates/noxis-classfile/src/format/parse/insns/refs.rs @@ -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<'_>, diff --git a/crates/noxis-classfile/src/format/parse/mod.rs b/crates/noxis-classfile/src/format/parse/mod.rs index d103e9b..940481b 100644 --- a/crates/noxis-classfile/src/format/parse/mod.rs +++ b/crates/noxis-classfile/src/format/parse/mod.rs @@ -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; diff --git a/crates/noxis-classfile/src/format/parse/pool.rs b/crates/noxis-classfile/src/format/parse/pool.rs index dd8c538..8908611 100644 --- a/crates/noxis-classfile/src/format/parse/pool.rs +++ b/crates/noxis-classfile/src/format/parse/pool.rs @@ -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 { let name = pool .try_get_class(index) @@ -13,7 +13,7 @@ pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result, 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 { let value = pool .try_get_utf8(index) @@ -66,7 +66,7 @@ pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result, index: u16) -> Result { let constant = pool .try_get(index) @@ -111,7 +111,7 @@ pub(super) fn const_from(pool: &ConstantPool<'_>, index: u16) -> Result, 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, diff --git a/crates/noxis-classfile/src/format/write/attrs.rs b/crates/noxis-classfile/src/format/write/attrs.rs index aa45119..865265d 100644 --- a/crates/noxis-classfile/src/format/write/attrs.rs +++ b/crates/noxis-classfile/src/format/write/attrs.rs @@ -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, pub byte_of: &'a dyn Fn(u32) -> Result, } -/// `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::>()?, }) } -/// Та же таблица для типов (`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 { Ok(match attr { Attribute::SourceFile(name) => RtAttr::SourceFile { diff --git a/crates/noxis-classfile/src/format/write/code/frames.rs b/crates/noxis-classfile/src/format/write/code/frames.rs index 20f5c7a..6371da0 100644 --- a/crates/noxis-classfile/src/format/write/code/frames.rs +++ b/crates/noxis-classfile/src/format/write/code/frames.rs @@ -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 + '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, } diff --git a/crates/noxis-classfile/src/format/write/code/insns.rs b/crates/noxis-classfile/src/format/write/code/insns.rs index 031682a..094f0af 100644 --- a/crates/noxis-classfile/src/format/write/code/insns.rs +++ b/crates/noxis-classfile/src/format/write/code/insns.rs @@ -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)) } diff --git a/crates/noxis-classfile/src/format/write/code/mod.rs b/crates/noxis-classfile/src/format/write/code/mod.rs index d621396..7bfcdcd 100644 --- a/crates/noxis-classfile/src/format/write/code/mod.rs +++ b/crates/noxis-classfile/src/format/write/code/mod.rs @@ -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 { let (labels, bare) = split_marks(&code.insns)?; let resolve = |label: Label| -> Result { @@ -59,7 +59,7 @@ pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result 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 Result<(BTreeMap, 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, 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, ClassfileError> { let mut map = BTreeMap::new(); let mut cursor = Cursor::new(Vec::new()); @@ -132,7 +132,7 @@ pub(crate) fn measure(insns: &[RtInsn]) -> Result, 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 { let target = fit_index(target)?; Ok(match cond { @@ -211,13 +211,13 @@ fn ret_op(prim: Option) -> RtInsn { } } -/// Номер инструкции обязан влезать в `u16` (метод < 64 КБ кода). +/// The instruction number must fit in `u16` (method < 64 KB of code). fn fit_index(index: u32) -> Result { 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 { let (index, target) = (to_i32(index)?, to_i32(target)?); target diff --git a/crates/noxis-classfile/src/format/write/code/ops.rs b/crates/noxis-classfile/src/format/write/code/ops.rs index ef57be7..c89a6e4 100644 --- a/crates/noxis-classfile/src/format/write/code/ops.rs +++ b/crates/noxis-classfile/src/format/write/code/ops.rs @@ -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 { 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 { let params = descriptor .split_once('(') @@ -229,7 +229,7 @@ fn arg_count(descriptor: &str) -> Result { .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 { diff --git a/crates/noxis-classfile/src/format/write/helpers.rs b/crates/noxis-classfile/src/format/write/helpers.rs index f314cf1..5bac30f 100644 --- a/crates/noxis-classfile/src/format/write/helpers.rs +++ b/crates/noxis-classfile/src/format/write/helpers.rs @@ -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 { match arg { Const::Dynamic { diff --git a/crates/noxis-classfile/src/format/write/mod.rs b/crates/noxis-classfile/src/format/write/mod.rs index 3561059..a9ef3fa 100644 --- a/crates/noxis-classfile/src/format/write/mod.rs +++ b/crates/noxis-classfile/src/format/write/mod.rs @@ -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, ClassfileError> { let mut pool = pool::Pool::new(); let version = RtVersion::from(class.version.major, class.version.minor) diff --git a/crates/noxis-classfile/src/format/write/pool/mod.rs b/crates/noxis-classfile/src/format/write/pool/mod.rs index 27cccc1..07d99c7 100644 --- a/crates/noxis-classfile/src/format/write/pool/mod.rs +++ b/crates/noxis-classfile/src/format/write/pool/mod.rs @@ -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, @@ -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 { match value { Const::Int(v) => self.pool.add_integer(*v).map_err(write_err), diff --git a/crates/noxis-classfile/src/format/write/pool/refs.rs b/crates/noxis-classfile/src/format/write/pool/refs.rs index 67e6e02..5e5adaa 100644 --- a/crates/noxis-classfile/src/format/write/pool/refs.rs +++ b/crates/noxis-classfile/src/format/write/pool/refs.rs @@ -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}; diff --git a/crates/noxis-classfile/src/insn/kinds.rs b/crates/noxis-classfile/src/insn/kinds.rs index dc340b5..851e7a0 100644 --- a/crates/noxis-classfile/src/insn/kinds.rs +++ b/crates/noxis-classfile/src/insn/kinds.rs @@ -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` относительно нуля. + /// `if` against zero. Zero(Cond), /// `if_icmp`. 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). +/// Type conversion (`i2l`, `f2b`, ... — JVMS §6.5.i2). #[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, diff --git a/crates/noxis-classfile/src/insn/mod.rs b/crates/noxis-classfile/src/insn/mod.rs index 0a4facc..ab0543f 100644 --- a/crates/noxis-classfile/src/insn/mod.rs +++ b/crates/noxis-classfile/src/insn/mod.rs @@ -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), - /// `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