Commit graph

18 commits

Author SHA1 Message Date
a4450b69ba
classfile: fix condy ldc category (ldc_w vs ldc2_w by descriptor), split attrs tests 2026-10-15 12:53:22 +02:00
d19e4f784d
deps: drop regex, toml, indexmap - std-only minirx, toml_lite, inferred IndexMap 2026-10-15 12:53:22 +02:00
15cd31252e
classfile,passes: model RuntimeVisibleTypeAnnotations and AnnotationDefault
Type annotations (method- and code-level) and annotation defaults were
passed through as Raw bytes with the original pool indices; the writer
rebuilds the pool, so those indices pointed at the wrong utf8 or past
the pool end. HotSpot tolerates the garbage (lazy reflection parsing),
but ASM's ClassReader dies with ArrayIndexOutOfBoundsException while
Paper's plugin converter reads every method - 76 of 275 LoParkour
classes failed to convert. Target_info and type_path carry no pool
references and stay opaque bytes; type descriptors and element values
are modeled and rewritten by rename like plain annotations.
2026-10-13 10:17:10 +02:00
90506a4736
passes: rename mixin classes + patch mixins.json/refmap like OBF
- Remove absorb_mixins keep rule (mixin classes now renameable)
- Add mixins.rs: patch_mixins_json rewrites package + entries via
  common prefix of mapped names; patch_refmap_json rewrites all strings
- Resources pass routes *.mixins.json and *refmap.json to specialized
  patchers, falls back to generic text remap

Reference: OBF fabric-obfuscator patchMixinsJson/patchRefmapJson.
2026-10-12 14:18:19 +02:00
b73c72d56d
classfile: model RuntimeVisible/InvisibleParameterAnnotations (fixes ASM AIOOBE on write)
Parameter annotations were unmodeled and fell through to Raw with stale
constant-pool indices after the pool rebuild. ASM (Paper converter, Fabric
mixin transformer) then failed with ArrayIndexOutOfBoundsException in
readParameterAnnotations. Now parsed and rewritten like class-level
annotations.
2026-10-12 13:19:12 +02:00
48a5bc23a9
classfile,passes: model runtime-visible annotations, rewrite their descriptors 2026-10-11 13:02:26 +02:00
4b14eeb40f
classfile: writer honors itf flag for invokestatic on interfaces 2026-10-11 12:24:50 +02:00
9a33cef9b1
classfile,passes,jar: model PermittedSubclasses, normalize interface flags, name class in write errors 2026-10-10 14:22:03 +02:00
63630dd223
frames: JDK hierarchy fallback, array-operand rewrite, aaload fixes; LoVisual 131 to 0 FAIL 2026-10-10 13:00:58 +02:00
1c417f596c
classfile: aaload on 1-dim ref arrays yields element type; passes: minimizer example 2026-10-10 11:47:06 +02:00
2cbb9db31e
passes: junk classes with real bodies and no fingerprint
NoisePass now builds branching helper methods (counted int loop,
random arithmetic, even/odd merge, non-throwing ops only) whose
results the entry sums into a private static field, instead of an
empty run()V. Names come from the same NameGenerator styles as
real code and live in a random real package (no more shadow/decoy
uniformity for grep to catch); every generated method gets fresh
StackMapTable frames. weave_first_reachable takes (class, entry)
pairs. New NoiseConfig.junk_methods_per_class (default 3).

Tests assert invariants (unique valid identifiers, calls, field
store, determinism) plus real-JVM verification of junk classes;
paranoid+junk_ratio=1 smoke on lorep: 18 junk classes, all load.
2026-10-09 14:04:24 +02:00
139aea9a8f
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.
2026-10-09 13:47:27 +02:00
2172206e83
passes: implement control-flow pass (4 schemes + rotation)
FlowPass rebuilds one method per scheme picked from FlowConfig:
state-spread (dense tableswitch dispatcher), computed-switch
(sparse lookupswitch over a counter local), butterfly (branch
inversion), exception-goto (dispatcher under try with a dead
catch-all), plus opaque goto-splices inside every method and
StackMapTable recomputation afterwards. Now in standard_pipeline
(between constants and noise); --no-flow is an honest off-flag.

Acceptance is real JVM, not shapes: per-scheme verify, rotation
spread, calc-prints-45 execution, paranoid smoke on lorep-1.1.0
(131 methods rebuilt, no new failures over standard).

Two verifier rules learned the hard way are now construction
invariants: no inserted mark shares a bytecode offset with a
foreign one (nop pads), and no frameless gap follows a terminal
(dead blocks are opaque-entered and throw).

Also fixes a latent frame-emitter bug the pass exposed: wide
stack values were written as slot pairs ([Double, Top]) while
javac writes one entry ([Double]); HotSpot rejects the pair.
Stacks now collapse like locals (emit::stack_vars), with a
regression test in classfile/tests/frames.rs.
2026-10-09 11:11:55 +02:00
39178420e4
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.
2026-10-09 10:31:02 +02:00
f08bf60635 classfile: intern constant-pool entries on write (fix LVTT/LVT ClassFormatError)
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.
2026-10-08 22:55:49 +02:00
a2e4c69210 classfile,core: pick frame compact form by byte delta; honor --no-* flags
StackMapTable compact-vs-extended selection used the instruction delta,
but JVM validity requires the byte offset delta <= 63. Track byte offsets
in convert_frames and branch on byte_delta while keeping the instruction
delta stored (ristretto re-derives bytes).

Pipeline::run ignored config.disable, so --no-* CLI flags had no effect;
gate each pass on config.is_enabled.
2026-10-08 22:39:22 +02:00
2674d7fc89 classfile,passes: model Nest attributes as IR (fix ClassFormatError)
NestHost/NestMembers were carried as Attribute::Raw with constant-pool
indices baked in. During writing the pool is rebuilt in a different order,
so those indices pointed at the wrong entries and the JVM rejected the
class: ClassFormatError: Nest member/host class_info_index N has bad
constant type.

Model them explicitly in the IR, convert on parse and write (resolving
class names through the new pool), and remap their class names during
rename. Add a NestMembers round-trip regression test. Verified on a real
jar: all Nest ClassFormatErrors gone.
2026-10-08 22:26:58 +02:00
a641e69455 Initial commit: noxis JVM bytecode obfuscator
Workspace of 9 crates (noxis, noxis-core, noxis-classfile, noxis-analysis,
noxis-passes, noxis-runtime, noxis-jar, noxis-verify, noxis-structure-check).
AGPL-3.0-only license, Rust 2024 edition, unsafe_code denied.
2026-10-08 22:01:03 +02:00