Coder Social home page Coder Social logo

jni-rs's Introduction

Build Status Docs Crates.io

JNI Bindings for Rust

Join the chat at https://gitter.im/jni-rs/Lobby

This project provides complete JNI bindings for Rust, allowing to:

  • Implement native Java methods for JVM and Android in Rust
  • Call Java code from Rust
  • Embed JVM in Rust applications and use any Java libraries

See the docs for more details.

Example

cd example
make

Contribution

See the Contribution Guide for details.

License

Licensed under either of

at your option.

jni-rs's People

Contributors

alexander-irbis avatar argv-minus-one avatar chrismooredev avatar debris avatar dependabot[bot] avatar dmitry-timofeev avatar dnaka91 avatar fpoli avatar georgwi avatar jrobsonchase avatar kjvalencik avatar madadam avatar marschall avatar maurolacy avatar mehcode avatar nbaksalyar avatar paulrouget avatar progmanos avatar rabbit0w0 avatar rib avatar robmv avatar rodrigodd avatar s1ck avatar skletsun avatar stanislav-tkach avatar sujithjay avatar timetoogo avatar tobiasdebruijn avatar upsuper avatar vvanders avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jni-rs's Issues

Example doesn't compile since GlobalRefGuard introduced.

Attempting to compile the code in example/mylib in the master branch leads to the following error:

error[E0599]: no method named `detach` found for type `jni::objects::GlobalRef` in the current scope
   --> src/lib.rs:137:29
    |
137 |     let callback = callback.detach().unwrap();
    |                             ^^^^^^

It looks like this is due to the change made in a98e71f Reimplemented GlobalRef (#66) when GlobalRef was made Send. Unfortunately I don't know enough about this to know how to fix it.

How to use pop_local_frame(JObject::null())

I would like to call env.pop_local_frame(JObject::null()).

The documentation says that "resulting JObject can be NULL if result is NULL".
However, in my case pop_local_frame returns an Err(NullPtr("PopLocalFrame result")).

Shouldn't the result be Ok(something), since it's an expected behaviour?

Write tests

Having tests is very useful. We could write some integration/system test that:

  • initializes a JVM, by calling directly jni-sys or the proposed extended invocation API #60
  • builds a JNIEnv with JNIEnv::from_raw
  • test stuff in JNIEnv, relaying mainly on JVM builtin classes

An example of the result is here, from the Rucaja crate.

Call of a Java method returning a primitive type does not check for exceptions

The implementation of call_method_unsafe (which is called from call_method) usesjni_unchecked! macro if the method return type is void or a primitive type. But if the method throws an exception, it won't be detected by the library unless either:

  • the user explicitly calls JNIEnv#exception_check/#exception_occurred
  • the user calls any unrelated JNI function, which is not safe to call when there is an exception pending.

Exception-safe JNI function checks for exceptions

delete_local_ref (DeleteLocalRef) is specified as one of a few safe functions to call when there is a pending exception (= the one that do not throw). But the implementation of JNIEnv#delete_local_ref checks for exceptions after the call:

 pub fn delete_local_ref(&self, obj: JObject) -> Result<()> {
        non_null!(obj, "delete_local_ref obj argument");
        Ok(unsafe {
            jni_unchecked!(self.internal, DeleteLocalRef, obj.into_inner());
            check_exception!(self.internal);
        })
    }

It might result in an Err when a user invokes delete_local_ref when they've already got an exception and try to delete local references.

Byte arrays in signature

Hello

I'm following your example. It has a signature like this:

pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv,
                                             class: JClass,
                                             input: JString)
                                             -> jstring {
...

From looking at the signature, it seems that parameter types come from the objects module while the return value is a sys type.

How would I accept a byte array as input value instead of a JString? There's sys::jbyteArray but there doesn't seem to be a matching objects type?

Return Option<jobject>

I have a function signature where I want to return an object instance or null.

I tried returning an Option<jobject>, but then the Rust code panics :)

Is there a way to return a null pointer?

jvalue has no field _data anymore

The crate jni_sys has renamed the field _data of struct jvalue to data, hence this crate does not compile anymore due to this line in jvalue.rs (line 54):

trace!("converted {:?} to jvalue {:?}", self, val._data);

Calling methods on GlobalRef

It is possible to call methods on a GlobalRef?

JNIEnv::call_method takes an JObject and i don't see any way to convert between the types.

`new_object` and `GlobalRef`

Is there a way to create a new object from the cached GlobalRef? As far as I can see, new_object expects a class name as string only. Am I missing something?

If this is currently impossible, I can try to fix this (somehow). 😅

`JMethodID`/`JStaticMethodID` lifetime

Both JMethodID and JStaticMethodID have lifetime associated with JNIEnv, but it isn't entirely correct:

A field or method ID does not prevent the VM from unloading the class from which the ID has been derived. After the class is unloaded, the method or field ID becomes invalid. The native code, therefore, must make sure to:

  • keep a live reference to the underlying class, or
  • recompute the method or field ID

This way method id should depend on class and not on JNIEnv. As far as I can see, there is no easy way to express that in the Rust code. In my code I use "hack" to workaround current restriction:

struct CachedConstructor {
    pub element_class: GlobalRef,
    // `static` lifetime is used here.
    pub constructor_id: JMethodID<'static>,
}

impl<InnerIter> CachedConstructor {
    pub fn new(env: &JNIEnv, class_name: &str) -> Result<Self> {
        let class = env.find_class(class_name)?;
        let element_class = env.new_global_ref(class.into())?;
        let id = env.get_method_id(class_name, "<init>", "([B[B)V")?;
        Ok(CachedConstructor {
            element_class,
            // Transmute & unsafe:
            constructor_id: unsafe { mem::transmute(id) },
        })
    }
}

Any advices on how to handle it better?

(Native) context of Java exceptions

Currently check_exception! macro returns JavaException error which isn't very informative: native (rust) call stack is missed because of Result handling logic.

More details: in the situation where there are many new_object calls and some of them fail (NoSuchMethodError) it isn't clear which one caused problem.

I'm not sure what is the best way to handle such situations. Some ideas:

  1. Obviously user can add additional checks after each new_object call. This approach requires additional boilerplate.
  2. jni-rs can include in Result::Err some additional information about the JNI call context: the call stack information and method parameters. For example, when GetMethodId fails with a NoSuchMethodError, include information about the class, method name and signature.
  3. Additional checks can be added to the new_object method (and related ones).

Additional question: perhaps, this situation isn't specific only to creating a new Java object, but it relates to the overall error handling strategy.

Move the project to an organization

@jechase, @anandkunal ,

Thank you for putting together and open-sourcing Rust JNI project. We at Exonum have been using it for more than a year and are interested in its further development.

Our team is grateful to you for reviewing and integrating our patches so far, however, it seems to us the project has ceased to be your top priority recently. For this reason, I suggest moving it under a new organization “JNI-rs” and giving more people permission to integrate changes and publish new versions of it on crates.io. In this way the project can evolve, enlarge its community, acquire better maintenance and improve in quality.

We understand it takes efforts maintaining an open-source project and would like to offer help with that. As you may know, members of Exonum team — @DarkEld3r , @alexander-irbis — have contributed various fixes and improvements to Rust JNI as well as reviewed every patch submitted to the project in the last year. Our team has expertise in running open-source projects in Rust, Exonum being the most prominent example.

Please consider further development of your project in a modified manner as suggested above and let us know your thoughts on the matter. You are free to contact our team members or me directly for discussion.

CC recent contributors: @paulrouget, @daschl, @mmastrac , @fpoli , @nbaksalyar , @king6cong , @mehcode

Crate type should be cdylib rather than dylib ?

The documentation mentions that the crate type should be set to dylib so that a shared object loadable by Java is produced. I had to set it to cdylib, dylib produces objects that link dynamically to libstd.

Warn a user when a GlobalRef is dropped with no thread attached

Currently DetachedGlobalRef attaches a native thread to the JVM in its #drop function in case when the current native thread is not attached, and immediately detaches. That prevents a memory leak, but it also makes it easy to rely on this mechanism frequently enough to introduce a performance bug and make your program come to a crawl. What makes it considerable, is that frequently enough means thousands.

@alexander-irbis did a simple benchmark that constantly attaches a native thread, calls a simple Java method, and immediately detaches it. It shows tens of thousands ops per second. Such a slow throughput occurs because JVM creates a new thread when a detached native thread calls AttachCurrentThread. The results above align well with the results I obtained with a Java benchmark which simply creates threads that do nothing and stop: JVM creates ~40K threads per second on my laptop.

I'd suggest to log a warning message in the case described above, saying that if you see it once, it's OK, if you see it frequently -- consider reading the docs and fixing your app, because:

  • This complex, slow behavior is hidden from a user, who is unlikely to know the underlying mechanics.
  • Its results are less obvious than an OutOfMemoryError (in case of leaks).
  • One needs to use a profiler to reveal the source of the problem.

This issue relates to DetachedGlobalRef as of v0.8.1, and a prospective new GlobalRef in #67, therefore, it's orthogonal to #66 .

set_byte_array_region and friends should take &mut array

Hi,

Currently, methods like https://docs.rs/jni/0.10.1/src/jni/wrapper/jnienv.rs.html#1315 take the buffer they modify as non-mutable, but they are modifying its content. I wonder if the array to modify should be passed in as &mut? I think this also applies to all other similar methods on the env.

Support for generating native proxies of Java objects

Can the library help somehow in the generation of native proxies of Java objects?

At the moment we are using this crate https://github.com/viperproject/jni-gen (undocumented...)

Our approach inspects the JVM by reflection at Rust's compile time and generates wrappers for methods listed in a whitelist in build.rs (see here for an example). We introduced the whitelist because we choose to generate a wrapper for every inherited method, thus increasing a lot the size of the generated code. Whenever possible (that is, unambiguous) the user can leave a field empty to let the generator choose the only possible name/signature/...

An example of the generated bindings: https://viperproject.github.io/prusti-dev/viper_sys/wrappers/index.html

Some problems that we found:

  • Java methods can be overloaded. How to choose the corresponding name in Rust?
  • Java method names allow more characters than function names in Rust. How to translate them?
  • How to handle inherited Java methods? Should the user know what is the base class that implements it, or should a wrapper be generated in all inheriting classes?

The end result works fine, but there is a lot that can be improved. For example, the whitelist in build.rs may be intimidating at a first glance, the name of method parameters is not preserved, and so on.

new_object: accessed stale local reference

I have a simple POJO class with an empty constructor.

public IceCandidate() { }

This is what the JNI class looks like according to javap:

public class ch.dbrgn.candidateparser.IceCandidate {
  public ch.dbrgn.candidateparser.IceCandidate();
  ...
}

When I try to create a new instance for that class:

let myobj = env.new_object("ch/dbrgn/candidateparser/IceCandidate", "()V", &[]).unwrap();

...then I get a native crash.

JNI ERROR (app bug): accessed stale local reference 0x5 (index 1 in a table of size 1)
art/runtime/java_vm_ext.cc:410] JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x5
art/runtime/java_vm_ext.cc:410]   native: #02 pc 0025a775  /system/lib/libart.so (_ZN3art9JavaVMExt8JniAbortEPKcS2_+740)
art/runtime/java_vm_ext.cc:410]   native: #03 pc 0025aecb  /system/lib/libart.so (_ZN3art9JavaVMExt9JniAbortFEPKcS2_z+74)
art/runtime/java_vm_ext.cc:410]   native: #05 pc 00101211  /system/lib/libart.so (_ZN3art11ScopedCheck5CheckERNS_18ScopedObjectAccessEbPKcPNS_12JniValueTypeE.constprop.95+684)
art/runtime/java_vm_ext.cc:410]   native: #06 pc 00108ac5  /system/lib/libart.so (_ZN3art8CheckJNI10NewObjectAEP7_JNIEnvP7_jclassP10_jmethodIDP6jvalue+436)
art/runtime/java_vm_ext.cc:410]   native: #07 pc 000099db  /data/app/ch.dbrgn.candidateparser.test-1/lib/arm/libcandidateparser_jni.so (Java_ch_dbrgn_candidateparser_CandidateParser_parseSdp+3230)

Any idea what reference that could be? I've tried to debug this for quite a while now.

Call EnsureLocalCapacity

I would like to call EnsureLocalCapacity, and I'm willing to contribute.

Is it fine if I add a ensure_local_capacity(&self, capacity: jint) method to JNIEnv?

Signature parse error does not include the input string

When a user passes an invalid signature string (e.g., "()Ljava/lang/List" to env.call_static_method), they get an obscure error:

Parse error at 93988628020833
Unexpected `end of input`
Expected `;`

It doesn't include the input string that caused the error, but includes this huge and unique to each invocation number. Is it a formatting bug in the "combine" library that produces this error message?

Auto-generate native Java-interfacing files

Overview

This is a feature request and an another take on #80 .

Consider adding a tool that takes a Java source file with native methods or a native C header of such file, produced by javac, and turns it into a Rust-template. Basically:

X.java --> (X.rs, X-human-readable.rs)

Such a tool may produce a single template file, similar to what one currently writes by hand, or a couple of files:

  • An intermediate, always re-generated, low-level .rs file, that has the methods with signatures as required by C-JNI. Such file might have some boilerplate that is required to guarantee safe operations (catches panics, converts errors into Java exceptions, etc., see the primary issue #80)
  • Optionally, a user-facing template file. This one is generated once, has human-readable identifiers, possibly, exactly matching the names of Java methods (e.g., nativeCreate instead of Java_com_example_company_NativePeerProxy_nativeCreate). Such methods might also enjoy some conversions that are performed by the code in intermediate file (e.g., jint -> i32, again, see #80).

If there is a user-facing file, intermediate one calls its functions.

Requirements

  1. Standard-compliance or reasonable restrictions. I.e., such a tool must be able to handle whatever javac -h produces. Restrictions may apply on method names (e.g., method names that contain only ASCII symbols).
  2. Ease of use: using such a tool must be simpler than copy-pasting javac -h output.
  3. Good UX:
    • Reasonable defaults, works out-of-the box.
    • Descriptive error messages when things go wrong at FFI boundary, since FFI is hard.
    • User-facing template that is IDE-friendly (macros hidden in intermediate file).
    • Refactoring-friendly: must work reasonably when native methods are added/renamed/have their signatures modified.
    • Build integration: whatever build-system you are using, the tool must integrate nicely (e.g., Gradle/Maven/Ant/Bazel/Cargo).
  4. Low or moderate overhead: ideally, you get extra safety for no extra performance or complexity cost.
  5. Works on popular OSes.

Criteria

  1. Low or moderate complexity: no knowledge of 🚀 science required to implement and maintain.
  2. Maintainable: when this library updates, it's easy and well-known how to modify the tool.
  3. Permissive license.

Possible approaches

  1. GNU-fu: Awk & friends, taking a javac -h output.
    • Don't have to reinvent the wheel to process text files.
    • Need to un-mangle method names to produce good user-facing file.
    • Not cross-platform.
  2. Rust-fu: a CLI-tool taking javac -h output.
    • Might plug better than ^ into Cargo.
    • Might reinvent the wheel.
  3. Java-fu: Java compiler plug-ins (aka processor, annotation processors).
    • Plugs natively into the compiler, therefore, works with anything you use to invoke javac.
    • Have programmatic access to source-file structure. Don't have to parse things.
    • Have to generate .rs files by hand.

Non-goals

  • Automatic generation of native Rust proxies of Java objects, see #82

See also

  • JNR might do something similar under the hood: https://github.com/jnr/jnr-ffi
  • A discussion regarding error-handling (both throwing exceptions from native code and catching panics at FFI boundary): #76

Add a registry of native peers

Overview

This is a feature request for an additional safety net for the use-case when Java passes pointers to native objects to the native code.

Consider the following code:

// An example of Java proxy of a native object: `vec` proxy
class VecProxy {
    // A handle to the corresponding native object (aka native peer).
    private final long nativeHandle;

    // Constructors, public methods using the native methods below:
    
    // Creates a new `vec` and returns a pointer to it.
    private static native long nativeCreate(int initialSize);

    // Adds the given element to the vector.
    private static native void nativeAdd(long nativeHandle, int newElement);

    // Destroys the corresponding native peer.
    private static native void nativeDestroy(long nativeHandle);
}

The native code has to cast jlongs to the corresponding objects. If there is a bug in Java code (use-after-free, double-free, unknown-pointer, wrong-pointer-type), the app is likely to crash.

Possible solution

A registry of all native objects that can be used at the given moment by a pointer. Such an object checks that a native handle:

  • is known,
  • has a correct type.

Such checks may be optional (e.g., enabled in debug builds only), and may be exposed through such interface:

  • Create a handle to a native object, optionally, registering it in the registry: to_handle
  • Cast a handle to a native object of the given type (with optional checks above): cast_handle
  • Drop handle, destroying the native object and, optionally, removing the handle from the registry: drop_handle

In Exonum Java Binding, we currently have a synchronized HashMap-based implementation.

Questions

  • Q0: Is it useful to all users? Shall it be:
    • In the core library?
    • In a separate satellite crate, e.g., jni-java-proxies?
    • Outside the lib?

See also

Compiling on Windows

I'm trying to compile my example fasihrana/rustjni_example on windows and I'm getting the following error:

error: failed to run custom build command for `jni v0.10.1` process didn't exit successfully: `C:\Code\RUSTSTUFF\rustjni_example\target\debug\build\jni-6a065823e243d0f6\build-script-build` (exit code: 101) --- stderr thread 'main' panicked at 'Failed to find libjvm.so. Try setting JAVA_HOME', C:\Users\frana\.cargo\registry\src\github.com-1ecc6299db9ec823\jni-0.10.1\build.rs:19:21 note: Run with `RUST_BACKTRACE=1` for a backtrace.

Why is it looking for libjvm.so on Windows and how do I fix that?

Consider Executors providing JNIEnv

Overview

This is a feature request for an abstraction enabling any code to execute a lambda in some context with JNIEnv (i.e., in a native thread that is guaranteed to be attached to the JVM).

Such abstraction is useful when impelementing native proxies of Java objects:

trait Executor {
  // Executes the given function in a native thread 
  // that is attached to the JVM (= has JNIEnv).
  def <T> with_context(Function<&JNIEnv, T> function);
}

// A native proxy of a Java Foo class.
struct JavaFooProxy {
  // A global reference to an instance of the Java Foo class.
  foo: GlobalRef;

  // An executor, allowing to call a Java method in an arbitrary moment.
  executor: Executor;
  
  // Calls Java `Foo#bar` method.
  def bar() {
    executor.with_context((env: &JNIEnv) -> {
        env.call_method(foo, "bar", BAR_SIGNATURE,);
    }
  }
}

Possible implementations

How Executor provides a context (JNIEnv) — is an implementation detail.
There might be various implementations of such an interface:

  1. SnailExecutor: always attaches/detaches the current thread, which is VERY costly 🐌
  2. CurrentThreadExecutor: will work when the current thread is already attached, uses JNIEnv directly.
  3. HackyExecutor: relies on the fact that your app never creates more than N threads
    and never detaches them (a benign leak).
  4. DefaultExecutor: keeps a couple of attached threads and passes the lambda to them.
    Reliable, but requires some time and care to get right.

In our code base, we currently have a SnailExecutor only.

Questions

Certainly not an exaustive list, please post yours:

  • Q0: Is it useful to the target audience? Shall it be:
    • In the core library?
    • In a separate satellite crate, e.g., jni-native-proxies?
    • Outside the lib?
  • Q1: Which implementations the library must provide, if any?
  • Q2: (Might be out-of-scope) Can one write a proxy that can work with both AutoLocal and GlobalRef?

See also

  • Auto-generation of native proxies: #82

Bytes signed/unsigned inconsistency

Currently there are four functions that work with bytes:

  • new_byte_array: takes &[i8]
  • convert_byte_array: returns Vec<u8>
  • new_direct_byte_buffer: takes &mut [u8]
  • get_direct_buffer_address: returns &mut [u8]
    Most of them work with unsigned bytes and that is consistent with Rust standard library, but new_byte_array expects slice of signed bytes, what is more Java-like.

I prefer to change new_byte_array signature (and happy to submit a patch), but that will be a breaking change. Is it OK?

v0.7.0 fails to compile with `transmute called with types of different sizes` on armv7-linux-androideabi

Hi! Apologies if I'm missing something obvious.

It seems that as of b6463aa I can no longer compile the library for arm targets.

Trying to run cargo build --target armv7-linux-androideabi fails with

error[E0512]: transmute called with types of different sizes
  --> /Users/realpassy/.cargo/registry/src/github.com-1ecc6299db9ec823/jni-0.7.0/src/wrapper/objects/jvalue.rs:53:13
   |
53 |             ::std::mem::transmute::<_, usize>(val)
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: source type: jni_sys::jvalue (64 bits)
   = note: target type: usize (32 bits)

I'm too much of a Rust n00b to say whether or not this is an intended limitation, but it seems that v0.6.1 works fine for that target.

UnsatisfiedLinkError with objects as parameter

I get an UnsatisfiedLinkError with this method:

FastCompute.arrayAddRS(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String

Rust:

#[no_mangle]
#[allow(non_snake_case)]
#[allow(unused_variables)]
pub extern "C" fn Java_FastCompute_arrayAddRS(env: JNIEnv, class: JClass, a: JObject, b: JObject) -> jstring {
    [...]
}

Java:

private native String arrayAddRS(Object a, Object b);

My other method (FastCompute.addRS.(II)I) is working without any problems

Add benchmarks of low-level vs high-level APIs

We've developed a couple of benchmarks comparing high-level APIs to low-level C-like APIs in Rust-JNI:

  • call_static_method vs call_static_method_unsafe, using j.l.Math#abs.
  • call_method vs call_method_unsafe, using j.l.Object#hashCode. The object construction is not included in the measurement.
  • new_object vs new_object_by_id, using j.l.Object#new. The measurement includes removing the local references in the benchmark itself.

I think it may be useful to upstream them so that users have an up-to-date data about their relative performance and can decide when it makes sense to them to use lower-level ones.

See also: https://developer.android.com/training/articles/perf-jni.html

strange memory usage and looks like a leak

I changed the example and run hello world in a loop, and memory usage keeps going up until around 165M and stops growing.

changes:

king6cong@4bedfbb#diff-cc38745d93dc48d0cd2612e5d596dad5L15

steps to reproduce:

git clone -b leak https://github.com/king6cong/jni-rs
cd jni-rs
cd example/mylib && cargo build && cd -
cd example && javac HelloWorld.java && java -Djava.library.path=mylib/target/debug/ HelloWorld

image

This is strange, and not sure if this is a memory leak

Higher-level api for defining java functions in a more "rusty" manner

See #76 for some background.

It would be nice to be able to define the FFI functions more like regular rust functions with rust error handling rather than as extern "C" functions. The current way of doing things forces you to manually catch Result::Err results and turn them into java exceptions (or however you want to deal with them), and to manually wrap everything in std::panic::catch_unwind (which you are doing, right?) and deal with the Result from that as well.

We could instead have a macro system that allows you to write your ffi methods such that they return the expected Result<T, E>, and then convert that into an exception or java return type as needed. It would do the same with automatically doing the catch_unwind wrapping and result management in a similar manner.

The macro system could potentially also do automatic conversions of java types (such as Strings) into their rust representations, though it should probably be possible to bypass this if desired for performance reasons.

Extend Invocation API support

Our team needs a means to launch a JVM from Rust (in C JNI interface that functionality is provided by the Invocation API, that is already partly supported by jni-rs). I'd like to ask if anybody is currently working on it, and if not, if we could contribute the remaining parts? Are there any nuances we must consider when designing and implementing this functionality?

Redesign and reimplement `GlobalRef`

The current implementation of global references in the form of GlobalRef / DetachedGlobalRef mistakenly assumes that the reference should be attached to the thread. In fact, this is not the case, the same reference can be concurrently used in different threads at the same time.

In addition, the need to destroy an instance of GlobalRef or DetachedGlobalRef when attaching / detaching is not ergonomic, since it complicates the storage of the reference as part of the other structures.

On the other hand, the task to protect from a race condition by the type system of Rust goes beyond the scope of the functionality of GlobalRef, and ultimately is not solved, due to the possibility of creating an additional global reference to the same data (and, it seems, not can be guaranteed to be solved).

Therefore, the functionality of the references and the attachments to threads should be divided into two independent structures that might roughly look like this:

#[derive(Clone)]
struct GlobalRef {
    internal: Arc<GlobalRefGuard>,
}

struct GlobalRefGuard {
    vm: JavaVM,
    obj: JObject<'static>,
}


#[derive(Clone)]
struct JNIEnv {
    env: Rc<JNIEnvGuard>,
}

GlobalRef in such an implementation will allow to re-use the global reference, including between threads, and also automatically release it, as soon as the need for it gone. In addition, reusing an existing global reference, instead of creating a new one each time (by calling NewGlobalRef), allows speeding up in some cases, but this speedup can be neglected if dozens of other calls to JNI or heavy computing operations occurs on each copy of the reference.

The structure JNIEnv with the use of a similar pattern would be able to transparently hold the attached JVM thread, while we need it somewhere in the native thread, and detach as soon as the need for it gone (or, knowing that we will need it for a long time, we could keep it without detaching, just saving an additional copy of JNIEnv). But this is beyond the scope of this issue and requires more detailed discussion and changes in JavaVM.

A bit later I will do PR with a new implementation of GlobalRef. But, since this is a breaking change, there may be open questions that should be discussed.

For example, should the current implementation of GlobalRef + DetachedGlobalRef be deprecated and then discarded or GlobalRef should remain as it is, but renamed to AttachedGlobalRef, because someone heavily depends on it?

How to throw exceptions from rust

I'm working on a rust crate with some functions that return Result - I'm hoping use the Err values to generate exceptions for the Java caller, but I'm not confident I'm using the provided API in the intended way.

The reason I'm doubtful is I expected to be able to do something like this:

#[no_mangle]                                                                   
#[allow(non_snake_case)]                                                       
pub extern "system" fn Java_MyLib_getMaybeBool(                               
    env: JNIEnv,                                                               
    class: JClass,                                                             
    input: JString,                                                            
) -> jboolean {                                                                
    let input: String = env.get_string(input)                                  
        .expect("Couldn't get java string!")                                   
        .into();                                                               

    match get_maybe_bool(&input) {
        Ok(output) => output as u8, 
        Err(err) => {                                                          
            env.throw(err.to_string()).unwrap();
            unreachable!();
        }                                                                      
    }                                                                          
} 

When I write this, the java caller segfaults in the Err case, hitting the unreachable!(); and then panics.

Instead, I'm returning a 0u8 following the throw, which I guess could be correct from a certain point of view (but not really). With the 0u8 return value, I see the caller get the exception and everything works how I'd think it should.

Am I missing some aspect of this? An example of throwing exceptions would be nice to have.

Wrapper for local reference automatical deletion

Sometimes it is important to manually delete the local references. I have made a simple wrapper for that:

pub struct AutoLocalRef<'a> {
    object: jobject,
    env: &'a JNIEnv<'a>,
}

impl<'a> AutoLocalRef<'a> {
    pub fn new(env: &'a JNIEnv<'a>, object: jobject) -> Self {
        Self { object, env }
    }

    pub fn as_obj(&self) -> JObject {
        self.object.into()
    }
}

impl<'a> Drop for AutoLocalRef<'a> {
    fn drop(&mut self) {
        if let Err(e) = self.env.delete_local_ref(self.object.into()) {
            error!("Unable to delete local reference: {}", e.description());
        }
    }
}

Typical use case:

fn make_java_object<'a>(env: &JNIEnv<'a>, value: &Value) -> Result<JObject<'a>> {
    let value = AutoLocalRef::new(env, env.byte_array_from_slice(value)?);
    env.new_object(
        "com/project/foo/bar/baz/ClassName",
        "([B)V",
        &[value.as_obj().into()],
    )
}

This function is used for building tree-like structure, so local references must be deleted because there can be many nodes.

Questions:

  1. Will it be useful to have such wrapper in the jni-rs library?
  2. Wrapper stores jobject inside and has method for conversion it to the JObject because this allows to clone internal object: many functions take JObject by value and reference must be deleted after that. Perhaps this can be handled better somehow.

How to use primitives like 'jint', 'jbyte', etc. ?

I'm a beginner to Rust and I have no idea how to write the equivalent Rust code of the C++ code below:

JNIEXPORT auto JNICALL Java_foo_Bar_foo(
		JNIEnv *env,
		jclass,
		jint num,
		jintArray _data) -> jintArray {
	jboolean *option = nullptr;
	auto len = env->GetArrayLength(_data);
	auto data = env->GetIntArrayElements(_data, option);
	env->ReleaseIntArrayElements(_data, data, JNI_ABORT);
	auto _ret = env->NewIntArray(len);
	env->SetIntArrayRegion(_ret, 0, len, ret);
	return _ret;
}

Sorry for my English.

Memory leaks in methods looking up Class objects

Overview

JNIEnv methods accepting Desc<JClass> leak a local reference to the Class object if they create a new local reference.

Description

If an implementation of Desc#lookup that creates a new local reference is passed to the JNIEnv methods, the created local reference will be leaked (resulting in a leak of native JVM heap and a (potential) leak of referenced Java object). E.g.,

env.call_static_method("java/lang/Math", "abs", "(I)I", &[JValue::from(-1 as jint)])

will leak a single local reference to an instance of Class<java.lang.Math> created here each time it is called.

There are about two dozens of Desc#lookups in the codebase.

Which client code is NOT affected (benign leaks)

Such leaks can be considered benign if all created local references are freed later:

  • Refs created in native methods called from Java — freed by the JVM when the native method returns.
  • Refs created in a native method attached briefly to the JVM — freed after the thread is detached.
  • Refs created in a lambda passed to with_local_frame — freed after the last statement in that lambda.

Which client code IS affected

Code using affected methods in:

  • Native threads attached to the JVM for a long or indefinite time. Although such code shall generally use with_local_frame/auto_local to manage the references created by the user, it must be noted that it's not required if it performs only simple calls that do not expose any references that have to be deleted. In other words, it is reasonable to expect that the library methods do not leak — and that's the specified behaviour of the C APIs:

To ensure that programmers can manually free local references, JNI functions are not allowed to create extra local references, except for references they return as the result.

  • Long-running loops.

How to reproduce

See #108

Possible solutions

  1. Always perform lookups in a local frame to ensure we delete refs if we create them. Slight performance regression — need to evaluate.
  2. Have methods with distinct signatures that perform lookups and delete manually in them. A certain UX regression.
  3. Distinguish (?) somehow when we do create a reference during lookup:
    • By using a combination of new_local_ref + AutoLocal. If a lookup is performed — the result is simply wrapped in an auto_local. If an existing reference is returned (e.g., JClass -> JClass), it is first duplicated with new_local_ref to keep the original intact, and then wrapped in auto_local. Slight performance regression in case the existing reference is used — unneeded new_local_ref + delete_local_ref
    • By returning a structure similar to auto_local that remembers if it shall delete the local reference (or retrofitting such functionality in AutoLocal). No performance hit when it is not needed, but higher complexity
  4. (?)

See Also

Investigate/implement error code checking

There are a number of places where we ignore the returned error code from jni methods because we're already checking for a java exception being thrown.

Doing the inverse (checking error code, ignore exception) could give greater insight into why the method failed since we could return more meaningful errors than "Java exception was thrown," and the error codes are reasonably well-defined.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.