ristretto's ConstantPool never deduplicates: every add_* pushes a new entry. HotSpot matches each LocalVariableTypeTable entry to a LocalVariableTable entry by raw name_index, so when a name (e.g. a local's) was interned twice the two tables got different indices and the class failed to load: 'ClassFormatError: LVTT entry ... does not match any LVT entry'. The writer now memoizes every pool builder by its semantic key and rebuilds composite constants (Class/NameAndType/Fieldref/Methodref/MethodHandle/ Dynamic) from our own utf8/class primitives so ristretto never mints a shadow Utf8. This also shrinks the pool (no more ldc -> ldc_w widening that changed code length on a pure round-trip). Split pool.rs into a pool/ module (refs in pool/refs.rs) to respect the file-size gate. Regression test: tests/pool asserts the written pool has no duplicate CONSTANT_Utf8. Verified: pure parse->write of the neoforge jar (92 classes) now loads under java -Xverify:all with 0 errors. |
||
|---|---|---|
| .. | ||
| learnings | ||
| architecture.md | ||
| README.md | ||
| stack.md | ||
| techniques.md | ||
Документация noxis
Обфускатор JVM/Java-байткода на Rust. Этот файл — индекс.
Что где
| файл | что внутри |
|---|---|
architecture.md |
слои, крейты, собственный IR, конвейер пассов, структурные правила |
techniques.md |
как работает каждая техника + плюсы/минусы/цена |
stack.md |
почему свой IR, какой backend, StackMapTable, PRNG, генерация рантайма |
learnings/lessons.md |
уроки из разбора EvoVis: что защита сделала плохо и чего мы не делаем |
learnings/reference.md |
разбор референсного обфускатора (Java/ASM) — что берём, что нет |
learnings/priorities.md |
что улучшим: стратегия и приоритеты |
learnings/ai-resistance.md |
новые техники обфускации и устойчивость к LLM-анализу |
План выполнения — ../todo.md. Исходный материал разбора —
../info/.
Структурные правила проекта
Правила — только про Rust-код: crates/*/src, crates/*/tests,
crates/*/benches. Документация (docs/**, корневые .md) и info/ вне
гейта: писать подробно там, где это документация, а не ограничивать объём
искусственно. Не учитываются также Cargo.lock, .gitignore, корень крейта
(в нём всегда лежат Cargo.toml + build.rs).
- Не больше 250 строк в одном
.rsфайле. - Не больше 4
.rs-файлов в одной директории. Подпапки не ограничены:src/может содержатьlib.rsи три модуля-директории, каждая из которых в свою очередь содержит не больше четырёх файлов.
tests/ и benches/ не исключены намеренно — иначе правило
«250 строк» обходится выносом кода в тесты.
Правило 2 — это не косметика: файл из тысячи строк означает, что в нём смешаны разные причины к изменению (нарушение KISS/DRY/SOLID), а команда из восьми файлов в одной папке — что модуль перестал быть модулем.
Как проверяется
Правила гоняются при компиляции. Каждый крейт имеет build.rs:
fn main() {
noxis_structure_check::check();
}
noxis-structure-check обходит деревья src/, tests/ и benches/ этого
крейта и падает со стопкой ошибок, если лимит нарушен. Поэтому
cargo build не соберёт нарушение — специально запускать проверку не нужно.
Деревья регистрируются через cargo:rerun-if-changed, чтобы гейт не
срабатывал на правки вне проверяемых путей.
Остальное, что не привязано к компиляции, собирается одним скриптом:
./scripts/check.sh # structure → fmt → clippy -D warnings → test
Что делать при нарушении
- файл разрослся → разрезать по одной ответственности на файл:
mod.rs= публичный контракт модуля, соседние файлы — внутренности; - папка переполнилась → завести подпапку и вынести подфункцию целиком, а не «чтобы влезло».
Каждый модуль получает mod.rs с //!-документацией — это точка входа,
читающаяся вместо пяти файлов.