Rust signature printer: maddi's verdicts as an ownership inference, rustc as an oracle for the linking engine

Đang mở
#167 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
30/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
java, kotlin, rust
Lĩnh vực
tooling

Hướng nghiên cứu

Start by reading the existing Kotlin and C# printer infrastructure and the stored verdicts for Fernflower and LangChain4j core. Add a Rust signature printer, then translate a handful of small Fernflower class bodies in strict mode and compile them with rustc and clippy. Classify each rejection against the issue's table; done means reporting whether the results support an oracle ratchet, a full translator, or neither.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

front-end:rust

Idea

maddi's analyses already infer most of what Rust makes a programmer write down. Rust is built on aliasing XOR mutability; the Road to Immutability splits the same idea over two axes: modification (rule 1, @Modified/@NotModified) is the mutability half, independence (rule 3) is the aliasing half. A type that is dependent and modifiable from outside, the case that stops a type at @FinalFields, is exactly what Rust refuses without RefCell. An unmodifiable view is a &Vec<T> borrow; a Bag that owns and mutates a private list is an owned Vec<T>.

The experiment: a Rust signature printer that prints struct and method signatures from the existing verdicts, and, as a second step, a body-faithful translation of a few small classes, compiled by rustc. It either confirms that maddi's verdicts amount to an ownership inference, or shows where they don't.

The mapping

Each row has a test in maddi-cst-print-rust (TestJavaToRustMapping unless another class is named): a Java example with the verdicts the analyzer computes for it, the printed Rust, and rustc's judgement. Where there is one, the counterpart (the neighbouring or missing verdict) and the error rustc gives for it are listed as well. The same examples run through the CLI with the analyzer's own verdicts; see rust-printing.md, §"The table of #167, row by row".

maddi Rust test (counterpart: rustc's error)
@NotModified parameter &T parametersAndReceivers
@Modified parameter &mut T, also when only its elements are modified parametersAndReceivers; elementModificationMissed: E0596
non-modifying method &self parametersAndReceivers; TestJavaToRustOwnership#correctVerdictsCompile
modifying method &mut self parametersAndReceivers; TestJavaToRustOwnership#wrongModificationVerdict: E0596
@Container takes its arguments by &, never &mut container
independent constructor parameter (copied) borrowed &T, copied by the constructor (.clone()) independentConstructorParameter; independentConstructorParameterCalledDependent: E0382
dependent field (stores what it is given) a field borrowing with a lifetime parameter: struct View<'a> { list: &'a Vec<T> } dependentField (outliving the list: E0515); TestJavaToRustOwnership#dependentFieldIsBorrowed (modified while borrowed: E0502)
hidden content (T, §xs) an opaque generic T, handed out as &T hiddenContent
@Immutable(hc=true) shared as Rc<T> without a lock; the hidden content cloned under T: Clone immutableHcShared; TestJavaToRustStructure#recordWithHiddenContentIsShared
@Finalizer a method taking self by value: the move consumes the object (Rust enforces the sequencing rules the book leaves unenforced; iterator adapters take self for the same reason our stream operations are @Finalizer) finalizer (a call after the finalizer: E0382)
@Mark / @Only(before/after) the typestate pattern: Config<Before> → Config<After>, the mark consuming self typestate (a call before the mark, a second mark: E0599)
@IgnoreModifications on a method (log methods) interior mutability: &self over RefCell fields ignoreModifications; ignoreModificationsMissing: E0596
@StaticSideEffects (System.setOut) static state as static mut; the method an unsafe fn staticSideEffects; staticSideEffectsMissing: E0133
nullability Option<T> nullability

The engines are related too. Rust's newer borrow checker, Polonius, is formulated as Datalog over subset constraints between origins plus loan liveness per program point, structurally close to the link closure (⊆ links, incremental fixpoint with witnesses). The difference is flow sensitivity: the link closure is path-insensitive on purpose. Nullability already has the two-layer shape a borrow analysis would need: a path-insensitive closure for declarations plus a flow-sensitive walk (NonNullFacts) for use sites.

Why: a stronger oracle for the linking engine

kotlinc judges two of our verdicts (nullability, and read-only vs mutable for collections). rustc's borrow checker would judge modification and independence on every parameter, receiver and field. Printing signatures straight from the verdicts and letting rustc check the bodies against them, rejections point at specific verdicts:

rustc says likely analyzer issue
cannot borrow as mutable behind a & reference (E0596) a @NotModified/non-modifying verdict the body contradicts: an unsound modification verdict, at the exact line
borrowed value does not live long enough, lifetime errors (E0597, E0716, …) a missed dependence: a field that keeps a reference it was given, while the verdict said independent
use of moved value (E0382) an ownership or @Finalizer assumption that doesn't hold
overlapping mutable and immutable borrows (E0502, E0499) aliasing plus modification the verdicts didn't see together

Three caveats decide whether the signal is usable:

  1. A strict mode with no escape hatch. A Rc<RefCell<…>> fallback wherever a verdict looks doubtful silences rustc; the error becomes a BorrowMutError panic at run time, only if a test reaches it. Oracle runs print the plain types the verdicts imply.
  2. rustc catches unsoundness, not imprecision. A @Modified that could be @NotModified compiles, since &mut is accepted where & would do. Clippy's needless_pass_by_ref_mut and unused_mut give the other direction: unsafe and noise columns, as in the Guava nullability oracle.
  3. Not every rejection is maddi's. rustc's checker is itself conservative; the OOPSLA 2023 aliasing study found it the main precision limit when translating C to safe Rust. Triage rule: verdict "not modified" and rustc needs &mut → analyzer wrong; verdict "dependent" and rustc rejects the borrow → analyzer right, the printer chose the wrong representation (needs Rc or a clone).

A signatures-only printer with todo!() bodies checks nothing (rustc doesn't look at the bodies), so step 2 is required for the oracle; step 1 is for reading plausibility by eye.

Plan

  1. Signatures for Fernflower and LangChain4j core, from the stored verdicts (reusing the printer infrastructure of the Kotlin and C# printers). Read them: does & vs &mut look right? Do dependent fields land where a Rust programmer would put a lifetime? How much would need Rc<RefCell<…>>?
  2. Bodies for a handful of small Fernflower classes, strict mode, compiled with rustc and clippy; classify every rejection with the table above.
  3. Decide: an oracle ratchet like the J2K/C# ones, a full translator, or neither.

Out of scope for now: parsing Rust into the CST. Macros, traits with associated types, pattern matching and lifetime generics, realistically through rustc's MIR, cost more than the K2 front end did, and most of what maddi would compute on Rust the borrow checker has already proven.

A full translator, later: library support

Rust equivalents of the JDK (the mapping, as for C#'s JDK→BCL): collections in std (Vec, HashMap, BTreeMap, VecDeque, HashSet), regex, chrono/jiff, num-bigint, rust_decimal/bigdecimal, serde_json for Jackson, log/tracing for slf4j, std::sync/crossbeam/rayon. No crate solves: UTF-16 String semantics (charAt, length) need a shim; no class inheritance, so superclasses become traits plus composition (the largest printer problem); exceptions become Result or panics; equals/hashCode become Eq/Hash.

Prior art. No mature Java→Rust translator that infers ownership. The closest is C→Rust: C2Rust produces unsafe Rust, and Crown (CAV 2023) adds a static ownership analysis to turn C pointers into safe Rust, our analysis in a different place.

Bridges to the Java not yet translated:

  • Rust calling Java: the jni crate (JNI Invocation API, embedding a JVM), j4rs on top of JNI (not verified).
  • GraalVM Native Image --shared: a native library whose @CEntryPoint functions Rust calls through the C ABI; non-object types only, so object graphs don't cross.
  • Java calling Rust: Panama FFM (final since JDK 22) against a Rust cdylib. The realistic migration path: translate one module at a time.

The verdicts help at the boundary: only an immutable or independent value can be copied across safely; a dependent one is an object shared between two runtimes.

Related, separately: lifetimes for Java

maddi does not track when objects become unreachable, but it could track outlives over the link graph: x linked by ∈ into a collection reachable from a static field lives as long as the program; a listener registered and never removed lives as long as its subject. That is Rust's 'a: 'b, and its first application would be leak detection. The book's Lazy blanking out supplier = null is already a lifetime statement.

References

🤖 Generated with Claude Code

Ngôn ngữ chính
Java
Star
1
Fork
1
Merge trung bình
1 giờ 13 phút
Pull request đã merge (30 ngày)
6

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của CodeLaser/maddi

Tất cả issue của CodeLaser/maddi

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.