frames,jar,passes: verifier-driven hardening from real jars

Proven by paranoid runs over lorep (17 classes, fully green),
LoVisual (4081 classes) and oc2r (1278 classes) with per-class
java -Xverify:all, each fix first reproduced as a red test:

- merge: Top absorbs instead of Top-wins (javac declares Top for
  slots unwritten on some inflow; precise types reject those edges).
  Regression: loop_head_keeps_top_for_loop_local.
- emit: collapse [Long|Double, Top] pairs on operand stacks
  (javac writes one entry; the pair is rejected). Regression:
  wide_stack_join_emits_single_entries.
- xfer ArrayLoad: push the wide pair, fixing lcmp underflow.
  Regression: array_load_wide_pushes_pair.
- xfer <init>: convert only the receiver's own allocation site
  instead of every Uninitialized (nested construction clobbered
  the outer object). Regression: nested_construction_keeps_outer.
- xfer ANewArray: array operands gain one dimension ([B] -> [[B)
  instead of an illegal L-wrap ([L[B;). Covered by the same test.
- jar load: duplicate class names (multi-release module-info
  twins) keep the first occurrence instead of crashing save.
  Test: duplicate_class_names_keep_first.
- strings/constants: skip methods past MAX_METHOD_INSNS (8000)
  so giants cannot overflow the 64KB writer ceiling. Tests:
  oversized_methods_are_skipped in both passes.
- merge errors now name both types being merged.

Also: em-dash removed repo-wide (724 spots, rule added to
todo.md), allow-lints only in tests (scratch example deleted),
code comments migrated to English in every file this change
touches.
This commit is contained in:
Loki 2026-10-09 13:47:27 +02:00
parent 2172206e83
commit 139aea9a8f
Signed by: boba
GPG key ID: 253067914055423B
96 changed files with 1053 additions and 831 deletions

View file

@ -37,7 +37,7 @@
| `DebugInfoStripper` | 69 | strip отладки | пасс `debug` |
| `StringFilter` | 35 | что считать строкой | функция внутри `strings` |
**Вывод:** структура совпадает с нашей почти один в один — это подтверждает
**Вывод:** структура совпадает с нашей почти один в один - это подтверждает
раскладку крейтов. Ключевое отличие: у них всё живёт в одном jar'е и
одном проекте; у нас конвейер отделён от I/O и от CLI.
@ -47,11 +47,11 @@
У них: remap → strings → numbers → flow → junk → debug. Тот же инвариант,
что и у нас: **переименование раньше всех техник, порождающих новые ссылки**,
а `debug` — последним.
а `debug` - последним.
### 3.2 Наследование как условие безопасности
`InheritanceAnalyzer` — самый большой после remap'ера модуль. Совпадает с
`InheritanceAnalyzer` - самый большой после remap'ера модуль. Совпадает с
нашим выводом: переименование virtual-методов **без** иерархии ломает
`invokevirtual`. У нас это `hierarchy` + семейства в `rename`.
@ -63,13 +63,13 @@
### 3.4 Подпись срезается
`*.SF/*.RSA/*.DSA/*.EC` удаляются при перепаковке — после записи подпись
`*.SF/*.RSA/*.DSA/*.EC` удаляются при перепаковке - после записи подпись
всё равно невалидна, а её наличие заставляет JVM падать. Это входит в
`noxis-jar`.
### 3.5 Маппинг в файл + ложные ограничения
`--mappings` пишет соответствие «новое → старое» — без него автор сам не
`--mappings` пишет соответствие «новое → старое» - без него автор сам не
читает stack trace. Плюс честные off-ключи: `--no-strings`, `--no-flow`,
`--no-junk`, `--keep-lines`, `--keep-source`, `--keep-vars`, `--no-widen`.
@ -78,7 +78,7 @@
### 3.6 Seed
`--seed` — детерминированный прогон. У нас это базовое свойство
`--seed` - детерминированный прогон. У нас это базовое свойство
`noxis-core`, без отдельного флага.
## 4. Что не берём
@ -86,24 +86,24 @@
### 4.1 Java + ASM
Главное отличие стека. Пара Java+ASM даёт родную JVM, готовые visitor'ы и
`CheckClassAdapter` — но и:
`CheckClassAdapter` - но и:
- нужно раскатывать JDK для сборки и запуска тестов;
- наш проект — Rust (`deny(unsafe_code)`, свои лимиты, свои гейты);
- наш проект - Rust (`deny(unsafe_code)`, свои лимиты, свои гейты);
- отсутствует возможность отдавать один бинарь.
Цена, которую мы платим: `StackMapTable` придётся считать самим (через
inference-верификатор — [`../stack.md`](../stack.md) §3). Это осознанно.
inference-верификатор - [`../stack.md`](../stack.md) §3). Это осознанно.
### 4.2 Swing GUI
Лишняя точка входа: GUI дублирует CLI и удваивает площадь тестирования.
У нас — только clap.
У нас - только clap.
### 4.3 `StringDecoderGenerator` как «генератор текста»
Они генерируют Java-исходник декодера и компилируют. Для нас это означало бы
зависимость от `javac` на этапе обфускации. Наш путь — генерировать
зависимость от `javac` на этапе обфускации. Наш путь - генерировать
**байткод напрямую** через наш IR (`noxis-runtime` держит схемы, генератор
собирает `ClassFile`).
@ -120,14 +120,14 @@ inference-верификатор — [`../stack.md`](../stack.md) §3). Это
## 5. Что проверяем на их smoke-тестах
В `target/` референса лежат готовые проверочные кейсы — их стоит
В `target/` референса лежат готовые проверочные кейсы - их стоит
переиспользовать как наш regression-набор:
| кейс | что ломает |
|---|---|
| `smoke-safe` | базовый round-trip, `Sample`/`MyMixin` |
| `smoke-inheritance` | `Base`/`Child`/`InheritedChild`/`ExternalChild` — семейства virtual и мосты |
| `smoke-flow` | `LoadAll` — загрузка всех классов, ловит `VerifyError` |
| `smoke-inheritance` | `Base`/`Child`/`InheritedChild`/`ExternalChild` - семейства virtual и мосты |
| `smoke-flow` | `LoadAll` - загрузка всех классов, ловит `VerifyError` |
| `smoke-fields` | переименование полей и статических полей |
| `smoke-aes` | шифрование строк, `RunStrings` сверяет расшифровку |
| `Probe.java` | точечная проверка одной гипотезы |
@ -144,7 +144,7 @@ inference-верификатор — [`../stack.md`](../stack.md) §3). Это
| keep-сканер | генератор Java-исходника |
| срез подписей, патч ресурсов | единственная схема шифра |
| маппинг в отчёт, честные off-ключи | эмпирическая верификация flow |
| их smoke-кейсы как regression | — |
| их smoke-кейсы как regression | - |
Полный перечень провалов защиты, которую они **не** закрывают, — в
Полный перечень провалов защиты, которую они **не** закрывают, - в
[`lessons.md`](lessons.md).