structure-check: gate counts code lines, docs and comments in English
Gate ignores blank and full-line comment lines so the 250-line limit measures code bulk, not documentation weight; docs/README.md synced. Translate code comments to English in classfile, core config, namegen, rename assign, constants forms. Expand Plausible word bank with client-ecosystem terms (Module, Setting, Mixin, ...). cargo fmt clean, check.sh green.
This commit is contained in:
parent
ec28ec82e3
commit
39178420e4
42 changed files with 532 additions and 437 deletions
|
|
@ -1,8 +1,9 @@
|
|||
//! Эмиссия компактных кадров в точках, где их ждёт верификатор.
|
||||
//! Emitting compact frames at the points where the verifier expects them.
|
||||
//!
|
||||
//! Вид кадра выбирается сравнением с предыдущим кадром (`Same`/`Append`/
|
||||
//! `Chop`/`SameLocals`, иначе `Full`) — как у `javac`. Хвостовые `Top`
|
||||
//! при сравнении и записи отрезаются: пустой слот — не тип.
|
||||
//! A frame's kind is chosen by comparing with the previous frame
|
||||
//! (`Same`/`Append`/`Chop`/`SameLocals`, otherwise `Full`) — like `javac`.
|
||||
//! Trailing `Top`s are trimmed both when comparing and when writing: an empty
|
||||
//! slot is not a type.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
|
|
@ -11,10 +12,10 @@ use super::xfer::State;
|
|||
use super::{StackMapFrame, VerificationType};
|
||||
use crate::error::ClassfileError;
|
||||
|
||||
/// Кадры во всех точках `targets` (отсортированы — дельты по порядку).
|
||||
/// Frames at every point in `targets` (sorted — deltas in order).
|
||||
///
|
||||
/// Дельты — в номерах инструкций (как backend после чтения); writer сам
|
||||
/// пересчитает их в байты.
|
||||
/// Deltas are in instruction numbers (like the backend after reading); the writer
|
||||
/// recomputes them into bytes itself.
|
||||
pub(super) fn emit_frames(
|
||||
states: &[Option<State>],
|
||||
targets: &BTreeSet<u32>,
|
||||
|
|
@ -49,8 +50,8 @@ fn select(
|
|||
previous: &[VerificationType],
|
||||
stack: &[VerificationType],
|
||||
) -> StackMapFrame {
|
||||
// Сравнение — на гранулярности переменных (`long` — одна запись,
|
||||
// как у `javac`): иначе половины пар дают `Append{[Top, Long]}`.
|
||||
// Comparison is at variable granularity (`long` — one entry, like `javac`);
|
||||
// otherwise halves would produce `Append{[Top, Long]}`.
|
||||
let current = vars(locals);
|
||||
let base = vars(previous);
|
||||
if current == base {
|
||||
|
|
@ -93,10 +94,10 @@ fn select(
|
|||
}
|
||||
}
|
||||
|
||||
/// Слоты → переменные: `[Long, Top]` — одна запись; хвостовые `Top` — прочь.
|
||||
/// Slots → variables: `[Long, Top]` — one entry; trailing `Top`s — gone.
|
||||
///
|
||||
/// Одиночные `Top` внутри (дыры) сохраняются: в `Full` это легальный тип,
|
||||
/// а в `Append` мы их не пускаем (см. `select`).
|
||||
/// Single `Top`s inside (holes) are kept: in `Full` they are a legal type, and we
|
||||
/// don't allow them in `Append` (see `select`).
|
||||
fn vars(locals: &[VerificationType]) -> Vec<VerificationType> {
|
||||
let mut out = Vec::with_capacity(locals.len());
|
||||
let mut index = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! CFG тела метода: метки, рёбра, точки кадров, диапазоны handler'ов.
|
||||
//! A method body's CFG: labels, edges, frame points, handler ranges.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ use crate::error::ClassfileError;
|
|||
use crate::insn::{Insn, Label};
|
||||
use crate::model::ExceptionHandler;
|
||||
|
||||
/// Метки → номера инструкций без маркеров.
|
||||
/// Labels → instruction numbers, without the markers.
|
||||
pub(super) fn strip_marks<'a>(
|
||||
insns: &'a [Insn],
|
||||
merger: &Merger<'_>,
|
||||
|
|
@ -31,7 +31,7 @@ pub(super) fn strip_marks<'a>(
|
|||
Ok((labels, bare))
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
/// CFG edges without exceptions: fallthrough + branch targets.
|
||||
pub(super) fn successors(
|
||||
insn: &Insn,
|
||||
index: u32,
|
||||
|
|
@ -40,8 +40,8 @@ pub(super) fn successors(
|
|||
merger: &Merger<'_>,
|
||||
) -> Result<Vec<u32>, ClassfileError> {
|
||||
let mut out = Vec::new();
|
||||
// Без продолжения: переходы без возврата и выходы из метода.
|
||||
// `Jsr`/`Ret` сюда не доходят (их отвергает `transfer`), но рёбра есть.
|
||||
// No fallthrough: jumps that don't return and method exits.
|
||||
// `Jsr`/`Ret` never reach here (rejected by `transfer`), but the edges exist.
|
||||
let terminal = matches!(
|
||||
insn,
|
||||
Insn::Goto(..)
|
||||
|
|
@ -67,7 +67,7 @@ pub(super) fn successors(
|
|||
Ok(out)
|
||||
}
|
||||
|
||||
/// Точки, где верификатор требует кадры: цели переходов + входы handler'ов.
|
||||
/// Points where the verifier requires frames: branch targets + handler entries.
|
||||
pub(super) fn frame_targets(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
@ -91,12 +91,12 @@ pub(super) fn frame_targets(
|
|||
Ok(targets)
|
||||
}
|
||||
|
||||
/// Точка handler'а в номерах инструкций: `(start, end, handler, тип перехвата)`.
|
||||
/// A handler point in instruction numbers: `(start, end, handler, catch type)`.
|
||||
///
|
||||
/// Диапазоны полуоткрытые `[start, end)`, как в JVMS.
|
||||
/// Ranges are half-open `[start, end)`, as in the JVMS.
|
||||
pub(super) type HandlerRange = (u32, u32, u32, Option<String>);
|
||||
|
||||
/// Handler'ы в номерах инструкций для рёбер исключений.
|
||||
/// Handlers in instruction numbers, for exception edges.
|
||||
pub(super) fn handler_ranges(
|
||||
table: &[ExceptionHandler],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Разбор дескрипторов типов (`(...)V`, `[L...;`).
|
||||
//! Descriptor parsing (`(...)V`, `[L...;`).
|
||||
|
||||
use super::super::VerificationType;
|
||||
|
||||
/// Ошибка разбора дескриптора (контекст добавляет вызывающий код).
|
||||
/// Descriptor parse error (context is added by the caller).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DescError(pub String);
|
||||
|
||||
/// Уложить слоты значения в locals с позиции; вернуть следующую позицию.
|
||||
/// Write value slots into `locals` at position; return next position.
|
||||
pub(crate) fn write_local(
|
||||
locals: &mut [VerificationType],
|
||||
slot: usize,
|
||||
|
|
@ -22,7 +22,7 @@ pub(crate) fn write_local(
|
|||
Ok(slot + width)
|
||||
}
|
||||
|
||||
/// Разобрать дескриптор массива на (глубина, элемент без `;`).
|
||||
/// Parse an array descriptor into (depth, element without `;`).
|
||||
pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
|
||||
let dims = descriptor.bytes().take_while(|&b| b == b'[').count();
|
||||
if dims == 0 {
|
||||
|
|
@ -31,7 +31,7 @@ pub(crate) fn split_array(descriptor: &str) -> Option<(usize, &str)> {
|
|||
Some((dims, descriptor[dims..].trim_end_matches(';')))
|
||||
}
|
||||
|
||||
/// Слоты одного полевого дескриптора (`long`/`double` — парой с `Top`).
|
||||
/// Slots for a single field descriptor (`long`/`double` — pair with `Top`).
|
||||
pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, DescError> {
|
||||
let mut chars = descriptor.chars();
|
||||
match chars.next() {
|
||||
|
|
@ -57,10 +57,10 @@ pub(crate) fn field_slots(descriptor: &str) -> Result<Vec<VerificationType>, Des
|
|||
}
|
||||
}
|
||||
|
||||
/// Слоты параметров и возврата (`None` — `void`).
|
||||
/// Parameter and return slots (`None` — `void`).
|
||||
type MethodSlots = (Vec<Vec<VerificationType>>, Option<Vec<VerificationType>>);
|
||||
|
||||
/// Дескриптор метода: слоты параметров и слоты возврата (`None` — `void`).
|
||||
/// Method descriptor: parameter slots and return slots (`None` — `void`).
|
||||
pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
|
||||
let (params, ret) = descriptor
|
||||
.split_once('(')
|
||||
|
|
@ -128,7 +128,7 @@ pub(crate) fn method_slots(descriptor: &str) -> Result<MethodSlots, DescError> {
|
|||
Ok((out, ret))
|
||||
}
|
||||
|
||||
/// Тип элемента по дескриптору массива в стеке.
|
||||
/// Element type from an array descriptor on the stack.
|
||||
pub(crate) fn array_element(
|
||||
array: &VerificationType,
|
||||
kind: crate::insn::ArrayKind,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! Слияние типов (LUB) и разбор дескрипторов.
|
||||
//! Type merging (LUB) and descriptor parsing.
|
||||
//!
|
||||
//! Решётка: `Top` — «пусто» (берётся сосед), `Null` впитывается в ссылку,
|
||||
//! одинаковые — сами в себя, ссылки — наименьший общий предок по иерархии.
|
||||
//! Несочетаемые инициализированные типы (`int` vs ссылка после `astore`
|
||||
//! в handler'е) дают `Top`, как обрезанные кадры `javac`: `Top` в кадре
|
||||
//! принимает любое фактическое значение, поэтому это всегда sound.
|
||||
//! Исключение — неинициализированные типы: их слияние означает сломанный
|
||||
//! поток конструирования, здесь честная ошибка, а не молчаливый `Top`.
|
||||
//! The lattice: `Top` is "empty" (the neighbour wins), `Null` is absorbed into a
|
||||
//! reference, equal types map to themselves, references take the least upper
|
||||
//! bound over the hierarchy. Incompatible initialized types (`int` vs a reference
|
||||
//! after `astore` in a handler) yield `Top`, like `javac`'s truncated frames: a
|
||||
//! `Top` in a frame accepts any actual value, so this is always sound. The
|
||||
//! exception is uninitialized types: merging them means a broken construction
|
||||
//! flow — here an honest error, not a silent `Top`.
|
||||
|
||||
pub(super) mod descs;
|
||||
|
||||
|
|
@ -17,13 +17,13 @@ use crate::model::Method;
|
|||
|
||||
use descs::write_local;
|
||||
|
||||
/// Иерархия классов для LUB: достаточно цепочки суперклассов.
|
||||
/// The class hierarchy for LUB: the superclass chain is enough.
|
||||
pub trait Hierarchy {
|
||||
/// Прямой суперкласс (`None` — `java/lang/Object` или неизвестный).
|
||||
/// The direct superclass (`None` — `java/lang/Object` or unknown).
|
||||
fn superclass(&self, class: &str) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Контекст слияния: имена для ошибок + иерархия для LCA.
|
||||
/// Merge context: names for errors + hierarchy for LCA.
|
||||
pub(super) struct Merger<'h> {
|
||||
pub class: String,
|
||||
pub method: String,
|
||||
|
|
@ -38,7 +38,7 @@ impl Merger<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Влить входящее состояние; `true` — слот изменился.
|
||||
/// Merge an incoming state; `true` — the slot changed.
|
||||
pub fn merge_into(
|
||||
&self,
|
||||
slot: &mut Option<State>,
|
||||
|
|
@ -61,7 +61,7 @@ impl Merger<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Начальное состояние: параметры (+ `this`) поверх `Top`.
|
||||
/// The initial state: parameters (+ `this`) over `Top`.
|
||||
pub fn initial(&self, method: &Method, max_locals: u16) -> Result<State, ClassfileError> {
|
||||
let is_static = method.access_flags & 0x0008 != 0;
|
||||
let (params, _) =
|
||||
|
|
@ -127,19 +127,19 @@ impl Merger<'_> {
|
|||
Ok(first.clone())
|
||||
}
|
||||
(Object(a), Object(b)) => Ok(Object(self.common_superclass(a, b))),
|
||||
// Одинаковые уже разобраны выше; здесь неинициализированные
|
||||
// всегда разные — это сломанный поток конструирования.
|
||||
// Equal ones are handled above; here uninitialized are always
|
||||
// different — that's a broken construction flow.
|
||||
(VerificationType::UninitializedThis | VerificationType::Uninitialized(_), _)
|
||||
| (_, VerificationType::UninitializedThis | VerificationType::Uninitialized(_)) => {
|
||||
Err(self.invalid("слияние неинициализированных типов"))
|
||||
}
|
||||
// Несочетаемые инициализированные (например, `int` из тела `try`
|
||||
// и `Throwable` из handler'а в одном слоте): `Top`, как у `javac`.
|
||||
// Incompatible initialized (e.g. `int` from the `try` body and
|
||||
// `Throwable` from a handler in one slot): `Top`, like `javac`.
|
||||
_ => Ok(Top),
|
||||
}
|
||||
}
|
||||
|
||||
/// Наименьший общий предок: подъём по цепочкам, запасной — `Object`.
|
||||
/// Least common ancestor: climb the chains, fallback — `Object`.
|
||||
fn common_superclass(&self, first: &str, second: &str) -> String {
|
||||
if first == second {
|
||||
return first.to_string();
|
||||
|
|
@ -176,7 +176,7 @@ impl Merger<'_> {
|
|||
"java/lang/Object".to_string()
|
||||
}
|
||||
|
||||
/// LCA массивов: та же размерность + LCA элементов, иначе `Object`.
|
||||
/// Arrays' LCA: same dimensionality + element LCA, otherwise `Object`.
|
||||
fn array_lca(&self, first: &str, second: &str) -> String {
|
||||
let (dims_a, elem_a) = descs::split_array(first).unwrap_or((0, first));
|
||||
let (dims_b, elem_b) = descs::split_array(second).unwrap_or((0, second));
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
//! `StackMapTable`: представление фреймов и вычисление после трансформаций.
|
||||
//! `StackMapTable`: frame representation and computation after transformations.
|
||||
//!
|
||||
//! Фреймы — самая дорогая часть любого обфускатора на Rust: JVM-верификатор
|
||||
//! (class version ≥ 50) требует точного `StackMapTable`, иначе класс не грузится.
|
||||
//! Варианты реализации и их цены — `docs/stack.md`.
|
||||
//! Frames are the most expensive part of any Rust obfuscator: the JVM verifier
|
||||
//! (class version ≥ 50) demands an exact `StackMapTable`, or the class won't load.
|
||||
//! Implementation options and their costs — `docs/stack.md`.
|
||||
//!
|
||||
//! Метод: CFG по меткам → worklist с типами (`xfer`) → слияние LUB (`merge`)
|
||||
//! → эмиссия компактных кадров (`emit`). Байтовые смещения берутся тем же
|
||||
//! преобразованием, что использует writer, — иначе кадры разъедутся с байтами.
|
||||
//! Method: CFG over labels → worklist with types (`xfer`) → LUB merge (`merge`)
|
||||
//! → emit compact frames (`emit`). Byte offsets come from the same encoding the
|
||||
//! writer uses — otherwise frames would drift apart from the bytes.
|
||||
|
||||
mod emit;
|
||||
mod flow;
|
||||
|
|
@ -27,7 +27,7 @@ pub use merge::Hierarchy;
|
|||
use merge::Merger;
|
||||
use xfer::{State, transfer};
|
||||
|
||||
/// Тип верификации из JVMS §4.10.1.
|
||||
/// A verification type from JVMS §4.10.1.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum VerificationType {
|
||||
Top,
|
||||
|
|
@ -37,14 +37,14 @@ pub enum VerificationType {
|
|||
Long,
|
||||
Null,
|
||||
UninitializedThis,
|
||||
/// Внутреннее имя класса (`java/lang/Object`) или дескриптор массива.
|
||||
/// The class's internal name (`java/lang/Object`) or an array descriptor.
|
||||
Object(String),
|
||||
/// Байтовое смещение инструкции `new`.
|
||||
/// The byte offset of the `new` instruction.
|
||||
Uninitialized(u16),
|
||||
}
|
||||
|
||||
impl VerificationType {
|
||||
/// Тип, соответствующий примитиву.
|
||||
/// The type matching a primitive.
|
||||
pub fn of_prim(p: Prim) -> Self {
|
||||
match p {
|
||||
Prim::Int => VerificationType::Integer,
|
||||
|
|
@ -55,7 +55,7 @@ impl VerificationType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Запись `StackMapTable` (JVMS §4.7.4).
|
||||
/// A `StackMapTable` entry (JVMS §4.7.4).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StackMapFrame {
|
||||
Same {
|
||||
|
|
@ -80,10 +80,11 @@ pub enum StackMapFrame {
|
|||
},
|
||||
}
|
||||
|
||||
/// Вычислить фреймы для тела метода после всех трансформаций.
|
||||
/// Compute the frames for a method body after all transformations.
|
||||
///
|
||||
/// Кадры нужны только классам версии ≥ 50; `jsr`/`ret` отвергаются (такие
|
||||
/// классы старше и кадры им не нужны). Иерархия — для LUB ссылочных типов.
|
||||
/// Frames are needed only for version ≥ 50 classes; `jsr`/`ret` are rejected
|
||||
/// (such classes are older and need no frames). The hierarchy is for the LUB of
|
||||
/// reference types.
|
||||
pub fn compute_frames(
|
||||
class: &str,
|
||||
method: &Method,
|
||||
|
|
@ -112,7 +113,7 @@ pub fn compute_frames(
|
|||
emit::emit_frames(&states, &targets, &initial.locals, &merger)
|
||||
}
|
||||
|
||||
/// Байтовое смещение каждой инструкции — тем же кодированием, что в writer'е.
|
||||
/// The byte offset of each instruction — using the same encoding as the writer.
|
||||
fn layout(
|
||||
bare: &[&Insn],
|
||||
labels: &BTreeMap<Label, u32>,
|
||||
|
|
@ -144,7 +145,7 @@ fn layout(
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// Основной цикл: состояния текут по рёбрам CFG до неподвижной точки.
|
||||
/// Main loop: states flow along CFG edges to a fixed point.
|
||||
fn run_worklist(
|
||||
merger: &Merger<'_>,
|
||||
bare: &[&Insn],
|
||||
|
|
@ -187,7 +188,7 @@ fn run_worklist(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Влить состояние в инструкцию; `true` — изменилось, продолжить обход.
|
||||
/// Merge a state into an instruction; `true` — changed, keep walking.
|
||||
fn flow_into(
|
||||
merger: &Merger<'_>,
|
||||
states: &mut [Option<State>],
|
||||
|
|
@ -218,8 +219,8 @@ fn flow_into(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Рёбра CFG без исключений: продолжение + цели переходов.
|
||||
/// Тип исключения на входе в handler (`catch-all` — любое throwable).
|
||||
/// CFG edges without exceptions: fallthrough + branch targets.
|
||||
/// The exception type at a handler's entry (`catch-all` — any throwable).
|
||||
fn catch_type(catch: &Option<String>) -> VerificationType {
|
||||
match catch {
|
||||
Some(name) => VerificationType::Object(name.clone()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue