Skip to content

Latest commit

 

History

History
165 lines (127 loc) · 12.7 KB

File metadata and controls

165 lines (127 loc) · 12.7 KB

AGENTS.md

This file is the working guide for coding agents in this repository. Read it before changing code. Keep it accurate when architecture, commands, or conventions change.

Project purpose

java-asm is a Rust 2024 workspace for reading, writing, inspecting, and presenting Java-family bytecode. It aims to provide an ASM-tree-like Rust API while supporting current JVM class-file formats (Java 21 is the documented target) and Android DEX/APK input.

The main data flow is:

class bytes -> JVMS raw structures -> ClassNode -> Smali-like presentation
DEX/APK bytes -> DEX raw/index structures -> resolved elements -> Smali-like presentation
                                                    -> server state -> egui UI

The parser core deliberately avoids runtime parsing dependencies. Repetitive binary read/write implementations are generated by the local proc-macro crate.

Workspace map

  • asm/ (java_asm): core library. Contains the public JVMS, node, DEX, opcode/constant, reference, and Smali APIs.
    • src/jvms/: class-file structures and public reader/writer entry points.
    • src/dex/: DEX raw structures, instructions, lazy/index-based access, and resolved elements.
    • src/node/: higher-level ASM-tree-like model and instruction nodes.
    • src/smali.rs: presentation tree/token types shared by JVM and DEX paths.
    • src/impls/: internal read/write/transform implementations. Keep implementation details here unless they are intentionally public API.
    • tests/: sample-backed integration tests for JVMS, node conversion, and DEX.
  • asm_macro/ (java_asm_macro): derives ReadFrom/WriteInto and constant-container helpers. Change this when binary-layout boilerplate should be generated consistently.
  • asm_server/ (java_asm_server): APK loading, lazy content access, R8/ProGuard mapping, fuzzy search, async/native-WASM task abstraction, and frontend-independent UI state/messages.
    • src/mapping.rs: mapping parsing and presentation-time class/member/line lookup. Accessors continue to use raw bytecode names.
    • src/targets/: target-specific runtime adapters under the native/ and wasm/ directories. Keep target dispatch in mod.rs; shared APK indexing lives in impls/apk_load.rs.
  • asm_cli/ (java_asm_cli): native Agent-facing CLI for finding classes with basic member structure, locating nested archive entries through internal_path, and exporting one or many classes as Smali. It does not create an AppContainer or provide MCP transport.
  • asm_egui/ (java_asm_egui): current desktop/experimental WASM egui frontend. UI code should consume asm_server state instead of reimplementing parsing.
    • index.html and Trunk.toml: browser shell and Trunk build configuration.
  • ta/: experimental Tauri/Preact frontend. It is currently excluded from Cargo workspace members; do not assume root Cargo commands build it.
  • asm/tests/res/: checked-in binary fixtures. CompileTesting.java is the source of the class fixture; compile_source.bat regenerates it on Windows.
  • .github/workflows/rust.yml: CI builds and tests the Cargo workspace on Ubuntu.

Architectural boundaries

  1. Preserve the distinction between wire-format structures and ergonomic structures.
    • JVMS/DEX raw structs should mirror specification field order, widths, counts, offsets, and terminology.
    • Resolve indexes/references and normalize data in transforms, accessors, or node conversion code.
    • Do not hide raw-format facts inside GUI code.
  2. Keep public modules thin. Public readers/writers collect inputs and delegate to ReadContext, WriteContext, transforms, or private machinery under asm/src/impls/. Expose only stable domain concepts.
  3. Reuse AsmResult<T> and add a specific AsmErr variant when an error category is meaningful. Propagate recoverable parse/I/O failures with ?; avoid new unwrap() calls in library paths.
  4. Preserve lazy DEX access. Retain offsets and indexes until callers request resolved class data, code, or Smali.
  5. Keep frontend-independent state and data preparation in asm_server; keep egui layout and interaction in asm_egui. A UI interaction that only changes presentation state, such as find navigation or scrolling, should be handled directly in the UI layer during the current frame.
  6. Use UIMessage only when an action must cross into server-owned behavior. Do not introduce event queues, result containers, pending flags, synthetic IDs, or pass-through return values when the UI can derive the result from existing state.
  7. Keep durable facts as state and derive transient presentation from them. For example, render a toast from its stored kind, message, and creation time instead of mirroring it into separate frontend state.
  8. Put native/WASM differences behind asm_server::targets; keep shared APK indexing in impls/apk_load.rs. Browser code must use the public java_asm_server re-exports for Instant, SystemTime, and Duration, plus the existing scheduling helpers. Do not add direct web-time dependencies or target checks to parsing, state, or frontend code.
  9. Keep mapping as a server-owned presentation transform. MappedName::raw_name is the stable class/DEX lookup key, while display_name initially matches it and may be replaced by imported mapping data for Smali, search, tabs, and the file tree.

Coding style observed in this repository

  • Write source code, comments, log messages, and repository documentation in English. Keep non-English text only when it is intentionally required for localization or CJK/font regression tests (for example, Log / 日志).
  • Prefer the simplest design that gives each piece of data and behavior one clear owner. Remove redundant states, conversions, wrappers, branches, and forwarding layers instead of explaining them.
  • Prefer direct, explicit Rust over elaborate abstractions. Add a struct, enum, trait, queue, callback, or helper only when it represents a real domain concept or removes meaningful repetition.
  • Keep control and data flow short. Avoid returning a value only to pass it through several functions, cloning data to detect changes, or tracking information that can be derived cheaply from current state.
  • Be efficient by default: avoid unnecessary allocation, cloning, locking, rescanning, and repainting. Keep lock scopes narrow, but do not add coordination machinery whose complexity costs more than the work it saves.
  • Model external specifications literally. Names like constant_pool_count, class_data_off, U32BasedSize, and instruction-format types are preferred over renamed business terminology.
  • Use newtypes/type aliases to communicate binary meaning and shared ownership (StrRef, DescriptorRef, InternalNameRef, DUInt, ArcVarOpt<T>).
  • Use pub use at domain boundaries for the intended convenient API, while leaving implementation modules private or pub(crate).
  • Derive Clone, Debug, Eq, PartialEq, Default, ReadFrom, or WriteInto where they remove mechanical code.
  • Add comments for specification rules, non-obvious invariants, index/offset semantics, endianness, ownership, and safe-unwrapping arguments. Avoid comments that merely restate an obvious expression.
  • Early returns and let Some(value) = ... else { return; }; are favored for guard clauses. Compact one-line guards already occur frequently; match the surrounding file instead of reformatting unrelated code.
  • Prefer iterator pipelines for transformations and straightforward loops when parsing bytes or mutating state.
  • Use Arc for genuinely shared immutable names/data and parking_lot::Mutex for shared application state; do not introduce shared ownership by default.
  • Clone Arc/Rc values and aliases explicitly with Arc::clone(&value)/Rc::clone(&value) so shared-reference cloning is distinguishable from cloning owned data.
  • Logging/timing is part of the debugging style: backend code uses log::{info,error,...} and integration tests use println! plus Instant.
  • Proc-macro failures may panic with actionable messages because they are compile-time author errors. Runtime parsers should return AsmErr instead.
  • Commit subjects are short, imperative, lower-case English phrases such as add wasm support or support fuzzy search in egui.

There is no repository rustfmt.toml, and the current tree is not clean under cargo fmt --all -- --check. Do not run workspace-wide automatic formatting as a drive-by cleanup. Format only code you changed, keep the local compact layout where practical, and do not mix broad formatting churn with a functional patch.

How to make a change

  1. Locate the owning layer before editing: raw layout, read/write implementation, transformation/node API, server behavior, or frontend rendering.
  2. Check the relevant official format section linked from the source comments when changing JVMS/DEX behavior.
  3. For a new binary structure, define fields in wire order, use existing numeric wrappers, and use ReadFrom/WriteInto plus #[index(...)] or #[align(...)] where supported. Extend asm_macro only if the pattern is reusable.
  4. For a new public capability, expose a narrow entry point and keep its mechanics in impls.
  5. Add or update the smallest relevant test. Prefer a focused assertion over output-only coverage for new behavior, while retaining timing/output when it helps inspect binary transformations.
  6. Run the narrow test first, then the affected crate, then the workspace when cross-crate behavior changed.
  7. Inspect git diff and ensure fixture, generated, IDE, and formatting changes are intentional.

Build and test

The normal toolchain is stable Rust with edition 2024 support.

Fast core loop:

cargo check -p java_asm
cargo test -p java_asm

Target a sample-backed integration test and keep its diagnostic output:

cargo test -p java_asm --test main jvms::read_test::read_jvms_test -- --nocapture
cargo test -p java_asm --test main node::read_test::read_node -- --nocapture
cargo test -p java_asm --test main dex::read_test::read_dex_test -- --nocapture

Server/backend changes:

cargo test -p java_asm_server -- --nocapture

Native CLI:

cargo test -p java_asm_cli
cargo build --release -p java_asm_cli

GUI compile/run:

cargo check -p java_asm_egui
cargo run -p java_asm_egui

Browser development and release:

rustup target add wasm32-unknown-unknown
cargo install trunk
cd asm_egui
trunk serve
trunk build --release

Browser-specific constraints:

  • Trunk serves the canvas in asm_egui/index.html on fixed port 8080 and writes release output to asm_egui/dist/; .github/workflows/web.yml publishes the same output.
  • Keep data-wasm-opt="z" data-wasm-opt-params="--all-features"; current Rust output requires the enabled WASM operations.
  • Browser input currently supports APK, standalone DEX, multiple selections, and nested Android archives such as APKS.
  • WebGL canvas text cannot use CSS font fallback. WASM fetches a pinned Noto Sans SC subset, while native builds use host fonts; keep the web font outside the WASM binary.
  • Loading is throttled through LoadingState. DEX pipelines run concurrently, native indexing uses the fixed Rayon pool, and WASM indexing must yield often enough for repainting.

Final Rust workspace verification (matches CI's effective build/test scope):

cargo build --workspace
cargo test --workspace

Root Cargo commands do not validate ta/. If intentionally changing the Tauri frontend, use its own ta/package.json scripts and ta/src-tauri/Cargo.toml separately.

Test and baseline notes

  • asm/tests/main.rs is the integration-test root; its child modules share helpers, so keep module paths intact when filtering tests.
  • Core fixtures are embedded with include_bytes!, making the tests independent of the process working directory.
  • Parser tests currently favor real .class/.dex samples and inspectability. When fixing a bug, add exact assertions for the affected field/instruction/error so regressions do not depend only on printed output.
  • The build emits existing unused/deprecated/dead-code warnings. Do not treat pre-existing warnings as failures, but do not add new warnings in touched code.

Scope and safety for agents

  • Preserve unrelated working-tree changes. Never rewrite IDE run configurations, fixtures, lockfiles, or generated assets unless the task requires it.
  • Do not update dependencies or regenerate Cargo.lock for an unrelated feature.
  • Do not silently change supported class/DEX versions or raw numeric widths.
  • Do not make ta/ a workspace member without checking its current experimental status and platform requirements.
  • If a behavior is incomplete, prefer an explicit error or documented limitation over fabricated/default data that makes a malformed binary look valid.