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:
Loki 2026-10-09 10:31:02 +02:00
parent ec28ec82e3
commit 39178420e4
Signed by: boba
GPG key ID: 253067914055423B
42 changed files with 532 additions and 437 deletions

View file

@ -1,6 +1,6 @@
//! Ошибки разбора/записи классовых файлов. //! Errors from class-file parsing/writing.
/// Ошибка на уровне одного классового файла. /// An error at the level of a single class file.
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ClassfileError { pub enum ClassfileError {
#[error("не удалось разобрать классовый файл: {0}")] #[error("не удалось разобрать классовый файл: {0}")]

View file

@ -1,8 +1,9 @@
//! Эмиссия компактных кадров в точках, где их ждёт верификатор. //! Emitting compact frames at the points where the verifier expects them.
//! //!
//! Вид кадра выбирается сравнением с предыдущим кадром (`Same`/`Append`/ //! A frame's kind is chosen by comparing with the previous frame
//! `Chop`/`SameLocals`, иначе `Full`) — как у `javac`. Хвостовые `Top` //! (`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; use std::collections::BTreeSet;
@ -11,10 +12,10 @@ use super::xfer::State;
use super::{StackMapFrame, VerificationType}; use super::{StackMapFrame, VerificationType};
use crate::error::ClassfileError; 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( pub(super) fn emit_frames(
states: &[Option<State>], states: &[Option<State>],
targets: &BTreeSet<u32>, targets: &BTreeSet<u32>,
@ -49,8 +50,8 @@ fn select(
previous: &[VerificationType], previous: &[VerificationType],
stack: &[VerificationType], stack: &[VerificationType],
) -> StackMapFrame { ) -> StackMapFrame {
// Сравнение — на гранулярности переменных (`long` — одна запись, // Comparison is at variable granularity (`long` — one entry, like `javac`);
// как у `javac`): иначе половины пар дают `Append{[Top, Long]}`. // otherwise halves would produce `Append{[Top, Long]}`.
let current = vars(locals); let current = vars(locals);
let base = vars(previous); let base = vars(previous);
if current == base { if current == base {
@ -93,10 +94,10 @@ fn select(
} }
} }
/// Слоты → переменные: `[Long, Top]` — одна запись; хвостовые `Top` — прочь. /// Slots → variables: `[Long, Top]` — one entry; trailing `Top`s — gone.
/// ///
/// Одиночные `Top` внутри (дыры) сохраняются: в `Full` это легальный тип, /// Single `Top`s inside (holes) are kept: in `Full` they are a legal type, and we
/// а в `Append` мы их не пускаем (см. `select`). /// don't allow them in `Append` (see `select`).
fn vars(locals: &[VerificationType]) -> Vec<VerificationType> { fn vars(locals: &[VerificationType]) -> Vec<VerificationType> {
let mut out = Vec::with_capacity(locals.len()); let mut out = Vec::with_capacity(locals.len());
let mut index = 0; let mut index = 0;

View file

@ -1,4 +1,4 @@
//! CFG тела метода: метки, рёбра, точки кадров, диапазоны handler'ов. //! A method body's CFG: labels, edges, frame points, handler ranges.
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
@ -7,7 +7,7 @@ use crate::error::ClassfileError;
use crate::insn::{Insn, Label}; use crate::insn::{Insn, Label};
use crate::model::ExceptionHandler; use crate::model::ExceptionHandler;
/// Метки → номера инструкций без маркеров. /// Labels → instruction numbers, without the markers.
pub(super) fn strip_marks<'a>( pub(super) fn strip_marks<'a>(
insns: &'a [Insn], insns: &'a [Insn],
merger: &Merger<'_>, merger: &Merger<'_>,
@ -31,7 +31,7 @@ pub(super) fn strip_marks<'a>(
Ok((labels, bare)) Ok((labels, bare))
} }
/// Рёбра CFG без исключений: продолжение + цели переходов. /// CFG edges without exceptions: fallthrough + branch targets.
pub(super) fn successors( pub(super) fn successors(
insn: &Insn, insn: &Insn,
index: u32, index: u32,
@ -40,8 +40,8 @@ pub(super) fn successors(
merger: &Merger<'_>, merger: &Merger<'_>,
) -> Result<Vec<u32>, ClassfileError> { ) -> Result<Vec<u32>, ClassfileError> {
let mut out = Vec::new(); let mut out = Vec::new();
// Без продолжения: переходы без возврата и выходы из метода. // No fallthrough: jumps that don't return and method exits.
// `Jsr`/`Ret` сюда не доходят (их отвергает `transfer`), но рёбра есть. // `Jsr`/`Ret` never reach here (rejected by `transfer`), but the edges exist.
let terminal = matches!( let terminal = matches!(
insn, insn,
Insn::Goto(..) Insn::Goto(..)
@ -67,7 +67,7 @@ pub(super) fn successors(
Ok(out) Ok(out)
} }
/// Точки, где верификатор требует кадры: цели переходов + входы handler'ов. /// Points where the verifier requires frames: branch targets + handler entries.
pub(super) fn frame_targets( pub(super) fn frame_targets(
bare: &[&Insn], bare: &[&Insn],
labels: &BTreeMap<Label, u32>, labels: &BTreeMap<Label, u32>,
@ -91,12 +91,12 @@ pub(super) fn frame_targets(
Ok(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>); pub(super) type HandlerRange = (u32, u32, u32, Option<String>);
/// Handler'ы в номерах инструкций для рёбер исключений. /// Handlers in instruction numbers, for exception edges.
pub(super) fn handler_ranges( pub(super) fn handler_ranges(
table: &[ExceptionHandler], table: &[ExceptionHandler],
labels: &BTreeMap<Label, u32>, labels: &BTreeMap<Label, u32>,

View file

@ -1,12 +1,12 @@
//! Разбор дескрипторов типов (`(...)V`, `[L...;`). //! Descriptor parsing (`(...)V`, `[L...;`).
use super::super::VerificationType; use super::super::VerificationType;
/// Ошибка разбора дескриптора (контекст добавляет вызывающий код). /// Descriptor parse error (context is added by the caller).
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct DescError(pub String); pub(crate) struct DescError(pub String);
/// Уложить слоты значения в locals с позиции; вернуть следующую позицию. /// Write value slots into `locals` at position; return next position.
pub(crate) fn write_local( pub(crate) fn write_local(
locals: &mut [VerificationType], locals: &mut [VerificationType],
slot: usize, slot: usize,
@ -22,7 +22,7 @@ pub(crate) fn write_local(
Ok(slot + width) Ok(slot + width)
} }
/// Разобрать дескриптор массива на (глубина, элемент без `;`). /// Parse an array descriptor into (depth, element without `;`).
pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> { pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
let dims = descriptor.bytes().take_while(|&b| b == b'[').count(); let dims = descriptor.bytes().take_while(|&b| b == b'[').count();
if dims == 0 { if dims == 0 {
@ -31,7 +31,7 @@ pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
Some((dims, descriptor[dims..].trim_end_matches(';'))) 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> { pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, DescError> {
let mut chars = descriptor.chars(); let mut chars = descriptor.chars();
match chars.next() { 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>>); 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> { pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
let (params, ret) = descriptor let (params, ret) = descriptor
.split_once('(') .split_once('(')
@ -128,7 +128,7 @@ pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
Ok((out, ret)) Ok((out, ret))
} }
/// Тип элемента по дескриптору массива в стеке. /// Element type from an array descriptor on the stack.
pub(crate) fn array_element( pub(crate) fn array_element(
array: &VerificationType, array: &VerificationType,
kind: crate::insn::ArrayKind, kind: crate::insn::ArrayKind,

View file

@ -1,12 +1,12 @@
//! Слияние типов (LUB) и разбор дескрипторов. //! Type merging (LUB) and descriptor parsing.
//! //!
//! Решётка: `Top` — «пусто» (берётся сосед), `Null` впитывается в ссылку, //! The lattice: `Top` is "empty" (the neighbour wins), `Null` is absorbed into a
//! одинаковые — сами в себя, ссылки — наименьший общий предок по иерархии. //! reference, equal types map to themselves, references take the least upper
//! Несочетаемые инициализированные типы (`int` vs ссылка после `astore` //! bound over the hierarchy. Incompatible initialized types (`int` vs a reference
//! в handler'е) дают `Top`, как обрезанные кадры `javac`: `Top` в кадре //! after `astore` in a handler) yield `Top`, like `javac`'s truncated frames: a
//! принимает любое фактическое значение, поэтому это всегда sound. //! `Top` in a frame accepts any actual value, so this is always sound. The
//! Исключение — неинициализированные типы: их слияние означает сломанный //! exception is uninitialized types: merging them means a broken construction
//! поток конструирования, здесь честная ошибка, а не молчаливый `Top`. //! flow — here an honest error, not a silent `Top`.
pub(super) mod descs; pub(super) mod descs;
@ -17,13 +17,13 @@ use crate::model::Method;
use descs::write_local; use descs::write_local;
/// Иерархия классов для LUB: достаточно цепочки суперклассов. /// The class hierarchy for LUB: the superclass chain is enough.
pub trait Hierarchy { pub trait Hierarchy {
/// Прямой суперкласс (`None` — `java/lang/Object` или неизвестный). /// The direct superclass (`None` — `java/lang/Object` or unknown).
fn superclass(&self, class: &str) -> Option<String>; fn superclass(&self, class: &str) -> Option<String>;
} }
/// Контекст слияния: имена для ошибок + иерархия для LCA. /// Merge context: names for errors + hierarchy for LCA.
pub(super) struct Merger<'h> { pub(super) struct Merger<'h> {
pub class: String, pub class: String,
pub method: String, pub method: String,
@ -38,7 +38,7 @@ impl Merger<'_> {
} }
} }
/// Влить входящее состояние; `true` — слот изменился. /// Merge an incoming state; `true` — the slot changed.
pub fn merge_into( pub fn merge_into(
&self, &self,
slot: &mut Option<State>, 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> { pub fn initial(&self, method: &Method, max_locals: u16) -> Result<State, ClassfileError> {
let is_static = method.access_flags & 0x0008 != 0; let is_static = method.access_flags & 0x0008 != 0;
let (params, _) = let (params, _) =
@ -127,19 +127,19 @@ impl Merger<'_> {
Ok(first.clone()) Ok(first.clone())
} }
(Object(a), Object(b)) => Ok(Object(self.common_superclass(a, b))), (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(_), _)
| (_, VerificationType::UninitializedThis | VerificationType::Uninitialized(_)) => { | (_, VerificationType::UninitializedThis | VerificationType::Uninitialized(_)) => {
Err(self.invalid("слияние неинициализированных типов")) Err(self.invalid("слияние неинициализированных типов"))
} }
// Несочетаемые инициализированные (например, `int` из тела `try` // Incompatible initialized (e.g. `int` from the `try` body and
// и `Throwable` из handler'а в одном слоте): `Top`, как у `javac`. // `Throwable` from a handler in one slot): `Top`, like `javac`.
_ => Ok(Top), _ => Ok(Top),
} }
} }
/// Наименьший общий предок: подъём по цепочкам, запасной — `Object`. /// Least common ancestor: climb the chains, fallback — `Object`.
fn common_superclass(&self, first: &str, second: &str) -> String { fn common_superclass(&self, first: &str, second: &str) -> String {
if first == second { if first == second {
return first.to_string(); return first.to_string();
@ -176,7 +176,7 @@ impl Merger<'_> {
"java/lang/Object".to_string() "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 { fn array_lca(&self, first: &str, second: &str) -> String {
let (dims_a, elem_a) = descs::split_array(first).unwrap_or((0, first)); 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)); let (dims_b, elem_b) = descs::split_array(second).unwrap_or((0, second));

View file

@ -1,12 +1,12 @@
//! `StackMapTable`: представление фреймов и вычисление после трансформаций. //! `StackMapTable`: frame representation and computation after transformations.
//! //!
//! Фреймы — самая дорогая часть любого обфускатора на Rust: JVM-верификатор //! Frames are the most expensive part of any Rust obfuscator: the JVM verifier
//! (class version ≥ 50) требует точного `StackMapTable`, иначе класс не грузится. //! (class version ≥ 50) demands an exact `StackMapTable`, or the class won't load.
//! Варианты реализации и их цены — `docs/stack.md`. //! Implementation options and their costs — `docs/stack.md`.
//! //!
//! Метод: CFG по меткам → worklist с типами (`xfer`) → слияние LUB (`merge`) //! Method: CFG over labels → worklist with types (`xfer`) → LUB merge (`merge`)
//! → эмиссия компактных кадров (`emit`). Байтовые смещения берутся тем же //! → emit compact frames (`emit`). Byte offsets come from the same encoding the
//! преобразованием, что использует writer, — иначе кадры разъедутся с байтами. //! writer uses — otherwise frames would drift apart from the bytes.
mod emit; mod emit;
mod flow; mod flow;
@ -27,7 +27,7 @@ pub use merge::Hierarchy;
use merge::Merger; use merge::Merger;
use xfer::{State, transfer}; use xfer::{State, transfer};
/// Тип верификации из JVMS §4.10.1. /// A verification type from JVMS §4.10.1.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum VerificationType { pub enum VerificationType {
Top, Top,
@ -37,14 +37,14 @@ pub enum VerificationType {
Long, Long,
Null, Null,
UninitializedThis, UninitializedThis,
/// Внутреннее имя класса (`java/lang/Object`) или дескриптор массива. /// The class's internal name (`java/lang/Object`) or an array descriptor.
Object(String), Object(String),
/// Байтовое смещение инструкции `new`. /// The byte offset of the `new` instruction.
Uninitialized(u16), Uninitialized(u16),
} }
impl VerificationType { impl VerificationType {
/// Тип, соответствующий примитиву. /// The type matching a primitive.
pub fn of_prim(p: Prim) -> Self { pub fn of_prim(p: Prim) -> Self {
match p { match p {
Prim::Int => VerificationType::Integer, 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)] #[derive(Debug, Clone, PartialEq)]
pub enum StackMapFrame { pub enum StackMapFrame {
Same { Same {
@ -80,10 +80,11 @@ pub enum StackMapFrame {
}, },
} }
/// Вычислить фреймы для тела метода после всех трансформаций. /// Compute the frames for a method body after all transformations.
/// ///
/// Кадры нужны только классам версии ≥ 50; `jsr`/`ret` отвергаются (такие /// Frames are needed only for version ≥ 50 classes; `jsr`/`ret` are rejected
/// классы старше и кадры им не нужны). Иерархия — для LUB ссылочных типов. /// (such classes are older and need no frames). The hierarchy is for the LUB of
/// reference types.
pub fn compute_frames( pub fn compute_frames(
class: &str, class: &str,
method: &Method, method: &Method,
@ -112,7 +113,7 @@ pub fn compute_frames(
emit::emit_frames(&states, &targets, &initial.locals, &merger) emit::emit_frames(&states, &targets, &initial.locals, &merger)
} }
/// Байтовое смещение каждой инструкции — тем же кодированием, что в writer'е. /// The byte offset of each instruction — using the same encoding as the writer.
fn layout( fn layout(
bare: &[&Insn], bare: &[&Insn],
labels: &BTreeMap<Label, u32>, labels: &BTreeMap<Label, u32>,
@ -144,7 +145,7 @@ fn layout(
.collect() .collect()
} }
/// Основной цикл: состояния текут по рёбрам CFG до неподвижной точки. /// Main loop: states flow along CFG edges to a fixed point.
fn run_worklist( fn run_worklist(
merger: &Merger<'_>, merger: &Merger<'_>,
bare: &[&Insn], bare: &[&Insn],
@ -187,7 +188,7 @@ fn run_worklist(
Ok(()) Ok(())
} }
/// Влить состояние в инструкцию; `true` — изменилось, продолжить обход. /// Merge a state into an instruction; `true` — changed, keep walking.
fn flow_into( fn flow_into(
merger: &Merger<'_>, merger: &Merger<'_>,
states: &mut [Option<State>], states: &mut [Option<State>],
@ -218,8 +219,8 @@ fn flow_into(
Ok(()) Ok(())
} }
/// Рёбра CFG без исключений: продолжение + цели переходов. /// CFG edges without exceptions: fallthrough + branch targets.
/// Тип исключения на входе в handler (`catch-all` — любое throwable). /// The exception type at a handler's entry (`catch-all` — any throwable).
fn catch_type(catch: &Option<String>) -> VerificationType { fn catch_type(catch: &Option<String>) -> VerificationType {
match catch { match catch {
Some(name) => VerificationType::Object(name.clone()), Some(name) => VerificationType::Object(name.clone()),

View file

@ -1,5 +1,6 @@
//! Чтение/запись бинарного формата `.class` (JVMS §4) через backend //! Reading/writing the binary `.class` format (JVMS §4) through the
//! `ristretto_classfile`: обзор кандидатов и решение — `docs/stack.md`. //! `ristretto_classfile` backend: candidate review and decision in
//! `docs/stack.md`.
mod frames; mod frames;
mod parse; mod parse;
@ -10,20 +11,21 @@ pub use frames::{Hierarchy, StackMapFrame, VerificationType, compute_frames};
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::model::ClassFile; use crate::model::ClassFile;
/// Разобрать один `.class` в [`ClassFile`]. /// Parse one `.class` into a [`ClassFile`].
pub fn parse(bytes: &[u8]) -> Result<ClassFile, ClassfileError> { pub fn parse(bytes: &[u8]) -> Result<ClassFile, ClassfileError> {
parse::class::parse_class(bytes) 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> { pub fn write(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> {
write::write_class(class) write::write_class(class)
} }
/// Классы, которые не изменялись, должны уезжать в выходной архив «как есть». /// Classes that were not modified must go to the output archive "as-is".
/// ///
/// Это дешевле и безопаснее, чем round-trip через наш IR: нет риска потерять /// This is cheaper and safer than a round-trip through our IR: no risk of
/// неизвестный атрибут. Реализация зависит от решения по backend (P1). /// losing an unknown attribute. The implementation depends on the backend
/// decision (P1).
pub fn passthrough_needed(class: &ClassFile) -> bool { pub fn passthrough_needed(class: &ClassFile) -> bool {
class.methods.is_empty() && class.fields.is_empty() class.methods.is_empty() && class.fields.is_empty()
} }

View file

@ -1,10 +1,10 @@
//! Атрибуты backend'а → атрибуты нашего IR. //! Backend attributes → our IR attributes.
//! //!
//! `BootstrapMethods`, `EnclosingMethod`, `MethodParameters` и `ConstantValue` //! `BootstrapMethods`, `EnclosingMethod`, `MethodParameters` and `ConstantValue`
//! моделируются явно (в них лежат ссылки пула, переживающие пересборку); //! are modeled explicitly (they hold pool references that survive rebuilding);
//! остальное структурированное, но нам ненужное, уезжает в `Raw` как есть. //! everything else that is structured but unneeded by us goes to `Raw` as-is.
//! `Code` и `StackMapTable` здесь не обрабатываются — их разбирает вызывающий //! `Code` and `StackMapTable` are not handled here — the calling method code
//! код метода (см. `super::class`). //! parses them (see `super::class`).
use ristretto_classfile::ConstantPool; use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::Attribute as RtAttr; use ristretto_classfile::attributes::Attribute as RtAttr;
@ -15,7 +15,7 @@ use super::pool::{
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::model::{Attribute, BootstrapMethod, InnerClass, MethodParameter, RecordComponent}; 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> { pub(super) fn convert(attr: &RtAttr, pool: &ConstantPool<'_>) -> Result<Attribute, ClassfileError> {
Ok(match attr { Ok(match attr {
RtAttr::SourceFile { 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> { pub(super) fn raw(attr: &RtAttr) -> Result<Attribute, ClassfileError> {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
attr.to_bytes(&mut bytes) 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( fn enclosing_method(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, 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> { fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
if index == 0 { if index == 0 {
Ok(None) 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> { fn optional_utf8(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
if index == 0 { if index == 0 {
Ok(None) Ok(None)

View file

@ -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::attributes::Attribute as RtAttr;
use ristretto_classfile::{ use ristretto_classfile::{
@ -11,7 +11,7 @@ use super::pool::{class_name, utf8};
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version}; 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> { 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 rt = RtClass::from_bytes(bytes).map_err(|e| ClassfileError::Parse(e.to_string()))?;
let pool = &rt.constant_pool; let pool = &rt.constant_pool;
@ -99,7 +99,7 @@ fn convert_attrs(
.collect() .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> { fn optional_class(pool: &ConstantPool<'_>, index: u16) -> Result<Option<String>, ClassfileError> {
if index == 0 { if index == 0 {
Ok(None) Ok(None)

View file

@ -1,7 +1,8 @@
//! Кадры `StackMapTable` при чтении: вариант в вариант, дельты как есть. //! `StackMapTable` frames on read: variant to variant, deltas as-is.
//! //!
//! Backend хранит кадры в номерах инструкций (см. `from_bytes_code_attributes`), //! The backend stores frames in instruction indices (see
//! наш IR — тоже: writer сам пересчитает их в байты при `to_bytes`. //! `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::ConstantPool;
use ristretto_classfile::attributes::{StackFrame, VerificationType as RtType}; use ristretto_classfile::attributes::{StackFrame, VerificationType as RtType};
@ -10,7 +11,7 @@ use super::super::pool::class_name;
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::format::{StackMapFrame, VerificationType}; use crate::format::{StackMapFrame, VerificationType};
/// Кадры backend'а → наши (индексные дельты — как есть). /// Backend frames → ours (index deltas — as-is).
pub(super) fn to_frames( pub(super) fn to_frames(
frames: &[StackFrame], frames: &[StackFrame],
pool: &ConstantPool<'_>, 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> { fn single(stack: &[RtType], pool: &ConstantPool<'_>) -> Result<VerificationType, ClassfileError> {
let mut types = types(stack, pool)?.into_iter(); let mut types = types(stack, pool)?.into_iter();
match (types.next(), types.next()) { match (types.next(), types.next()) {
@ -84,7 +85,7 @@ fn types(
values.iter().map(|value| vtype(value, pool)).collect() 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> { fn vtype(value: &RtType, pool: &ConstantPool<'_>) -> Result<VerificationType, ClassfileError> {
Ok(match value { Ok(match value {
RtType::Top => VerificationType::Top, 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( pub(super) fn catch_type(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, index: u16,

View file

@ -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}; use std::collections::{BTreeMap, BTreeSet};
@ -11,10 +11,10 @@ use ristretto_classfile::attributes::{ExceptionTableEntry, Instruction};
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::Label; 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( pub(super) fn label_indexes(
code: &[Instruction], code: &[Instruction],
exception_table: &[ExceptionTableEntry], exception_table: &[ExceptionTableEntry],
@ -36,7 +36,7 @@ pub(super) fn label_indexes(
Ok(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> { fn branch_indexes(instruction: &Instruction, index: u16) -> Vec<i32> {
let base = i32::from(index); let base = i32::from(index);
match instruction { match instruction {
@ -73,7 +73,7 @@ fn once(value: i32) -> std::iter::Once<i32> {
std::iter::once(value) std::iter::once(value)
} }
/// Превратить отсортированный набор номеров в последовательные метки. /// Turn a sorted set of numbers into sequential labels.
pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> { pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
indexes indexes
.iter() .iter()
@ -82,7 +82,7 @@ pub(super) fn assign(indexes: &BTreeSet<u32>) -> BTreeMap<u32, Label> {
.collect() .collect()
} }
/// Метка для номера инструкции. /// The label for an instruction number.
pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label, ClassfileError> { pub(super) fn lookup(labels: &BTreeMap<u32, Label>, index: u16) -> Result<Label, ClassfileError> {
labels labels
.get(&u32::from(index)) .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}"))) .ok_or_else(|| ClassfileError::Parse(format!("нет метки для инструкции {index}")))
} }
/// Позиции отладочных таблиц, которым нужны метки. /// Debug-table positions that need labels.
pub(super) fn collect_debug_labels( pub(super) fn collect_debug_labels(
attributes: &[RtAttr], attributes: &[RtAttr],
starts: &[u16], starts: &[u16],
@ -122,7 +122,7 @@ pub(super) fn collect_debug_labels(
Ok(()) Ok(())
} }
/// Байт → номер инструкции (только границы инструкций). /// Byte → instruction number (instruction boundaries only).
pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileError> { pub(super) fn byte_to_index(starts: &[u16], byte: u16) -> Result<u32, ClassfileError> {
starts starts
.iter() .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} мимо инструкции"))) .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( fn end_index(
starts: &[u16], starts: &[u16],
start: u16, start: u16,
@ -148,7 +148,7 @@ fn end_index(
byte_to_index(starts, end) byte_to_index(starts, end)
} }
/// Метка для байтового смещения отладочной таблицы. /// The label for a debug table's byte offset.
pub(super) fn resolve_byte( pub(super) fn resolve_byte(
labels: &BTreeMap<u32, Label>, labels: &BTreeMap<u32, Label>,
starts: &[u16], starts: &[u16],
@ -161,7 +161,7 @@ pub(super) fn resolve_byte(
.ok_or_else(|| ClassfileError::Parse(format!("нет метки для смещения {byte}"))) .ok_or_else(|| ClassfileError::Parse(format!("нет метки для смещения {byte}")))
} }
/// Метка конца диапазона отладочной таблицы. /// The end label of a debug table's range.
pub(super) fn resolve_end( pub(super) fn resolve_end(
labels: &BTreeMap<u32, Label>, labels: &BTreeMap<u32, Label>,
starts: &[u16], starts: &[u16],

View file

@ -1,10 +1,10 @@
//! `Code` backend'а → тело метода нашего IR: метки, исключения, кадры. //! Backend `Code` → our IR method body: labels, exceptions, frames.
//! //!
//! Backend уже перевёл байтовые смещения в номера инструкций — конвертеры //! The backend already turned byte offsets into instruction indices — the
//! работают прямо с ними: метки ставятся на целях переходов, границах //! converters work directly with them: labels are placed on branch targets,
//! try-блоков и позициях отладочных таблиц; кадры переносятся как есть //! try-block boundaries and debug-table positions; frames are carried over as-is
//! (индексные дельты). Позиции `LineNumberTable`/`LocalVariableTable` — //! (index deltas). `LineNumberTable`/`LocalVariableTable` positions are labels
//! тоже метки, поэтому вставки пассов их не ломают (байты досчитает writer). //! too, so pass insertions don't break them (the writer recomputes the bytes).
mod frames; mod frames;
mod labels; mod labels;
@ -27,7 +27,7 @@ use crate::model::{
use frames::{catch_type, to_frames}; use frames::{catch_type, to_frames};
use labels::{assign, collect_debug_labels, label_indexes, lookup, resolve_byte, resolve_end}; 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( pub(super) fn convert_code(
max_stack: u16, max_stack: u16,
max_locals: 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> { fn layout(code: &[Instruction]) -> Result<(Vec<u16>, u16), ClassfileError> {
let mut cursor = Cursor::new(Vec::new()); let mut cursor = Cursor::new(Vec::new());
let mut offsets = Vec::with_capacity(code.len()); let mut offsets = Vec::with_capacity(code.len());

View file

@ -1,4 +1,4 @@
//! Простые инструкции без ссылок на пул (кроме `ldc`) и без ветвлений. //! Simple instructions with no pool references (except `ldc`) and no branches.
use ristretto_classfile::ConstantPool; use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::Instruction; use ristretto_classfile::attributes::Instruction;
@ -7,7 +7,7 @@ use super::super::pool::const_from;
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::{Arith, ArrayKind, Const, Convert, Insn, Prim, SlotKind}; 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( pub(super) fn convert_basic(
instruction: &Instruction, instruction: &Instruction,
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,

View file

@ -1,4 +1,4 @@
//! Ветвления, `switch` и возвраты: абсолютные/относительные цели → метки. //! Branches, `switch` and returns: absolute/relative targets → labels.
use ristretto_classfile::attributes::{Instruction, LookupSwitch, TableSwitch}; use ristretto_classfile::attributes::{Instruction, LookupSwitch, TableSwitch};
@ -6,7 +6,7 @@ use super::Resolver;
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::{BranchCond, Cond, Insn, Label, Prim}; 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( pub(super) fn convert_flow(
instruction: &Instruction, instruction: &Instruction,
index: u16, index: u16,
@ -52,7 +52,7 @@ fn branch(cond: BranchCond, target: u16, resolve: &Resolver<'_>) -> Result<Insn,
Ok(Insn::Branch(cond, resolve(target)?)) 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> { fn abs(index: u16, delta: i32, resolve: &Resolver<'_>) -> Result<Label, ClassfileError> {
let absolute = i32::from(index) let absolute = i32::from(index)
.checked_add(delta) .checked_add(delta)

View file

@ -1,9 +1,8 @@
//! `Instruction` backend'а → [`Insn`](crate::insn::Insn) нашего IR. //! Backend `Instruction` → [`Insn`](crate::insn::Insn) of our IR.
//! //!
//! Смещения ветвлений приходят из backend'а (абсолютные для `if*`/`goto`, //! Branch offsets come from the backend (absolute for `if*`/`goto`, relative to
//! относительные от начала инструкции для `switch`), поэтому конвертация //! the instruction start for `switch`), so conversion takes a resolver from an
//! принимает резолвер смещения в [`Label`](crate::insn::Label) — метки //! offset to a [`Label`](crate::insn::Label) — the calling code assigns labels.
//! назначает вызывающий код.
mod basic; mod basic;
mod flow; mod flow;
@ -15,10 +14,10 @@ use ristretto_classfile::attributes::Instruction;
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::{Insn, Label}; 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; 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( pub(super) fn convert(
instruction: &Instruction, instruction: &Instruction,
index: u16, index: u16,

View file

@ -1,4 +1,4 @@
//! Поля, вызовы и создание объектов: индексы пула → строки IR. //! Fields, calls and object creation: pool indices → IR strings.
use ristretto_classfile::ConstantPool; use ristretto_classfile::ConstantPool;
use ristretto_classfile::attributes::{ArrayType, Instruction}; 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::error::ClassfileError;
use crate::insn::{ArrayKind, Insn, InvokeKind}; 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( pub(super) fn convert_ref(
instruction: &Instruction, instruction: &Instruction,
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,

View file

@ -1,9 +1,9 @@
//! Backend `ristretto_classfile` → наш IR: пул, инструкции, тела методов. //! Backend `ristretto_classfile` → our IR: pool, instructions, method bodies.
//! //!
//! Пока ниоткуда не подключён (P1): `format` вернёт `Unimplemented`, пока не //! Not wired in from anywhere yet (P1): `format` returns `Unimplemented` until
//! будут готовы `parse`/`write` классового уровня поверх этих конвертеров. //! 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
//! `mod parse` без рефакторинга. //! refactoring.
pub(super) mod attrs; pub(super) mod attrs;
pub(super) mod class; pub(super) mod class;

View file

@ -1,11 +1,11 @@
//! Разрешение индексов constant pool в строки нашего IR. //! Resolving constant-pool indices into strings of our IR.
use ristretto_classfile::{Constant, ConstantPool, ReferenceKind}; use ristretto_classfile::{Constant, ConstantPool, ReferenceKind};
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::{Const, InvokeKind}; 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> { pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
let name = pool let name = pool
.try_get_class(index) .try_get_class(index)
@ -13,7 +13,7 @@ pub(super) fn class_name(pool: &ConstantPool<'_>, index: u16) -> Result<String,
Ok(name.to_string()) Ok(name.to_string())
} }
/// `(owner, name, descriptor)` для `CONSTANT_Fieldref`/`_Methodref`. /// `(owner, name, descriptor)` for a `CONSTANT_Fieldref`/`_Methodref`.
pub(super) fn ref_parts( pub(super) fn ref_parts(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, index: u16,
@ -45,7 +45,7 @@ pub(super) fn ref_parts(
Ok((owner, name, descriptor)) Ok((owner, name, descriptor))
} }
/// `(name, descriptor)` для `CONSTANT_NameAndType`. /// `(name, descriptor)` for a `CONSTANT_NameAndType`.
pub(super) fn name_and_type( pub(super) fn name_and_type(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, index: u16,
@ -58,7 +58,7 @@ pub(super) fn name_and_type(
Ok((name, descriptor)) Ok((name, descriptor))
} }
/// Строка из `CONSTANT_Utf8`. /// The string from a `CONSTANT_Utf8`.
pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> { pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, ClassfileError> {
let value = pool let value = pool
.try_get_utf8(index) .try_get_utf8(index)
@ -66,7 +66,7 @@ pub(super) fn utf8(pool: &ConstantPool<'_>, index: u16) -> Result<String, Classf
Ok(value.to_string()) 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> { pub(super) fn const_from(pool: &ConstantPool<'_>, index: u16) -> Result<Const, ClassfileError> {
let constant = pool let constant = pool
.try_get(index) .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( pub(super) fn dynamic_parts(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, index: u16,
@ -134,12 +134,12 @@ pub(super) fn dynamic_parts(
Ok((name, descriptor, bootstrap)) Ok((name, descriptor, bootstrap))
} }
/// Вид вызова по индексу ссылки: интерфейсная или обычная. /// Call kind by reference index: interface or regular.
pub(super) fn is_interface(pool: &ConstantPool<'_>, index: u16) -> bool { pub(super) fn is_interface(pool: &ConstantPool<'_>, index: u16) -> bool {
matches!(pool.try_get(index), Ok(Constant::InterfaceMethodRef { .. })) 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 { pub(super) fn kind_code(kind: &ReferenceKind) -> u8 {
match kind { match kind {
ReferenceKind::GetField => 1, 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` для всех видов вызова, /// The JVM accepts both `Methodref` and `InterfaceMethodref` for every call
/// кроме `invokeinterface` (там только интерфейсная); строгость сверх JVM /// kind except `invokeinterface` (interface reference only there); being
/// ломает чтение реальных классов (статические методы интерфейсов). /// stricter than the JVM breaks reading real classes (interface static methods).
pub(super) fn expect_kind( pub(super) fn expect_kind(
pool: &ConstantPool<'_>, pool: &ConstantPool<'_>,
index: u16, index: u16,

View file

@ -1,7 +1,7 @@
//! Атрибуты нашего IR → атрибуты backend'а. //! Our IR attributes → backend attributes.
//! //!
//! Имена атрибутов (`SourceFile`, …) интернируются в пул здесь же — backend //! Attribute names (`SourceFile`, …) are interned into the pool right here — the
//! хранит только `name_index`, строка восстанавливается при чтении. //! backend stores only `name_index`, and the string is recovered on read.
use ristretto_classfile::attributes::{ use ristretto_classfile::attributes::{
Attribute as RtAttr, LineNumber as RtLine, LocalVariableTable as RtLocal, Attribute as RtAttr, LineNumber as RtLine, LocalVariableTable as RtLocal,
@ -14,13 +14,13 @@ use crate::error::ClassfileError;
use crate::insn::Label; use crate::insn::Label;
use crate::model::{Attribute, LineNumber, LocalVariable, LocalVariableType}; use crate::model::{Attribute, LineNumber, LocalVariable, LocalVariableType};
/// Позиции таблиц кода: метки → байты текущего layout. /// Code-table positions: labels → bytes of the current layout.
pub(super) struct Positions<'a> { pub(super) struct Positions<'a> {
pub resolve: &'a dyn Fn(Label) -> Result<u32, ClassfileError>, pub resolve: &'a dyn Fn(Label) -> Result<u32, ClassfileError>,
pub byte_of: &'a dyn Fn(u32) -> Result<u16, 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( pub(super) fn line_number_table(
lines: &[LineNumber], lines: &[LineNumber],
pool: &mut Pool, 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( pub(super) fn local_table(
vars: &[LocalVariable], vars: &[LocalVariable],
pool: &mut Pool, pool: &mut Pool,
@ -66,7 +66,7 @@ pub(super) fn local_table(
.collect::<Result<_, ClassfileError>>()?, .collect::<Result<_, ClassfileError>>()?,
}) })
} }
/// Та же таблица для типов (`signature` вместо дескриптора). /// The same table for types (`signature` instead of a descriptor).
pub(super) fn local_type_table( pub(super) fn local_type_table(
vars: &[LocalVariableType], vars: &[LocalVariableType],
pool: &mut Pool, pool: &mut Pool,
@ -103,7 +103,7 @@ fn range(
Ok((first, length)) Ok((first, length))
} }
/// Один наш атрибут → атрибут backend'а. /// One of our attributes → a backend attribute.
pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, ClassfileError> { pub(super) fn convert(attr: &Attribute, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
Ok(match attr { Ok(match attr {
Attribute::SourceFile(name) => RtAttr::SourceFile { Attribute::SourceFile(name) => RtAttr::SourceFile {

View file

@ -1,9 +1,9 @@
//! Кадры `StackMapTable` при записи: вариант в вариант, дельты по инструкциям. //! `StackMapTable` frames on write: variant to variant, deltas per instruction.
//! //!
//! Наш IR хранит `offset_delta` в номерах инструкций; backend при `to_bytes` //! Our IR stores `offset_delta` in instruction numbers; the backend recomputes
//! сам пересчитает их в байты. Компактный формат (`Same`/`SameLocals`) годен //! them into bytes on `to_bytes`. The compact form (`Same`/`SameLocals`) is valid
//! только когда **байтовая** дельта ≤ 63 — иначе нужен расширенный вариант. //! only when the **byte** delta is ≤ 63 — otherwise the extended form is needed.
//! Выбор делаем по байтовой дельте: байты считаются от layout текущего метода. //! 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}; 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::error::ClassfileError;
use crate::format::{StackMapFrame, VerificationType}; 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); 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( pub(super) fn convert_frames(
frames: &[StackMapFrame], frames: &[StackMapFrame],
pool: &mut Pool, pool: &mut Pool,
@ -32,7 +32,7 @@ pub(super) fn convert_frames(
last_instruction + delta + 1 last_instruction + delta + 1
}; };
let byte = byte_of(instruction)?; let byte = byte_of(instruction)?;
// Байтовая дельта относительно конца предыдущего кадра. // Byte delta relative to the end of the previous frame.
let byte_delta = if first { let byte_delta = if first {
byte byte
} else { } else {
@ -48,7 +48,7 @@ pub(super) fn convert_frames(
Ok(out) Ok(out)
} }
/// `byte_delta` — истинная байтовая дельта; компактный вид годен лишь при ≤ 63. /// `byte_delta` is the true byte delta; the compact form is valid only when ≤ 63.
fn build_frame( fn build_frame(
frame: &StackMapFrame, frame: &StackMapFrame,
pool: &mut Pool, pool: &mut Pool,
@ -60,8 +60,8 @@ fn build_frame(
Ok(match frame { Ok(match frame {
StackMapFrame::Same { .. } => { StackMapFrame::Same { .. } => {
if byte_delta <= 63 { if byte_delta <= 63 {
// Backend при verify()/to_bytes читает frame_type как дельту по // The backend, in verify()/to_bytes, reads frame_type as a delta by
// ИНСТРУКЦИЯМ и сам пересчитает её в байты (≤ 63 — влезает). // INSTRUCTION and recomputes it into bytes itself (≤ 63 fits).
RtFrame::SameFrame { RtFrame::SameFrame {
frame_type: delta as u8, frame_type: delta as u8,
} }

View file

@ -1,8 +1,8 @@
//! Наш [`Insn`](crate::insn::Insn) → `Instruction` backend'а. //! Our [`Insn`](crate::insn::Insn) → the backend's `Instruction`.
//! //!
//! Обратная сторона `super::super::parse::insns`: метки резолвятся в //! The reverse side of `super::super::parse::insns`: labels resolve into
//! абсолютные номера инструкций (`switch` — в дельты от себя), константы //! absolute instruction numbers (`switch` — into deltas from itself), constants
//! и ссылки уходят в пул через [`Pool`](super::super::pool::Pool). //! and references go to the pool via [`Pool`](super::super::pool::Pool).
use indexmap::IndexMap; use indexmap::IndexMap;
use ristretto_classfile::attributes::{Instruction as RtInsn, LookupSwitch, TableSwitch}; 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::error::ClassfileError;
use crate::insn::{Insn, Label, SlotKind}; 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( pub(crate) fn convert(
insn: &Insn, insn: &Insn,
index: u32, 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 { fn narrow(slot: u16, short: fn(u8) -> RtInsn, wide: fn(u16) -> RtInsn) -> RtInsn {
u8::try_from(slot).map(short).unwrap_or_else(|_| wide(slot)) u8::try_from(slot).map(short).unwrap_or_else(|_| wide(slot))
} }

View file

@ -1,7 +1,7 @@
//! Тело метода нашего IR → атрибут `Code` backend'а. //! Our IR method body → the backend `Code` attribute.
//! //!
//! Метки резолвятся в номера инструкций один раз здесь; дальше backend сам //! Labels resolve into instruction numbers once, here; then the backend
//! пересчитывает номера в байтовые смещения при `to_bytes`. //! recomputes numbers into byte offsets on `to_bytes`.
mod frames; mod frames;
pub(crate) mod insns; pub(crate) mod insns;
@ -22,7 +22,7 @@ use crate::model::{Attribute, Code, ExceptionHandler};
use frames::convert_frames; 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> { pub(super) fn convert_code(code: &Code, pool: &mut Pool) -> Result<RtAttr, ClassfileError> {
let (labels, bare) = split_marks(&code.insns)?; let (labels, bare) = split_marks(&code.insns)?;
let resolve = |label: Label| -> Result<u32, ClassfileError> { 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); let mut attributes = Vec::with_capacity(code.attributes.len() + 1);
for attr in &code.attributes { for attr in &code.attributes {
attributes.push(match attr { 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::LineNumberTable(lines) => attrs::line_number_table(lines, pool, &positions)?,
Attribute::LocalVariableTable(vars) => attrs::local_table(vars, pool, &positions)?, Attribute::LocalVariableTable(vars) => attrs::local_table(vars, pool, &positions)?,
Attribute::LocalVariableTypeTable(vars) => { 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> { fn split_marks(insns: &[Insn]) -> Result<(BTreeMap<Label, u32>, Vec<&Insn>), ClassfileError> {
let mut labels = BTreeMap::new(); let mut labels = BTreeMap::new();
let mut bare = Vec::with_capacity(insns.len()); 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)) 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> { pub(crate) fn measure(insns: &[RtInsn]) -> Result<BTreeMap<u32, u16>, ClassfileError> {
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
let mut cursor = Cursor::new(Vec::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) Ok(map)
} }
/// Цели ветвлений не влияют на размер — подменяем текущей позицией для замера. /// Branch targets don't affect size — substitute the current position for sizing.
fn smash_targets(insn: &mut RtInsn, position: u16) { fn smash_targets(insn: &mut RtInsn, position: u16) {
match insn { match insn {
RtInsn::Ifeq(target) 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> { fn branch(cond: BranchCond, target: u32) -> Result<RtInsn, ClassfileError> {
let target = fit_index(target)?; let target = fit_index(target)?;
Ok(match cond { 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> { fn fit_index(index: u32) -> Result<u16, ClassfileError> {
u16::try_from(index) u16::try_from(index)
.map_err(|_| ClassfileError::Write(format!("номер инструкции {index} вне u16"))) .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> { fn delta(index: u32, target: u32) -> Result<i32, ClassfileError> {
let (index, target) = (to_i32(index)?, to_i32(target)?); let (index, target) = (to_i32(index)?, to_i32(target)?);
target target

View file

@ -1,4 +1,4 @@
//! Операнды: константы, арифметика, массивы, вызовы. //! Operands: constants, arithmetic, arrays, calls.
use ristretto_classfile::attributes::{ArrayType, Instruction as RtInsn}; use ristretto_classfile::attributes::{ArrayType, Instruction as RtInsn};
@ -6,7 +6,7 @@ use super::super::pool::Pool;
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::insn::{Arith, ArrayKind, Const, Convert, InvokeKind, Prim}; 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> { pub(super) fn int_const(value: &Const, pool: &mut Pool) -> Result<RtInsn, ClassfileError> {
Ok(match value { Ok(match value {
Const::Int(v) => match *v { 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> { fn arg_count(descriptor: &str) -> Result<u8, ClassfileError> {
let params = descriptor let params = descriptor
.split_once('(') .split_once('(')
@ -229,7 +229,7 @@ fn arg_count(descriptor: &str) -> Result<u8, ClassfileError> {
.map_err(|_| ClassfileError::Write(format!("слишком много аргументов в {descriptor}"))) .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 { fn skip_to(chars: &mut std::str::Chars<'_>, end: char) -> bool {
for char in chars { for char in chars {
if char == end { if char == end {

View file

@ -1,8 +1,9 @@
//! Хелперы сборки `InnerClasses`/`BootstrapMethods`/`MethodParameters`. //! Helpers building `InnerClasses`/`BootstrapMethods`/`MethodParameters`.
//! //!
//! Вынесены из `attrs`, чтобы тот держался в лимите 250 строк; логика не //! Moved out of `attrs` so it stays within the 250-line limit; the logic is
//! изменилась — только место. Каждый строит запись backend'а из элемента IR, //! unchanged — only the location. Each builds a backend entry from an IR
//! интернируя имена/дескрипторы и резолвя индексы `CONSTANT_Class` через пул. //! element, interning names/descriptors and resolving `CONSTANT_Class` indices
//! through the pool.
use ristretto_classfile::MethodAccessFlags; use ristretto_classfile::MethodAccessFlags;
use ristretto_classfile::attributes::{ 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> { fn bootstrap_arg(arg: &Const, pool: &mut Pool) -> Result<u16, ClassfileError> {
match arg { match arg {
Const::Dynamic { Const::Dynamic {

View file

@ -1,7 +1,7 @@
//! Наш IR → байты `.class` через backend `ristretto_classfile`. //! Our IR → `.class` bytes through the `ristretto_classfile` backend.
//! //!
//! Пул собирается с нуля детерминированным обходом; backend сам пересчитывает //! The pool is built from scratch by a deterministic walk; the backend recomputes
//! номера инструкций в байтовые смещения и проверяет результат (`verify`). //! instruction numbers into byte offsets and verifies the result (`verify`).
mod attrs; mod attrs;
pub(crate) mod code; pub(crate) mod code;
@ -16,7 +16,7 @@ use ristretto_classfile::{
use crate::error::ClassfileError; use crate::error::ClassfileError;
use crate::model::{ClassFile, Field, Method}; use crate::model::{ClassFile, Field, Method};
/// Класс нашего IR → байты `.class`. /// Our IR class → `.class` bytes.
pub(crate) fn write_class(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> { pub(crate) fn write_class(class: &ClassFile) -> Result<Vec<u8>, ClassfileError> {
let mut pool = pool::Pool::new(); let mut pool = pool::Pool::new();
let version = RtVersion::from(class.version.major, class.version.minor) let version = RtVersion::from(class.version.major, class.version.minor)

View file

@ -1,15 +1,15 @@
//! Сборка constant pool при записи: строки нашего IR → индексы backend'а. //! Building the constant pool on write: our IR strings → backend indices.
//! //!
//! Пул строится с нуля детерминированным обходом (классы, поля, методы, //! The pool is built from scratch by a deterministic walk (classes, fields,
//! атрибуты, код), поэтому одинаковый IR даёт одинаковые байты. Индексы //! methods, attributes, code), so identical IR yields identical bytes. `Raw`
//! `Raw`-атрибутов при этом не переносятся — см. `super::attrs`. //! attribute indices are not carried over — see `super::attrs`.
//! //!
//! Backend не дедуплицирует: `add_*` всегда толкает новый элемент. Кэшируем //! The backend does not deduplicate: `add_*` always pushes a new entry. We cache
//! семантические ключи сами — иначе `LocalVariableTable` и //! semantic keys ourselves — otherwise `LocalVariableTable` and
//! `LocalVariableTypeTable` получат разные `name_index` для одного имени, и //! `LocalVariableTypeTable` would get different `name_index` values for the same
//! HotSpot отвергнет класс (`LVTT entry ... does not match any LVT entry`). //! 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
//! Референсы (поле/метод/хендл/динамик) — в [`refs`]. //! compactness. References (field/method/handle/dynamic) live in [`refs`].
use std::collections::HashMap; use std::collections::HashMap;
@ -20,7 +20,7 @@ use crate::insn::Const;
mod refs; mod refs;
/// Строитель пула; живёт только внутри одного `write`. /// The pool builder; lives only within a single `write`.
pub(crate) struct Pool { pub(crate) struct Pool {
pub(super) pool: ConstantPool<'static>, pub(super) pool: ConstantPool<'static>,
pub(super) utf8s: HashMap<String, u16>, pub(super) utf8s: HashMap<String, u16>,
@ -68,8 +68,8 @@ impl Pool {
if let Some(&index) = self.name_types.get(&key) { if let Some(&index) = self.name_types.get(&key) {
return Ok(index); return Ok(index);
} }
// Собираем из примитивов, а не `add_name_and_type`: последний толкает // Build from primitives, not `add_name_and_type`: the latter pushes its
// собственные `Utf8`-строки мимо нашего кэша (дубли в пуле). // own `Utf8` strings past our cache (pool duplicates).
let name_index = self.utf8(name)?; let name_index = self.utf8(name)?;
let descriptor_index = self.utf8(descriptor)?; let descriptor_index = self.utf8(descriptor)?;
let index = self let index = self
@ -96,7 +96,7 @@ impl Pool {
Ok(index) 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> { pub(super) fn constant(&mut self, value: &Const) -> Result<u16, ClassfileError> {
match value { match value {
Const::Int(v) => self.pool.add_integer(*v).map_err(write_err), Const::Int(v) => self.pool.add_integer(*v).map_err(write_err),

View file

@ -1,10 +1,10 @@
//! Референсы constant pool: `Fieldref`/`Methodref`/`MethodHandle`/`Dynamic`. //! Constant-pool references: `Fieldref`/`Methodref`/`MethodHandle`/`Dynamic`.
//! //!
//! Это `impl Pool` из [`super`]; вынесено отдельно, чтобы материнский файл //! 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/…) — одинаковая ссылка всегда получает один //! (owner/name/descriptor/…) — an identical reference always gets the same index,
//! индекс, а пул не раздувается дублями (раздувание иначе расширяет `ldc` //! and the pool doesn't bloat with duplicates (bloat would otherwise widen `ldc`
//! в `ldc_w` и меняет длину кода при переносе). //! into `ldc_w` and change code length on relocation).
use ristretto_classfile::{Constant, ReferenceKind}; use ristretto_classfile::{Constant, ReferenceKind};

View file

@ -1,9 +1,9 @@
//! Типы операндов инструкций: константы, примитивы, условия, вызовы. //! Instruction operand types: constants, primitives, conditions, calls.
/// Идентификатор метки внутри одного метода. /// A label identifier within a single method.
pub type Label = u32; pub type Label = u32;
/// Константа для `ldc`/`ldc_w`/`ldc2_w`. /// A constant for `ldc`/`ldc_w`/`ldc2_w`.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Const { pub enum Const {
Int(i32), Int(i32),
@ -11,7 +11,7 @@ pub enum Const {
Float(f32), Float(f32),
Double(f64), Double(f64),
String(String), String(String),
/// Внутреннее имя класса (`java/lang/String`). /// The class's internal name (`java/lang/String`).
Class(String), Class(String),
MethodType(String), MethodType(String),
MethodHandle { MethodHandle {
@ -21,7 +21,7 @@ pub enum Const {
descriptor: String, descriptor: String,
itf: bool, itf: bool,
}, },
/// `CONSTANT_Dynamic`: имя, дескриптор, индекс bootstrap-метода. /// `CONSTANT_Dynamic`: name, descriptor, bootstrap-method index.
Dynamic { Dynamic {
name: String, name: String,
descriptor: String, descriptor: String,
@ -29,7 +29,7 @@ pub enum Const {
}, },
} }
/// Размер операнда/результата арифметики. /// Operand/result size of an arithmetic operation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Prim { pub enum Prim {
Int, Int,
@ -38,7 +38,7 @@ pub enum Prim {
Double, Double,
} }
/// Арифметическая/битовая операция; тип берётся из [`Prim`]. /// An arithmetic/bitwise operation; the type comes from [`Prim`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arith { pub enum Arith {
Add, Add,
@ -55,7 +55,7 @@ pub enum Arith {
Ushr, Ushr,
} }
/// Условие перехода. /// A branch condition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cond { pub enum Cond {
Eq, Eq,
@ -66,10 +66,10 @@ pub enum Cond {
Ge, Ge,
} }
/// Вид условного перехода (соответствует семействам `if*`). /// Kind of conditional branch (matches the `if*` families).
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BranchCond { pub enum BranchCond {
/// `if<cond>` относительно нуля. /// `if<cond>` against zero.
Zero(Cond), Zero(Cond),
/// `if_icmp<cond>`. /// `if_icmp<cond>`.
Int(Cond), Int(Cond),
@ -79,7 +79,7 @@ pub enum BranchCond {
Null { is_null: bool }, Null { is_null: bool },
} }
/// Вид вызова. /// Kind of call.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvokeKind { pub enum InvokeKind {
Virtual, Virtual,
@ -88,7 +88,7 @@ pub enum InvokeKind {
Interface, 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Convert { pub enum Convert {
I2L, I2L,
@ -108,7 +108,7 @@ pub enum Convert {
D2F, D2F,
} }
/// Тип элемента массива для `newarray`/`*aload`/`*astore`. /// Array element type for `newarray`/`*aload`/`*astore`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArrayKind { pub enum ArrayKind {
Boolean, Boolean,
@ -122,10 +122,10 @@ pub enum ArrayKind {
Reference, Reference,
} }
/// Тип слота локальной переменной для `*load*`/`*store*`. /// Local-variable slot type for `*load*`/`*store*`.
/// ///
/// Хранится явно: выбор опкода (`iload` vs `aload`) — подсказка верификатору, /// Stored explicitly: the opcode choice (`iload` vs `aload`) is a hint to the
/// потерять её при round-trip нельзя. /// verifier, and losing it on a round-trip is not acceptable.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlotKind { pub enum SlotKind {
Int, Int,

View file

@ -1,7 +1,7 @@
//! Инструкции IR: смещения отсутствуют, ветвления адресуют [`Label`]. //! IR instructions: no offsets, branches address a [`Label`].
//! //!
//! Это принципиально: любой пасс, меняющий порядок/длину инструкций, не обязан //! This is fundamental: any pass that changes instruction order/length need not
//! пересчитывать jump-офсеты — это делает writer один раз в конце пайплайна. //! recompute jump offsets — the writer does that once at the end of the pipeline.
mod kinds; mod kinds;
@ -9,32 +9,32 @@ pub use kinds::{
Arith, ArrayKind, BranchCond, Cond, Const, Convert, InvokeKind, Label, Prim, SlotKind, Arith, ArrayKind, BranchCond, Cond, Const, Convert, InvokeKind, Label, Prim, SlotKind,
}; };
/// Мнемоника одной инструкции. /// Mnemonic of a single instruction.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Insn { pub enum Insn {
/// Маркер метки. Ветвления указывают на [`Label`], writer вычисляет смещения. /// Label marker. Branches point at a [`Label`]; the writer computes offsets.
Mark(Label), Mark(Label),
Nop, Nop,
AconstNull, AconstNull,
Const(Const), Const(Const),
/// Загрузка локальной переменной (`iload`, `aload`, ...). /// Load a local variable (`iload`, `aload`, ...).
Load { Load {
slot: u16, slot: u16,
kind: SlotKind, kind: SlotKind,
}, },
/// Сохранение локальной переменной. /// Store a local variable.
Store { Store {
slot: u16, slot: u16,
kind: SlotKind, kind: SlotKind,
}, },
/// `iinc` — увеличение локальной переменной на константу. /// `iinc` — increase a local variable by a constant.
Iinc { Iinc {
index: u16, index: u16,
delta: i32, delta: i32,
}, },
/// Загрузка элемента массива. /// Load an array element.
ArrayLoad(ArrayKind), ArrayLoad(ArrayKind),
/// Сохранение элемента массива. /// Store an array element.
ArrayStore(ArrayKind), ArrayStore(ArrayKind),
Pop, Pop,
Pop2, Pop2,
@ -45,23 +45,23 @@ pub enum Insn {
Dup2X1, Dup2X1,
Dup2X2, Dup2X2,
Swap, Swap,
/// `iadd`, `lmul`, `lneg`, ... — тип + операция. /// `iadd`, `lmul`, `lneg`, ... — type + operation.
Binary { Binary {
prim: Prim, prim: Prim,
op: Arith, op: Arith,
}, },
/// Сравнение длин (`lcmp`) и плавающих (`fcmpl`/`fcmpg`). /// Comparison of longs (`lcmp`) and floats (`fcmpl`/`fcmpg`).
Cmp { Cmp {
prim: Prim, prim: Prim,
nan_great: bool, nan_great: bool,
}, },
/// Конверсия типов (`i2l`, `d2f`, ...). /// Type conversion (`i2l`, `d2f`, ...).
Convert(Convert), Convert(Convert),
Branch(BranchCond, Label), Branch(BranchCond, Label),
Goto(Label), Goto(Label),
/// `jsr` — вызов подпрограммы (только классы версии ≤ 50). /// `jsr` — subroutine call (only version ≤ 50 classes).
Jsr(Label), Jsr(Label),
/// `ret` — возврат из подпрограммы (`jsr`/`ret` парны). /// `ret` — return from a subroutine (`jsr`/`ret` come in pairs).
Ret(u16), Ret(u16),
TableSwitch { TableSwitch {
default: Label, default: Label,
@ -73,9 +73,9 @@ pub enum Insn {
default: Label, default: Label,
pairs: Vec<(i32, Label)>, pairs: Vec<(i32, Label)>,
}, },
/// `return`/`ireturn`/...; `None` — пустой возврат. /// `return`/`ireturn`/...; `None` — void return.
Return(Option<Prim>), Return(Option<Prim>),
/// `areturn` — возврат ссылки (у [`Prim`] нет ссылочного типа). /// `areturn` — return a reference ([`Prim`] has no reference type).
AReturn, AReturn,
GetStatic { GetStatic {
owner: String, owner: String,
@ -112,7 +112,7 @@ pub enum Insn {
New(String), New(String),
NewArray(ArrayKind), NewArray(ArrayKind),
ANewArray(String), ANewArray(String),
/// `multianewarray` — массив любого измерения; `dims` — сколько измерений со стека. /// `multianewarray` — array of any dimension; `dims` — how many dimensions from the stack.
MultiANewArray { MultiANewArray {
descriptor: String, descriptor: String,
dims: u8, dims: u8,
@ -126,7 +126,7 @@ pub enum Insn {
} }
impl Insn { impl Insn {
/// Метки, на которые ссылается инструкция (для построения CFG). /// Labels the instruction references (for building the CFG).
pub fn targets(&self) -> Vec<Label> { pub fn targets(&self) -> Vec<Label> {
match self { match self {
Insn::Branch(_, label) | Insn::Goto(label) | Insn::Jsr(label) => vec![*label], 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> { pub fn branch_target_mut(&mut self) -> Option<&mut Label> {
match self { match self {
Insn::Branch(_, label) | Insn::Goto(label) => Some(label), Insn::Branch(_, label) | Insn::Goto(label) => Some(label),

View file

@ -1,13 +1,13 @@
//! # noxis-classfile //! # noxis-classfile
//! //!
//! Собственный IR для классовых файлов JVM: разбор, представление, запись. //! Own IR for JVM class files: parsing, representation, writing.
//! //!
//! Почему свой IR, а не работа напрямую с крейтом-парсером: пассам нужна //! Why a custom IR instead of working directly with a parser crate: passes
//! стабильная модель (метки вместо смещений, осознанный набор атрибутов), //! need a stable model (labels instead of offsets, a deliberate attribute
//! а не API третьей стороны, который меняется между версиями. //! set), not a third-party API that changes between versions.
//! //!
//! Статус: модель и билдер готовы, бинарный parse/write — этап P1 //! Status: model and builder are ready; binary parse/write is stage P1
//! (см. `docs/stack.md`). //! (see `docs/stack.md`).
pub mod error; pub mod error;
pub mod format; pub mod format;

View file

@ -1,8 +1,8 @@
//! Сборка классов «с нуля»: junk-классы, рантайм-декодеры, аннотации-шум. //! Building classes "from scratch": junk classes, runtime decoders, noise annotations.
use crate::model::{Attribute, ClassFile, Code, Field, Method, Version}; use crate::model::{Attribute, ClassFile, Code, Field, Method, Version};
/// Строитель класса без бинарного представления на входе. /// A class builder with no binary representation on input.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ClassBuilder { pub struct ClassBuilder {
version: Version, version: Version,
@ -16,8 +16,8 @@ pub struct ClassBuilder {
} }
impl ClassBuilder { impl ClassBuilder {
/// Новый класс `public` с указанным суперклассом (`None` = `java/lang/Object`, /// A new `public` class with the given superclass (`None` = `java/lang/Object`,
/// кроме самого `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 { pub fn new(this_class: impl Into<String>, super_class: Option<&str>) -> Self {
let this_class = this_class.into(); let this_class = this_class.into();
let super_class = match super_class { 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 { pub fn version(mut self, version: Version) -> Self {
self.version = version; self.version = version;
self self
} }
/// Задать флаги доступа (перезаписывает `ACC_PUBLIC|ACC_SUPER`). /// Set access flags (overwrites `ACC_PUBLIC|ACC_SUPER`).
pub fn access_flags(mut self, flags: u16) -> Self { pub fn access_flags(mut self, flags: u16) -> Self {
self.access_flags = flags; self.access_flags = flags;
self self
} }
/// Добавить интерфейс. /// Add an interface.
pub fn interface(mut self, name: impl Into<String>) -> Self { pub fn interface(mut self, name: impl Into<String>) -> Self {
self.interfaces.push(name.into()); self.interfaces.push(name.into());
self self
} }
/// Добавить поле. /// Add a field.
pub fn field(mut self, name: impl Into<String>, descriptor: impl Into<String>) -> Self { pub fn field(mut self, name: impl Into<String>, descriptor: impl Into<String>) -> Self {
self.fields.push(Field { self.fields.push(Field {
access_flags: 0x0001, // ACC_PUBLIC access_flags: 0x0001, // ACC_PUBLIC
@ -66,7 +66,7 @@ impl ClassBuilder {
self self
} }
/// Добавить метод без тела (`abstract`/`native` должны выставить флаги сами). /// Add a method without a body (`abstract`/`native` must set their own flags).
pub fn method( pub fn method(
mut self, mut self,
access_flags: u16, access_flags: u16,
@ -83,7 +83,7 @@ impl ClassBuilder {
self self
} }
/// Добавить метод с телом. /// Add a method with a body.
pub fn method_with_code( pub fn method_with_code(
mut self, mut self,
access_flags: u16, access_flags: u16,
@ -101,13 +101,13 @@ impl ClassBuilder {
self self
} }
/// Добавить атрибут уровня класса. /// Add a class-level attribute.
pub fn attribute(mut self, attribute: Attribute) -> Self { pub fn attribute(mut self, attribute: Attribute) -> Self {
self.attributes.push(attribute); self.attributes.push(attribute);
self self
} }
/// Собрать класс. /// Build the class.
pub fn build(self) -> ClassFile { pub fn build(self) -> ClassFile {
ClassFile { ClassFile {
version: self.version, 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 { pub fn empty_code(max_locals: u16) -> Code {
Code { Code {
max_stack: 0, max_stack: 0,

View file

@ -1,4 +1,4 @@
//! Структура «программы» — единица работы пайплайна. //! The "program" structure — the unit of work for the pipeline.
pub mod builder; pub mod builder;
pub mod version; pub mod version;
@ -8,7 +8,7 @@ pub use version::Version;
use crate::format::StackMapFrame; use crate::format::StackMapFrame;
use crate::insn::{Const, Insn, Label}; use crate::insn::{Const, Insn, Label};
/// Неизвестные/специфичные атрибуты хранятся байтами — round-trip без потерь. /// Unknown/specific attributes are stored as bytes — lossless round-trip.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Attribute { pub enum Attribute {
SourceFile(String), SourceFile(String),
@ -19,26 +19,26 @@ pub enum Attribute {
Deprecated, Deprecated,
Synthetic, Synthetic,
InnerClasses(Vec<InnerClass>), InnerClasses(Vec<InnerClass>),
/// `NestHost`: класс, служащий nest-хостом (JVMS §4.7.28). /// `NestHost`: the class acting as nest host (JVMS §4.7.28).
NestHost(String), NestHost(String),
/// `NestMembers`: классы-члены этого nest-хоста (JVMS §4.7.29). /// `NestMembers`: the member classes of this nest host (JVMS §4.7.29).
NestMembers(Vec<String>), NestMembers(Vec<String>),
/// `ConstantValue` поля (`static final`): значение из пула. /// A field's `ConstantValue` (`static final`): a value from the pool.
ConstantValue(Const), ConstantValue(Const),
/// `BootstrapMethods` для `invokedynamic` (лямбды): метод + аргументы. /// `BootstrapMethods` for `invokedynamic` (lambdas): method + arguments.
BootstrapMethods(Vec<BootstrapMethod>), BootstrapMethods(Vec<BootstrapMethod>),
/// `EnclosingMethod`: класс и опциональный метод, содержащие локальный класс. /// `EnclosingMethod`: the class and optional method enclosing a local class.
EnclosingMethod { EnclosingMethod {
class: String, class: String,
method: Option<(String, String)>, method: Option<(String, String)>,
}, },
/// `MethodParameters`: имена формальных параметров (`None` — безымянный). /// `MethodParameters`: formal parameter names (`None` — unnamed).
MethodParameters(Vec<MethodParameter>), MethodParameters(Vec<MethodParameter>),
/// `Record`: компоненты record-класса. /// `Record`: the record class's components.
Record(Vec<RecordComponent>), Record(Vec<RecordComponent>),
/// `LocalVariableTypeTable`: сигнатуры generic-локальных (как LVT). /// `LocalVariableTypeTable`: generic local signatures (like LVT).
LocalVariableTypeTable(Vec<LocalVariableType>), LocalVariableTypeTable(Vec<LocalVariableType>),
/// Всё остальное: имя + сырые байты `info` (осторожно с индексами пула). /// Everything else: name + raw `info` bytes (careful with pool indices).
Raw { Raw {
name: String, name: String,
data: Vec<u8>, data: Vec<u8>,
@ -47,14 +47,14 @@ pub enum Attribute {
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct LineNumber { pub struct LineNumber {
/// Метка начала (байты вычисляет writer — переживает вставки пассов). /// Start label (the writer computes bytes — survives pass insertions).
pub start: Label, pub start: Label,
pub line: u16, pub line: u16,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LocalVariable { pub struct LocalVariable {
/// Метки диапазона видимости (writer пересчитает в байты). /// Visibility-range labels (the writer recomputes them into bytes).
pub start: Label, pub start: Label,
pub end: Label, pub end: Label,
pub name: String, pub name: String,
@ -70,10 +70,10 @@ pub struct InnerClass {
pub access_flags: u16, pub access_flags: u16,
} }
/// Один `bootstrap`-метод: ссылка вида `MethodHandle` + аргументы-константы. /// One `bootstrap` method: a `MethodHandle`-style reference + const arguments.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct BootstrapMethod { pub struct BootstrapMethod {
/// Номер `reference_kind` (JVMS §5.4.3.5). /// The `reference_kind` number (JVMS §5.4.3.5).
pub kind: u8, pub kind: u8,
pub owner: String, pub owner: String,
pub name: String, pub name: String,
@ -82,14 +82,14 @@ pub struct BootstrapMethod {
pub args: Vec<Const>, pub args: Vec<Const>,
} }
/// Формальный параметр метода (`MethodParameters`). /// A method's formal parameter (`MethodParameters`).
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct MethodParameter { pub struct MethodParameter {
pub name: Option<String>, pub name: Option<String>,
pub access_flags: u16, pub access_flags: u16,
} }
/// Компонент `Record`: поле record-класса с атрибутами. /// A `Record` component: a record class field with attributes.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RecordComponent { pub struct RecordComponent {
pub name: String, pub name: String,
@ -97,10 +97,10 @@ pub struct RecordComponent {
pub attributes: Vec<Attribute>, pub attributes: Vec<Attribute>,
} }
/// Строка `LocalVariableTypeTable` (как `LocalVariable`, но с сигнатурой). /// A `LocalVariableTypeTable` entry (like `LocalVariable`, but with a signature).
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LocalVariableType { pub struct LocalVariableType {
/// Метки диапазона видимости (writer пересчитает в байты). /// Visibility-range labels (the writer recomputes them into bytes).
pub start: Label, pub start: Label,
pub end: Label, pub end: Label,
pub name: String, pub name: String,
@ -126,18 +126,18 @@ pub struct Method {
} }
impl Method { impl Method {
/// `native`/`abstract` тела не имеют. /// `native`/`abstract` methods have no body.
pub fn has_code(&self) -> bool { pub fn has_code(&self) -> bool {
self.code.is_some() self.code.is_some()
} }
} }
/// Тело метода. /// A method body.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Code { pub struct Code {
pub max_stack: u16, pub max_stack: u16,
pub max_locals: u16, pub max_locals: u16,
/// Маркеры меток + инструкции; ветвления указывают на [`Label`]. /// Label markers + instructions; branches point at a [`Label`].
pub insns: Vec<Insn>, pub insns: Vec<Insn>,
pub exception_table: Vec<ExceptionHandler>, pub exception_table: Vec<ExceptionHandler>,
pub frames: Vec<StackMapFrame>, pub frames: Vec<StackMapFrame>,
@ -145,7 +145,7 @@ pub struct Code {
} }
impl Code { impl Code {
/// Все метки, объявленные в теле метода. /// All labels declared in the method body.
pub fn labels(&self) -> Vec<Label> { pub fn labels(&self) -> Vec<Label> {
self.insns self.insns
.iter() .iter()
@ -163,11 +163,11 @@ pub struct ExceptionHandler {
pub start: Label, pub start: Label,
pub end: Label, pub end: Label,
pub handler: Label, pub handler: Label,
/// Внутреннее имя класса или `None` для `any`. /// The class's internal name, or `None` for `any`.
pub catch_type: Option<String>, pub catch_type: Option<String>,
} }
/// Один `.class` в нормализованном виде. /// One `.class` in normalized form.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct ClassFile { pub struct ClassFile {
pub version: Version, pub version: Version,
@ -181,34 +181,34 @@ pub struct ClassFile {
} }
impl ClassFile { impl ClassFile {
/// Ищет метод по имени и дескриптору. /// Finds a method by name and descriptor.
pub fn method(&self, name: &str, descriptor: &str) -> Option<&Method> { pub fn method(&self, name: &str, descriptor: &str) -> Option<&Method> {
self.methods self.methods
.iter() .iter()
.find(|m| m.name == name && m.descriptor == descriptor) .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> { pub fn method_mut(&mut self, name: &str, descriptor: &str) -> Option<&mut Method> {
self.methods self.methods
.iter_mut() .iter_mut()
.find(|m| m.name == name && m.descriptor == descriptor) .find(|m| m.name == name && m.descriptor == descriptor)
} }
/// Признак `ACC_INTERFACE`. /// The `ACC_INTERFACE` flag.
pub fn is_interface(&self) -> bool { pub fn is_interface(&self) -> bool {
self.access_flags & 0x0200 != 0 self.access_flags & 0x0200 != 0
} }
} }
/// Ресурс внутри архива (JSON, properties, картинки, ...). /// A resource inside the archive (JSON, properties, images, ...).
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Resource { pub struct Resource {
pub path: String, pub path: String,
pub data: Vec<u8>, pub data: Vec<u8>,
} }
/// Вся входная программа: классы + ресурсы. /// The whole input program: classes + resources.
#[derive(Debug, Clone, Default, PartialEq)] #[derive(Debug, Clone, Default, PartialEq)]
pub struct Program { pub struct Program {
pub classes: Vec<ClassFile>, pub classes: Vec<ClassFile>,
@ -216,24 +216,24 @@ pub struct Program {
} }
impl Program { impl Program {
/// Класс по внутреннему имени (`pkg/Class`). /// A class by internal name (`pkg/Class`).
pub fn class(&self, internal_name: &str) -> Option<&ClassFile> { pub fn class(&self, internal_name: &str) -> Option<&ClassFile> {
self.classes.iter().find(|c| c.this_class == internal_name) 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> { pub fn class_mut(&mut self, internal_name: &str) -> Option<&mut ClassFile> {
self.classes self.classes
.iter_mut() .iter_mut()
.find(|c| c.this_class == internal_name) .find(|c| c.this_class == internal_name)
} }
/// Ресурс по пути внутри архива. /// A resource by its path inside the archive.
pub fn resource(&self, path: &str) -> Option<&Resource> { pub fn resource(&self, path: &str) -> Option<&Resource> {
self.resources.iter().find(|r| r.path == path) self.resources.iter().find(|r| r.path == path)
} }
/// Пустая ли программа. /// Whether the program is empty.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.classes.is_empty() && self.resources.is_empty() self.classes.is_empty() && self.resources.is_empty()
} }

View file

@ -1,6 +1,6 @@
//! Версия классового файла (major/minor). //! Class-file version (major/minor).
/// Версия классового файла. /// The class-file version.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Version { pub struct Version {
pub minor: u16, pub minor: u16,

View file

@ -1,49 +1,50 @@
//! Секции конфигурации: стили имён и настройки отдельных пассов. //! Config sections: name styles and per-pass settings.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Стиль генерации имён. /// Name generation style.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum NameStyle { pub enum NameStyle {
/// `a`, `b`, ... `aa` — короткие латинские. /// `a`, `b`, ... `aa` — short Latin.
ShortLatin, ShortLatin,
/// `I`, `l`, `O`, `0` — визуально одинаковые. /// `I`, `l`, `O`, `0` — visually identical.
IlO0, IlO0,
/// Случайные unicode-имена (суррогаты допустимы в modified UTF-8). /// Random Unicode names (surrogates are valid in modified UTF-8).
Unicode, Unicode,
/// Смешанная латиница средней длины. /// Medium-length mixed-case Latin.
#[default] #[default]
MixedLatin, MixedLatin,
/// Псевдосмысленные слова из другого проекта (дезориентация). /// Pseudo-meaningful words from another project (misdirection).
DecoyWords, DecoyWords,
/// Правдоподобные имена: `TextureCache`, `resolveToken` — вместо /// Plausible names: `TextureCache`, `resolveToken` — blends in as
/// явного мусора маскируется под обычный прикладной код. /// ordinary application code instead of obvious noise.
Plausible, Plausible,
} }
/// Настройки переименования. /// Rename pass settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct RenameConfig { pub struct RenameConfig {
/// Переименовывать пакеты в случайные пути. /// Scatter packages into random paths.
pub spread_packages: bool, pub spread_packages: bool,
/// Размер пула случайных пакетов. /// Size of the random package pool.
pub package_pool: usize, pub package_pool: usize,
/// Стиль имён классов. /// Name style for classes.
pub class_style: NameStyle, pub class_style: NameStyle,
/// Стиль имён полей и методов. /// Name style for fields and methods.
pub member_style: NameStyle, pub member_style: NameStyle,
/// Стиль имён локальных переменных и параметров. /// Name style for local variables and parameters.
pub local_style: NameStyle, pub local_style: NameStyle,
/// Делать члены `public` (чтобы не ломать inheritance между пакетами). /// Widen members to `public` (so moving to a new package doesn't break
/// inheritance).
pub widen_access: bool, pub widen_access: bool,
/// Переименовывать enum-константы. /// Rename enum constants.
pub rename_enum_constants: bool, pub rename_enum_constants: bool,
/// Переименовывать локальные переменные и параметры. /// Rename local variables and parameters.
pub rename_locals: bool, pub rename_locals: bool,
/// Переименовывать и не-private члены (агрессивно: безопасно для приложений, /// Also rename non-private members (aggressive: safe for applications,
/// опасно для библиотек и кода с рефлексией). /// dangerous for libraries and reflection-heavy code).
pub rename_public_members: bool, pub rename_public_members: bool,
} }
@ -63,19 +64,20 @@ impl Default for RenameConfig {
} }
} }
/// Настройки шифрования строк. /// String encryption settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct StringsConfig { pub struct StringsConfig {
/// Схемы шифра, между которыми перемешиваются строки. /// Cipher schemes strings are rotated across.
pub schemes: Vec<String>, pub schemes: Vec<String>,
/// Строки короче этого не шифруются. /// Strings shorter than this are not encrypted.
pub min_len: usize, pub min_len: usize,
/// Дробить длинные строки на чанки (меньше похоже на пул). /// Split long strings into chunks (looks less like a string pool).
pub split_above: usize, pub split_above: usize,
/// Не шифровать строки, совпадающие с этими паттернами. /// Don't encrypt strings matching these patterns.
pub skip: Vec<String>, pub skip: Vec<String>,
/// Вычислять ключ из KDF(class, method, index), а не класть рядом с текстом. /// Derive the key from KDF(class, method, index) instead of placing it
/// next to the ciphertext.
pub derive_key: bool, pub derive_key: bool,
} }
@ -91,15 +93,15 @@ impl Default for StringsConfig {
} }
} }
/// Настройки обфускации контрольного потока. /// Control-flow obfuscation settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct FlowConfig { pub struct FlowConfig {
/// Схемы, между которыми выбирается случайно по методу. /// Schemes picked randomly per method.
pub schemes: Vec<String>, pub schemes: Vec<String>,
/// Не трогать методы больше этого числа инструкций (бюджет размера). /// Don't touch methods larger than this many instructions (size budget).
pub max_insns: usize, pub max_insns: usize,
/// Вплетать opaque-предикаты в тела блоков. /// Weave opaque predicates into block bodies.
pub encrypt_block_bodies: bool, pub encrypt_block_bodies: bool,
} }
@ -118,15 +120,15 @@ impl Default for FlowConfig {
} }
} }
/// Настройки шифрования констант. /// Constant encryption settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct ConstantsConfig { pub struct ConstantsConfig {
/// Схемы разбиения (`xor`, `add`, `shift`), выбор по RNG на константу. /// Splitting schemes (`xor`, `add`, `shift`), picked by RNG per constant.
pub schemes: Vec<String>, pub schemes: Vec<String>,
/// Вставлять opaque-предикаты (не больше этого на метод). /// Insert opaque predicates (at most this many per method).
pub max_predicates: usize, pub max_predicates: usize,
/// Не трогать константы в этих классах (префиксы внутренних имён). /// Don't touch constants in these classes (internal-name prefixes).
pub skip: Vec<String>, pub skip: Vec<String>,
} }
@ -140,15 +142,16 @@ impl Default for ConstantsConfig {
} }
} }
/// Настройки шума. /// Noise pass settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct NoiseConfig { pub struct NoiseConfig {
/// Сколько junk-классов на каждый настоящий (0 = выключено). /// How many junk classes per real one (0 = disabled).
pub junk_ratio: usize, pub junk_ratio: usize,
/// Число junk-аннотаций на класс. /// Number of junk annotations per class.
pub junk_annotations: usize, pub junk_annotations: usize,
/// Вплетать junk-классы в граф достижимости (обязательно для осмысленности). /// Weave junk classes into the reachability graph (required for them to
/// make sense).
pub weave_reachable: bool, pub weave_reachable: bool,
} }
@ -162,17 +165,17 @@ impl Default for NoiseConfig {
} }
} }
/// Настройки отладочной информации. /// Debug info pass settings.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, deny_unknown_fields)] #[serde(default, deny_unknown_fields)]
pub struct DebugConfig { pub struct DebugConfig {
/// Удалять `LineNumberTable`. /// Strip `LineNumberTable`.
pub strip_line_numbers: bool, pub strip_line_numbers: bool,
/// Удалять `LocalVariableTable`. /// Strip `LocalVariableTable`.
pub strip_local_variables: bool, pub strip_local_variables: bool,
/// Подменять `SourceFile` на ложное имя. /// Replace `SourceFile` with a fake name.
pub spoof_source_file: bool, pub spoof_source_file: bool,
/// Удалять `Signature`/`InnerClasses`/`EnclosingMethod`. /// Strip `Signature`/`InnerClasses`/`EnclosingMethod`.
pub strip_signatures: bool, pub strip_signatures: bool,
} }

View file

@ -1,14 +1,13 @@
//! Схемы разбиения констант и opaque-предикаты. //! Constant-splitting schemes and opaque predicates.
//! //!
//! Целые константы вне `iconst`-диапазона заменяются тройками (`x^k`, //! Integer constants outside the `iconst` range are replaced by triples
//! `a+b`, сдвиг); opaque-предикат — всегда истинное условие с мёртвой //! (`x^k`, `a+b`, shift); an opaque predicate is an always-true condition with
//! ветвью из одного `nop` (валидно, не исполняется, ломает линейный //! a one-`nop` dead branch (valid, never executed, breaks linear disassembly).
//! дисассемблирование).
use noxis_classfile::{Arith, BranchCond, Cond, Const, Insn, Prim}; use noxis_classfile::{Arith, BranchCond, Cond, Const, Insn, Prim};
use noxis_core::Rng; use noxis_core::Rng;
/// Схема разбиения (имена из `ConstantsConfig::schemes`). /// Splitting scheme (names from `ConstantsConfig::schemes`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Scheme { pub(super) enum Scheme {
Xor, Xor,
@ -27,7 +26,7 @@ impl Scheme {
} }
} }
/// Разбить `int`-константу на вычисляющую её тройку. /// Split an `int` constant into a triple that computes it.
pub(super) fn encrypt_int(value: i32, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> { pub(super) fn encrypt_int(value: i32, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> {
let (first, second) = match scheme { let (first, second) = match scheme {
Scheme::Xor => { Scheme::Xor => {
@ -50,7 +49,7 @@ pub(super) fn encrypt_int(value: i32, scheme: Scheme, rng: &mut Rng) -> Vec<Insn
} }
} }
/// Разбить `long`-константу (те же три схемы на `l*`-опкодах). /// Split a `long` constant (the same three schemes on `l*` opcodes).
pub(super) fn encrypt_long(value: i64, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> { pub(super) fn encrypt_long(value: i64, scheme: Scheme, rng: &mut Rng) -> Vec<Insn> {
let (first, second) = match scheme { let (first, second) = match scheme {
Scheme::Xor => { Scheme::Xor => {
@ -73,7 +72,7 @@ pub(super) fn encrypt_long(value: i64, scheme: Scheme, rng: &mut Rng) -> Vec<Ins
} }
} }
/// `(a, b, op)` как три инструкции. /// `(a, b, op)` as three instructions.
fn binary(first: i32, second: i32, op: Arith, prim: Prim) -> Vec<Insn> { fn binary(first: i32, second: i32, op: Arith, prim: Prim) -> Vec<Insn> {
vec![ vec![
Insn::Const(Const::Int(first)), Insn::Const(Const::Int(first)),
@ -93,7 +92,7 @@ fn binary_long(first: i64, second: i64, op: Arith) -> Vec<Insn> {
] ]
} }
/// `v = ((v >> 1) << 1) | (v & 1)` — работает для любых, включая отрицательные. /// `v = ((v >> 1) << 1) | (v & 1)` — works for any value, including negatives.
fn shift_form(value: i32) -> Vec<Insn> { fn shift_form(value: i32) -> Vec<Insn> {
vec![ vec![
Insn::Const(Const::Int(value >> 1)), Insn::Const(Const::Int(value >> 1)),
@ -110,7 +109,7 @@ fn shift_form(value: i32) -> Vec<Insn> {
] ]
} }
/// То же для `long` (величина сдвига — всегда `int`!). /// The same for `long` (the shift amount is always an `int`!).
fn shift_form_long(value: i64) -> Vec<Insn> { fn shift_form_long(value: i64) -> Vec<Insn> {
vec![ vec![
Insn::Const(Const::Long(value >> 1)), Insn::Const(Const::Long(value >> 1)),
@ -127,13 +126,18 @@ fn shift_form_long(value: i64) -> Vec<Insn> {
] ]
} }
/// Opaque-предикат `(C*C + C) & 1 == 0` (истина для любого `C`): /// Opaque predicate `(C*C + C) & 1 == 0` (true for any `C`).
/// `[pred, ifne DEAD, goto MERGE, DEAD: nop, MERGE:]`, дальше — оригинал.
/// ///
/// Метки выдаёт вызывающий код (свободные номера метода). /// Stack-neutral and leaves control flow unchanged: the computed value is
/// consumed by a branch that never fires; the dead `DEAD: nop` branch only
/// bloats linear disassembly.
///
/// Pattern: `[pred..., ifne DEAD, goto MERGE, DEAD: nop, MERGE:]`.
/// Labels come from the calling code (free method numbers).
pub(super) fn opaque_true(dead: u32, merge: u32, rng: &mut Rng) -> Vec<Insn> { pub(super) fn opaque_true(dead: u32, merge: u32, rng: &mut Rng) -> Vec<Insn> {
let constant = rng.next_u64() as i32; let constant = rng.next_u64() as i32;
vec![ vec![
// P = ((C*C) + C) & 1 — even, so always 0.
Insn::Const(Const::Int(constant)), Insn::Const(Const::Int(constant)),
Insn::Dup, Insn::Dup,
Insn::Binary { Insn::Binary {
@ -150,6 +154,7 @@ pub(super) fn opaque_true(dead: u32, merge: u32, rng: &mut Rng) -> Vec<Insn> {
prim: Prim::Int, prim: Prim::Int,
op: Arith::And, op: Arith::And,
}, },
// P != 0 never holds — the branch into DEAD is not taken, value popped.
Insn::Branch(BranchCond::Zero(Cond::Ne), dead), Insn::Branch(BranchCond::Zero(Cond::Ne), dead),
Insn::Goto(merge), Insn::Goto(merge),
Insn::Mark(dead), Insn::Mark(dead),

View file

@ -1,9 +1,9 @@
//! Алфавиты, ключевые слова и запрещённые имена. //! Alphabets, keywords and forbidden names.
//! //!
//! Списки вынесены отдельно: к ним обращаются и генератор, и пасс //! Kept in their own file because both the generator and the rename pass
//! переименования (резервирование keep-имён). //! reach into them (reserving keep-listed names before generation starts).
/// Жёсткие ключевые слова Java: имя класса/метода не может им совпадать. /// Hard Java keywords: a class/method name can never collide with these.
pub(super) const KEYWORDS: &[&str] = &[ pub(super) const KEYWORDS: &[&str] = &[
"abstract", "abstract",
"assert", "assert",
@ -66,8 +66,8 @@ pub(super) const KEYWORDS: &[&str] = &[
"yield", "yield",
]; ];
/// Имена, которые не стоит затирать: мешают читать/логировать и ловятся /// Names not worth overwriting: they'd hurt readability/logging and get
/// инструментами до любой деобфускации. /// flagged by tooling before any deobfuscation even starts.
pub(super) const AVOID_CLASS: &[&str] = &[ pub(super) const AVOID_CLASS: &[&str] = &[
"Object", "Object",
"String", "String",
@ -101,9 +101,9 @@ pub(super) const AVOID_MEMBER: &[&str] = &[
pub(super) const LATIN_LOWER: &[u8] = b"abcdefghijklmnopqrstuvwxyz"; pub(super) const LATIN_LOWER: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
pub(super) const LATIN_UPPER: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ"; pub(super) const LATIN_UPPER: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/// Визуально неразличимые символы — ядро стиля `IlO0`. /// Visually indistinguishable characters: the core of the `IlO0` style.
pub(super) const CONFUSABLE: &[u8] = b"IlO01"; pub(super) const CONFUSABLE: &[u8] = b"IlO01";
/// Unicode-буквы, валидные как Java-идентификаторы (Cyrillic/Greek/CJK). /// Unicode letters valid in a Java identifier (Cyrillic/Greek/CJK).
pub(super) const UNICODE_LETTERS: &[char] = &[ pub(super) const UNICODE_LETTERS: &[char] = &[
'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'у', 'ф', 'а', 'б', 'в', 'г', 'д', 'е', 'ж', 'з', 'и', 'к', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'у', 'ф',
'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'λ', 'μ', 'π', 'х', 'ц', 'ч', 'ш', 'щ', 'э', 'ю', 'я', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'λ', 'μ', 'π',
@ -118,15 +118,58 @@ pub(super) const DECOY_RIGHT: &[&str] = &[
"ripple", "ripple",
]; ];
/// Стиль `Plausible`: домен-нейтральные существительные для классов/полей — /// `Plausible` style word bank. Mixes domain-neutral software nouns with
/// похожи на обычный прикладной код, не привязаны к конкретному проекту. /// terms that are boring, structural scaffolding in the client/mod
/// ecosystem noxis is actually used on (see
/// `/storage/project/jvm/LoVisual/ref/clients/*`: `Module`, `Setting`,
/// `Event`, `Mixin`, `Manager`, `Component`, `Accessor`, `Hud`, `Overlay`,
/// `Theme`, `Timer`, `Animation`, `Rotation` all show up dozens of times
/// across real client codebases). Deliberately excludes anything that
/// itself reads as a "feature flag" worth an analyst's attention (no
/// `Esp`/`Aimbot`/`KillAura`/`Chams`): a decoy name that announces
/// "interesting code lives here" defeats the point of blending in.
pub(super) const PLAUSIBLE_NOUN: &[&str] = &[ pub(super) const PLAUSIBLE_NOUN: &[&str] = &[
"Texture", "Event", "State", "Render", "Asset", "Config", "Session", "Cache", "Buffer", "Texture",
"Socket", "Stream", "Widget", "Layout", "Theme", "Profile", "Metric", "Token", "Handle", "Event",
"Context", "Registry", "Request", "Response", "Payload", "Resource", "Snapshot", "Pipeline", "State",
"Queue", "Slot", "Frame", "Entry", "Render",
"Asset",
"Config",
"Session",
"Cache",
"Buffer",
"Socket",
"Stream",
"Widget",
"Layout",
"Theme",
"Profile",
"Metric",
"Token",
"Handle",
"Context",
"Registry",
"Request",
"Response",
"Payload",
"Resource",
"Snapshot",
"Pipeline",
"Queue",
"Slot",
"Frame",
"Entry",
"Module",
"Setting",
"Overlay",
"Hud",
"Component",
"Accessor",
"Timer",
"Animation",
"Rotation",
]; ];
/// Суффиксы ролей для классов (`NounSuffix`, напр. `TextureCache`). /// Role suffixes for classes (`NounSuffix`, e.g. `TextureCache`).
pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[ pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[
"Manager", "Manager",
"Handler", "Handler",
@ -148,8 +191,10 @@ pub(super) const PLAUSIBLE_SUFFIX: &[&str] = &[
"Processor", "Processor",
"Context", "Context",
"Listener", "Listener",
"Mixin",
"Component",
]; ];
/// Глаголы для методов/полей (`verbNoun`, напр. `resolveToken`). /// Verbs for methods/fields (`verbNoun`, e.g. `resolveToken`).
pub(super) const PLAUSIBLE_VERB: &[&str] = &[ pub(super) const PLAUSIBLE_VERB: &[&str] = &[
"get", "set", "update", "process", "handle", "compute", "resolve", "load", "build", "parse", "get", "set", "update", "process", "handle", "compute", "resolve", "load", "build", "parse",
"fetch", "apply", "validate", "clear", "reset", "sync", "flush", "bind", "attach", "detach", "fetch", "apply", "validate", "clear", "reset", "sync", "flush", "bind", "attach", "detach",

View file

@ -1,4 +1,4 @@
//! Генерация имён: стили, анти-ключевые слова, уникальность в scope. //! Name generation: styles, anti-keyword checks, uniqueness within a scope.
pub mod alphabet; pub mod alphabet;
#[cfg(test)] #[cfg(test)]
@ -13,18 +13,18 @@ use self::alphabet::{
LATIN_UPPER, PLAUSIBLE_NOUN, PLAUSIBLE_SUFFIX, PLAUSIBLE_VERB, UNICODE_LETTERS, LATIN_UPPER, PLAUSIBLE_NOUN, PLAUSIBLE_SUFFIX, PLAUSIBLE_VERB, UNICODE_LETTERS,
}; };
/// Генератор имён с гарантированной уникальностью в пределах одного scope. /// Name generator with guaranteed uniqueness within a single scope.
pub struct NameGenerator { pub struct NameGenerator {
rng: Rng, rng: Rng,
style: NameStyle, style: NameStyle,
used: HashSet<String>, used: HashSet<String>,
/// Счётчик-страховка, если случайные варианты исчерпаны. /// Fallback counter for when the random pool is exhausted.
fallback: u64, fallback: u64,
avoid_class: bool, avoid_class: bool,
} }
impl NameGenerator { impl NameGenerator {
/// Генератор для указанного стиля. /// Generator for the given style.
pub fn new(rng: Rng, style: NameStyle) -> Self { pub fn new(rng: Rng, style: NameStyle) -> Self {
Self { Self {
rng, rng,
@ -35,18 +35,18 @@ impl NameGenerator {
} }
} }
/// Режим «имена классов»: дополнительно избегаем имён из JDK/аннотаций. /// "Class name" mode: additionally avoid names from the JDK/annotations.
pub fn for_classes(mut self) -> Self { pub fn for_classes(mut self) -> Self {
self.avoid_class = true; self.avoid_class = true;
self self
} }
/// Занять конкретное имя (защита от коллизий с keep-именами). /// Reserve a specific name (protects against collisions with keep-listed names).
pub fn reserve(&mut self, name: impl Into<String>) { pub fn reserve(&mut self, name: impl Into<String>) {
self.used.insert(name.into()); self.used.insert(name.into());
} }
/// Следующее свободное имя. /// The next free name.
pub fn next_name(&mut self) -> String { pub fn next_name(&mut self) -> String {
for _ in 0..64 { for _ in 0..64 {
let candidate = self.candidate(); let candidate = self.candidate();
@ -55,7 +55,7 @@ impl NameGenerator {
return candidate; return candidate;
} }
} }
// Страховка: детерминированный счётчик, гарантированно уникален. // Fallback: a deterministic counter, guaranteed unique.
loop { loop {
self.fallback += 1; self.fallback += 1;
let candidate = format!("{}{}", self.style_prefix(), self.fallback); let candidate = format!("{}{}", self.style_prefix(), self.fallback);
@ -95,7 +95,7 @@ impl NameGenerator {
}; };
let idx = self.rng.below(alpha.len() as u64) as usize; let idx = self.rng.below(alpha.len() as u64) as usize;
let ch = alpha[idx] as char; let ch = alpha[idx] as char;
// Первый символ — только буква (не цифра). // First character must be a letter, not a digit.
if i == 0 && !ch.is_ascii_alphabetic() { if i == 0 && !ch.is_ascii_alphabetic() {
out.push('a'); out.push('a');
} else { } else {
@ -123,9 +123,9 @@ impl NameGenerator {
} }
name name
} }
// Маскируется под обычный прикладной код, а не под мусор: // Blends in as ordinary application code instead of obvious
// классы — `NounSuffix` (`TextureCache`), члены — `verbNoun` // noise: classes get `NounSuffix` (`TextureCache`), members get
// (`resolveToken`) camelCase. // camelCase `verbNoun` (`resolveToken`).
NameStyle::Plausible => { NameStyle::Plausible => {
let noun = PLAUSIBLE_NOUN[self.rng.below(PLAUSIBLE_NOUN.len() as u64) as usize]; let noun = PLAUSIBLE_NOUN[self.rng.below(PLAUSIBLE_NOUN.len() as u64) as usize];
if self.avoid_class { if self.avoid_class {

View file

@ -1,4 +1,4 @@
//! Тесты генератора имён: уникальность, валидность, форма по стилю. //! Name generator tests: uniqueness, validity, per-style shape.
#![allow(clippy::expect_used, clippy::unwrap_used)] #![allow(clippy::expect_used, clippy::unwrap_used)]
@ -15,7 +15,7 @@ fn names_are_unique_and_valid() {
let mut seen = HashSet::new(); let mut seen = HashSet::new();
for _ in 0..500 { for _ in 0..500 {
let name = names.next_name(); let name = names.next_name();
assert!(seen.insert(name.clone()), "повтор имени {name}"); assert!(seen.insert(name.clone()), "duplicate name {name}");
assert!(!KEYWORDS.contains(&name.as_str())); assert!(!KEYWORDS.contains(&name.as_str()));
assert!(name.starts_with(|c: char| c.is_ascii_alphabetic())); assert!(name.starts_with(|c: char| c.is_ascii_alphabetic()));
} }
@ -57,7 +57,7 @@ fn plausible_class_names_look_like_noun_suffix() {
for _ in 0..50 { for _ in 0..50 {
let name = names.next_name(); let name = names.next_name();
assert!(name.starts_with(|c: char| c.is_ascii_uppercase()), "{name}"); assert!(name.starts_with(|c: char| c.is_ascii_uppercase()), "{name}");
// Два слова подряд слиплись в CamelCase без разделителей. // Two words run together in CamelCase with no separator.
assert!( assert!(
name.chars().filter(|c| c.is_ascii_uppercase()).count() >= 2, name.chars().filter(|c| c.is_ascii_uppercase()).count() >= 2,
"{name}" "{name}"

View file

@ -1,5 +1,5 @@
//! Назначение новых имён: карты, семейства виртуальных методов, //! Assigning new names: maps, virtual method families, the package pool,
//! пул пакетов и финальное ширинирование доступа. //! and final access widening.
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
@ -10,11 +10,11 @@ use noxis_core::{Config, Context, NameStyle, Rng};
use super::{ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC, ACC_STATIC}; use super::{ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC, ACC_STATIC};
use crate::namegen::NameGenerator; use crate::namegen::NameGenerator;
/// `(owner, name, descriptor)` — как в JVM-ссылке. /// `(owner, name, descriptor)` — matches a JVM reference.
pub(super) type MemberKey = (String, String, String); pub(super) type MemberKey = (String, String, String);
/// Карты переименований одного прогона: `declared` — где объявлено, /// Rename maps for one run: `declared` is where a member was declared,
/// `alias` — наследники, которые наследуют это имя без своего объявления. /// `alias` is subclasses that inherit the name without declaring it themselves.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(super) struct RenameMaps { pub(super) struct RenameMaps {
pub(super) classes: BTreeMap<String, String>, pub(super) classes: BTreeMap<String, String>,
@ -41,7 +41,7 @@ impl RenameMaps {
.cloned() .cloned()
} }
/// Псевдоними: наследник вызывает член, объявленный в предке. /// Aliases: a subclass calls a member declared on a parent class.
pub(super) fn expand(&mut self, hierarchy: &ClassHierarchy, owners: &[String]) { pub(super) fn expand(&mut self, hierarchy: &ClassHierarchy, owners: &[String]) {
let methods: Vec<(MemberKey, String)> = self let methods: Vec<(MemberKey, String)> = self
.methods .methods
@ -85,8 +85,8 @@ impl RenameMaps {
} }
} }
/// Переименование виртуальных методов «семейством»: одно имя на всю иерархию, /// Rename virtual methods as a "family": one name for the whole hierarchy,
/// включая мостовые методы с ковариантным возвратом. /// including bridge methods with covariant returns.
pub(super) fn rename_virtual_families( pub(super) fn rename_virtual_families(
program: &Program, program: &Program,
analysis: &Analysis, analysis: &Analysis,
@ -94,8 +94,8 @@ pub(super) fn rename_virtual_families(
cfg: &Config, cfg: &Config,
ctx: &mut Context, ctx: &mut Context,
) -> usize { ) -> usize {
// Семейство = (имя, параметры): возврат игнорируем — так захватываем // Family = (name, params): the return type is ignored, so covariant
// ковариантные возвраты и мостовые методы (`ACC_BRIDGE`) разом. // returns and bridge methods (`ACC_BRIDGE`) are captured together.
let mut families: BTreeMap<(String, String), Vec<MemberKey>> = BTreeMap::new(); let mut families: BTreeMap<(String, String), Vec<MemberKey>> = BTreeMap::new();
let mut locked_families: BTreeSet<(String, String)> = BTreeSet::new(); let mut locked_families: BTreeSet<(String, String)> = BTreeSet::new();
@ -149,7 +149,7 @@ pub(super) fn rename_virtual_families(
renamed renamed
} }
/// `(<params>)<ret>` → `(<params>)`, либо `None`, если дескриптор кривой. /// `(<params>)<ret>` → `(<params>)`, or `None` if the descriptor is malformed.
fn params_of(descriptor: &str) -> Option<String> { fn params_of(descriptor: &str) -> Option<String> {
let close = descriptor.find(')')?; let close = descriptor.find(')')?;
if !descriptor.starts_with('(') { if !descriptor.starts_with('(') {
@ -172,7 +172,8 @@ pub(super) fn simple_name(internal: &str) -> String {
} }
} }
/// Пул случайных пакетов (2–3 сегмента); при выключенном разбросе — один пустой. /// Pool of random packages (2-3 segments); a single empty one when spreading
/// is turned off.
pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> { pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
if !cfg.rename.spread_packages { if !cfg.rename.spread_packages {
return vec![String::new()]; return vec![String::new()];
@ -194,7 +195,7 @@ pub(super) fn generate_packages(cfg: &Config, rng: &mut Rng) -> Vec<String> {
.collect() .collect()
} }
/// Переписать дескриптор/сигнатуру: `Lpkg/Class;` → новое имя класса. /// Rewrite a descriptor/signature: `Lpkg/Class;` → the new class name.
pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String { pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>) -> String {
if !desc.contains('L') { if !desc.contains('L') {
return desc.to_string(); return desc.to_string();
@ -220,7 +221,7 @@ pub(super) fn rewrite_descriptor(desc: &str, classes: &BTreeMap<String, String>)
out out
} }
/// Расширить доступ до `public`, чтобы переезд в другой пакет не ломал вызовы. /// Widen access to `public` so moving to a different package doesn't break calls.
pub(super) fn widen_all(program: &mut Program) { pub(super) fn widen_all(program: &mut Program) {
for class in program.classes.iter_mut() { for class in program.classes.iter_mut() {
class.access_flags = widen(class.access_flags); class.access_flags = widen(class.access_flags);

View file

@ -1,7 +1,8 @@
//! Compile-time gate for the project structure rules. //! Compile-time gate for the project structure rules.
//! //!
//! Every crate calls [`check`] from its `build.rs`, so a file that grows past //! Every crate calls [`check`] from its `build.rs`, so a file that grows past
//! 250 lines or a directory that fills up with a fifth file fails the build. //! 250 *code* lines (comments and blank lines excluded) or a directory that
//! fills up with a fifth file fails the build.
mod rules; mod rules;
@ -48,6 +49,26 @@ mod tests {
assert!(found[0].contains("lines >"), "{found:?}"); assert!(found[0].contains("lines >"), "{found:?}");
} }
#[test]
fn ignores_comments_and_blank_lines() {
// A file padded with comment and blank lines stays under the limit:
// only real code counts toward `MAX_FILE_LINES`.
let tmp = TempDir::new("comments");
let mut body = String::new();
for _ in 0..super::MAX_FILE_LINES * 2 {
body.push_str("// a comment line\n");
body.push_str("/// a doc line\n");
body.push_str("//! a module line\n");
body.push('\n');
}
for index in 0..40 {
body.push_str(&format!("pub fn f{index}() {{}}\n"));
}
fs::write(tmp.0.join("lib.rs"), body).expect("write");
let found = violations(&tmp.0);
assert!(found.is_empty(), "{found:?}");
}
#[test] #[test]
fn rejects_directory_over_file_limit() { fn rejects_directory_over_file_limit() {
let tmp = TempDir::new("many"); let tmp = TempDir::new("many");
@ -79,8 +100,8 @@ mod tests {
#[test] #[test]
fn ignores_markdown_size_and_count() { fn ignores_markdown_size_and_count() {
// Правила — только про Rust-код: `.md` не лимитирован ни по // Rules cover Rust code only: `.md` is bounded neither by the line
// строкам, ни в счётчике файлов директории. // limit nor by the per-directory file count.
let tmp = TempDir::new("md"); let tmp = TempDir::new("md");
for index in 0..super::MAX_DIR_FILES + 3 { for index in 0..super::MAX_DIR_FILES + 3 {
fs::write(tmp.0.join(format!("doc{index}.md")), "line\n".repeat(300)).expect("write"); fs::write(tmp.0.join(format!("doc{index}.md")), "line\n".repeat(300)).expect("write");

View file

@ -1,7 +1,8 @@
//! Project structure rules, invoked from every crate's `build.rs`. //! Project structure rules, invoked from every crate's `build.rs`.
//! //!
//! Rules apply to Rust code only, not to documentation: //! Rules apply to Rust code only, not to documentation:
//! * every `.rs` file is at most [`MAX_FILE_LINES`] lines long; //! * every `.rs` file holds at most [`MAX_FILE_LINES`] *code* lines — blank
//! lines and full-line comments (`//`, `///`, `//!`) are not counted;
//! * every directory inside a crate's `src/`, `tests/` or `benches/` holds //! * every directory inside a crate's `src/`, `tests/` or `benches/` holds
//! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited. //! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
//! //!
@ -13,7 +14,10 @@ use std::ffi::OsStr;
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
/// Maximum number of lines in a Rust source file. /// Maximum number of code lines in a Rust source file.
///
/// Comment and blank lines are excluded (see [`count_code_lines`]) so the
/// gate measures real code bulk, not documentation weight.
pub const MAX_FILE_LINES: usize = 250; pub const MAX_FILE_LINES: usize = 250;
/// Maximum number of files per directory; subdirectories are not counted. /// Maximum number of files per directory; subdirectories are not counted.
@ -35,9 +39,9 @@ pub fn check() {
let manifest = manifest_dir(); let manifest = manifest_dir();
let mut violations = Vec::new(); let mut violations = Vec::new();
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы — // `src/` is required; `tests/` and `benches/` are checked only when they
// иначе правило «250 строк» обходится выносом кода в тесты. `docs/` вне // exist — otherwise the line rule is bypassed by moving code into tests.
// гейта: правила — только про Rust-код. // `docs/` stays out of the gate: the rules are about Rust code only.
let roots = [ let roots = [
manifest.join("src"), manifest.join("src"),
manifest.join("tests"), manifest.join("tests"),
@ -124,25 +128,34 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
} }
fn check_line_limit(path: &Path, violations: &mut Vec<String>) { fn check_line_limit(path: &Path, violations: &mut Vec<String>) {
let lines = count_lines(path); let lines = count_code_lines(path);
if lines > MAX_FILE_LINES { if lines > MAX_FILE_LINES {
violations.push(format!( violations.push(format!(
"{}: {lines} lines > {MAX_FILE_LINES}", "{}: {lines} code lines > {MAX_FILE_LINES}",
path.display() path.display()
)); ));
} }
} }
/// Counts lines without requiring the file to be valid UTF-8. /// Counts *code* lines, skipping blanks and full-line comments.
fn count_lines(path: &Path) -> usize { ///
/// A line is code when, after leading whitespace is stripped, it is non-empty
/// and does not start with `//` (which also covers `///` and `//!`). A line
/// that carries code followed by a trailing comment still counts once. Falls
/// back to a raw newline count when the file is not valid UTF-8.
fn count_code_lines(path: &Path) -> usize {
let Ok(text) = fs::read_to_string(path) else {
let Ok(bytes) = fs::read(path) else { let Ok(bytes) = fs::read(path) else {
return 0; return 0;
}; };
if bytes.is_empty() { return bytes.iter().filter(|&&byte| byte == b'\n').count();
return 0; };
} text.lines()
let newlines = bytes.iter().filter(|&&b| b == b'\n').count(); .filter(|line| {
newlines + usize::from(bytes.last() != Some(&b'\n')) let trimmed = line.trim_start();
!trimmed.is_empty() && !trimmed.starts_with("//")
})
.count()
} }
fn manifest_dir() -> PathBuf { fn manifest_dir() -> PathBuf {

View file

@ -25,7 +25,9 @@
искусственно. Не учитываются также `Cargo.lock`, `.gitignore`, корень крейта искусственно. Не учитываются также `Cargo.lock`, `.gitignore`, корень крейта
(в нём всегда лежат `Cargo.toml` + `build.rs`). (в нём всегда лежат `Cargo.toml` + `build.rs`).
1. **Не больше 250 строк** в одном `.rs` файле. 1. **Не больше 250 строк кода** в одном `.rs` файле. Пустые строки и
полно-строчные комментарии (`//`, `///`, `//!`) не считаются —
измеряется объём кода, а не документации.
2. **Не больше 4 `.rs`-файлов** в одной директории. Подпапки не ограничены: 2. **Не больше 4 `.rs`-файлов** в одной директории. Подпапки не ограничены:
`src/` может содержать `lib.rs` и три модуля-директории, каждая из которых `src/` может содержать `lib.rs` и три модуля-директории, каждая из которых
в свою очередь содержит не больше четырёх файлов. в свою очередь содержит не больше четырёх файлов.