Skip to content

jni-rs update to v0.22#462

Open
qdot wants to merge 16 commits intomasterfrom
jni-rs-update-0.22
Open

jni-rs update to v0.22#462
qdot wants to merge 16 commits intomasterfrom
jni-rs-update-0.22

Conversation

@qdot
Copy link
Copy Markdown
Contributor

@qdot qdot commented Apr 27, 2026

5 years of updates and I didn't do any of it because none of us have any idea how this part of the library works.

@gedgygedgy, wherever you are, your code has stood the test of time well, and even the robot says it was well structured and a pleasure to upgrade.

qdot and others added 16 commits April 25, 2026 17:47
Breaking changes addressed:
- call_method_unchecked takes &[jni::sys::jvalue] instead of &[JValue];
  added JValue::to_jni() at all ~40 call sites
- JavaType replaced by ReturnType in call_method_unchecked return type
  parameter (Object/Array/Primitive instead of carrying class strings)
- JMethodID no longer has a lifetime parameter
- JObject::into_inner() renamed to into_raw()
- set_rust_field/get_rust_field/take_rust_field marked unsafe; wrapped
  all 9 call sites in unsafe blocks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major restructure of the Android JNI backend for jni-rs 0.21 breaking
changes. JNIEnv now requires &mut self for all operations and JObject
types are no longer Copy/Clone.

Key changes:
- Remove env field from all JNI wrapper structs, pass &mut JNIEnv
  per-method-call instead
- JSendFuture/JSendStream store JavaVM and obtain env via get_env()
  on each poll, solving the Future::poll env-passing problem
- Replace JList/JMap wrapper usage with direct env.call_method()
  iteration to avoid &mut borrow conflicts
- Extract throw_panic from throw_unwind to enable catch_unwind in
  ops.rs without double &mut env borrows
- Use typed arrays (JByteArray, JObjectArray) per 0.21 API
- Wrap call_method_unchecked in unsafe blocks with raw jvalue args
- Update test infrastructure for RefCell<JNIEnv> invariance: explicit
  RefMut guards, scoped setup before block_on, block-scoped borrows
  between await points

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jni 0.21 tightened lifetime variance on JNIEnv, requiring explicit shared
lifetimes wherever JObject and JNIEnv are used together.

Changes:
- `with_obj` closure: use HRTB `for<'env>` so JNIEnv and JPeripheral share
  the same lifetime, fixing the invariant mutable reference errors
- `Peripheral::new`, `report_scan_result`, `adapter_report_scan_result_internal`,
  `adapter_report_scan_result` JNI callback: add explicit `'a` lifetime
  annotations tying env and JObject parameters together
- `adapter_on_connection_state_changed_internal`: get address string before
  acquiring the MutexGuard from `get_rust_field` to avoid double-mutable
  borrow of env
- `JUuid::as_obj().as_raw()` → `uuid.as_raw()` via Deref: `JObject::as_obj()`
  was removed in jni 0.21; JUuid already Derefs to JObject
- Test crate `lib.rs`: update `run_test`, `initBtleplug`, and `jni_test!`
  macro to use `&mut JNIEnv` as required by the updated API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…dplug

Phase 2 of jni 0.22 migration. Non-FFI function signatures now use
Env<'a> instead of JNIEnv<'a>. GlobalRef fields in Clone structs are
wrapped in Arc<Global<JObject<'static>>>. Classcache stores
Arc<Global<JObject<'static>>>.

extern "C" FFI functions and test_utils are left as-is for later phases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3 of jni 0.22 migration. All string literals passed to JNI API
methods (find_class, get_method_id, call_method, new_object,
is_instance_of, set/get/take_rust_field, call_static_method) are now
wrapped with jni_str!() for names and jni_sig!() for type signatures.

classcache::find_add_class uses JNIString for runtime &str conversion.
register_native_methods calls wrapped in unsafe blocks (required by 0.22).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all deprecated get_env() and attach_current_thread_permanently()
calls with jni 0.22's callback-based attach_current_thread(|env| {...}).

Production code: adapter.rs (4 sites), peripheral.rs with_obj + notifications
closure (2 sites), future.rs poll_internal (1 site), stream.rs
poll_next_internal (1 site).

Test infrastructure: Replace RefCell<JNIEnv<'static>> thread-local with
with_env(f) helper that wraps attach_current_thread. This is a fundamental
rethink — 0.22's callback model means Env can't escape the closure, so the
old pattern of storing env in a RefCell is dead. All 25 test call sites
migrated from JVM_ENV.with(|cell|) to with_env(|env|). Block_on/join tests
use nested with_env calls for independent env access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Completes the jni 0.22 FFI migration:
- ops.rs: JNIEnv → Env for non-FFI code, EnvUnowned + with_env() for
  extern "C" fn_adapter_call_internal and fn_adapter_close_internal
- jni/mod.rs: extern "C" callbacks migrated to EnvUnowned + with_env()
- classcache: Store Global<JClass<'static>> instead of Global<JObject<'static>>
  so &Global<JClass> satisfies Desc<JClass> for new_object/get_method_id
- Fix <&JClass>::from(class.as_obj()) → class.as_ref() across all files
- arrays.rs: new_byte_array now takes usize, remove jint cast

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- exception_check() now returns bool (was Result<bool>), remove ?
- exception_occurred() now returns Option<JThrowable> (was Result), use .unwrap()
- exception_clear() now returns () (was Result<()>), remove ?/.unwrap()
- throw() now returns Err(JavaException) on success; use match or let _ =
  instead of .unwrap()/.? to preserve original control flow semantics
- JObject → JThrowable/JString: use env.cast_local::<T>() (From impls removed)
- NativeMethod struct fields replaced by unsafe from_raw_parts constructor
- JObjectArray::from_raw now requires env param and element type param
- JavaStr → MUTF8Chars in test string assertions (String::from(chars))

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rors

Migrate deprecated array methods to type methods (JByteArray::set_region,
JPrimitiveArray::len, JObjectArray::get_element), replace removed
From<JObject> for JString with cast_local, update from_raw calls to pass
env, fix jboolean (now bool) comparisons, resolve JObject Send lifetime
issue in async connect by scoping Global references, and replace
env.get_string with JString::mutf8_chars throughout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tern

Replace deprecated JNIEnv with EnvUnowned in extern "system" FFI entry
points, use with_env/into_outcome for env access, and update throw_new
to use jni_str! and JNIString for 0.22 string type requirements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…aVM::singleton()

Convert all hand-rolled JNI wrapper types (struct + from_env + Deref + From impls)
to jni 0.22's bind_java_type! macro. Replace GLOBAL_JVM OnceCell with
JavaVM::singleton(). Simplify JSendFuture/JSendStream by dropping cached
JMethodID fields in favour of cast_local per poll call.

Net reduction of ~780 lines of boilerplate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The macro generates extern "system" trampolines with automatic catch_unwind
and error-to-Java-exception propagation, replacing the manual EnvUnowned
pattern that silently swallowed errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the hand-rolled DashMap-based class cache with bind_java_type!'s
built-in global class caching via Reference::lookup_class(). Add bare
bind_java_type! declarations for exception types, FnAdapter, and other
utility classes previously only tracked in classcache. Delete
classcache.rs entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use string literal syntax for Java class paths containing "impl"
  keyword (bind_java_type! parses dot-separated idents, and "impl" is
  reserved in Rust)
- Use block syntax for methods with name overrides (inline sig + block
  is not supported, must use { name = "...", sig = (...) -> T } form)
- Fix borrow checker issues in JPeripheral wrapper methods by splitting
  raw call and cast_local into separate statements
- Fix error type mismatches in with_obj and notification stream by
  unifying on crate::Result

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…apping

bind_java_type! maps JObject to Ljava/lang/Object; in JNI signatures, but
Java methods using domain-specific types (UUID, Future, Stream, ScanResult,
byte[], List, Map, etc.) require exact signature matches. This caused runtime
"Method not found" errors and a SIGSEGV in the scan callback.

Changes:
- objects.rs: Move all methods with domain-typed params/returns out of
  bind_java_type! into manual env.call_method() with correct JNI signatures.
  Keep bind_java_type! for class definitions and primitive-only methods.
- future.rs: Move JFuture::poll to manual impl with correct Waker/PollResult sigs
- stream.rs: Move JStream::poll_next to manual impl with correct Waker/PollResult sigs
- mod.rs: Fix reportScanResult to use extern "C" + EnvUnowned + with_env for
  raw JNI ABI compatibility (from_raw_parts requires raw C calling convention).
  Add env.get_java_vm() to seed JavaVM singleton during init.
- peripheral_finder.rs: Add catch_unwind and logging to adapter background thread
  for Android debugging (silent thread death caused confusing RecvError)

Verified: 28/29 Android integration tests pass on Pixel 9a. The single failure
(testPropertiesContainPeripheralInfo TX power) is a test-peripheral issue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant