Calling the JVM from Rust via JNI

Overview

Rucaja (Rust calls Java) Build Status

Calling JVM code from Rust via JNI.

Usage

JNI calls are about 10-20 times slower than regular JVM instructions. It is adviced to call as few functions as possible that do as much work as possible.

Creating a flat Java wrapper class and producing a fat JAR with all the dependencies also reduces the amount of Rust interface code.

A more complete example is kud1ing/tinkerpop-rs which uses Rucaja to call Apache TinkerPop.

Platforms

The code is tested on Linux and macOS. Your platform might need adjustments in build.rs.

Mac

Trying to run may give:

dyld: Library not loaded: @rpath/libjvm.dylib
  Referenced from: ./target/debug/rucaja
  Reason: image not found
Abort trap: 6

this might require something like:

sudo ln -s $(/usr/libexec/java_home)/jre/lib/server/libjvm.dylib /usr/local/lib

License

Licensed under either of

at your option.

Comments
  • NullPointerException in tests/java_array

    NullPointerException in tests/java_array

    I open this issue to track the NullPointerException in tests/java_array.rs (introduced in #22).

    Starting the JVM with -Xcheck:jni gives a (slightly) better error message:

    FATAL ERROR in native method: Bad global or local ref passed to JNI
    

    Disabling -Xcheck:jni and running the test with a debug JVM the message becomes more interesting:

    # To suppress the following error report, specify this argument
    # after -XX: or in .hotspotrc:  SuppressErrorAt=/jniHandles.hpp:178
    #
    # A fatal error has been detected by the Java Runtime Environment:
    #
    #  Internal Error (/home/fpoli/hg/jdk8u/hotspot/src/share/vm/runtime/jniHandles.hpp:178), pid=15159, tid=0x00007fd6ef1ed840
    #  assert(result != badJNIHandle) failed: Pointing to zapped jni handle area
    #
    # JRE version: OpenJDK Runtime Environment (8.0) (build 1.8.0-internal-fastdebug-fpoli_2017_11_21_14_33-b00)
    # Java VM: OpenJDK 64-Bit Server VM (25.71-b00-fastdebug mixed mode linux-amd64 compressed oops)
    # Core dump written. Default location: /home/fpoli/src/rust-calls-jvm/core or core.15159
    #
    # An error report file with more information is saved as:
    # /home/fpoli/src/rust-calls-jvm/hs_err_pid15159.log
    #
    # If you would like to submit a bug report, please visit:
    #   http://bugreport.java.com/bugreport/crash.jsp
    #
    

    Looking at hotspot/src/share/vm/runtime/jniHandles.hpp:178 the content is this:

    inline oop JNIHandles::resolve(jobject handle) {
      oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);
      assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");
      assert(result != badJNIHandle, "Pointing to zapped jni handle area"); // <-- line 178
      return result;
    };
    
    opened by fpoli 17
  • convert jstring to Rust string buffer

    convert jstring to Rust string buffer

    The primitive java types have direct mappings in jni_sys.

    So is there any convenient way to convert jstring (e.g., generated by jvm::call_static_object_method) to a "str"/"&[u8]"?

    In my opinion this should be reasonable since Java String is quite special. Maybe there should be an additional parameter to keep the length?

    opened by hongxuchen 9
  • Reduce `unsafe` from the function declarations to smallest possible `unsafe` blocks

    Reduce `unsafe` from the function declarations to smallest possible `unsafe` blocks

    Should we hide unsafe inside the functions or is more honest/beneficial to leak unsafe in the function declarations as it is?

    Discussion from the perspective of keep leaking unsafeness:

    • Pro:
      • what Rucaja does is possibly unsafe
        • the JNI is low-level and not well documented
        • but we try hard to use JNI correctly and thus everything we do should be safe, unless it is a bug
    • Con:
      • forces users to deal with unsafeness
    enhancement help wanted 
    opened by kud1ing 5
  • Type for holding Java objects

    Type for holding Java objects

    Currently there are JvmClass, JvmObject and JvmStrings to bring some compile time type safety into Rust regarding different JVM classes. Unfortunately there is some code duplication.

    Come up with an optimal way to hold Java objects in a Rust type that reflect the most specific Java class.

    Aspects:

    • jobject is just a type alias to *mut _jobject
      • jclass, jstring etc. are just type aliases to jobject
    • in the constructor of the wrapper
      • do not accept JVM null pointers
      • do not accept JVM objects of the wrong type
        • IsInstanceOf() compares a jobject with a jclass.

    Options:

    • Macros? See https://github.com/rawrasaur/rust-jdbc/blob/5ad63b8f06ce1ded4e5c731cbae26c69d6c4b457/src/lib.rs
      • Rust nightly not necessary anymore?
    • Tuple Struct?
      • does it help to reduce code duplication?
    • Generic type in conjunction with an enum?
    enhancement help wanted 
    opened by kud1ing 3
  • Added basic JVM method signature builder.

    Added basic JVM method signature builder.

    Needs a macro to make it actually useful, but at least it's not possible to construct an invalid JVM method signature with it as far as I know.

    I'm not a macro wizard yet, but at least the hard part is done. If we add const onto the function it will be able to evaluate it at compile time and hopefully simplify straight down to a string literal in the binary.

    I hope I didn't put it in a silly location, I haven't dived through all the code to understand it yet. Let me know if there's a better way to do this that follows the conventions for the project.

    opened by treyzania 2
  • Test object arrays

    Test object arrays

    Here I add an integration test that uses arrays built from Rucaja. Unfortunately, it fails with a NullPointerException. Either I'm doing something wrong (probably) or there is a bug in some library. Any idea?

    running 1 test
    Exception in thread "Thread-7" java.lang.NullPointerException
        at java.util.Arrays.binarySearch0(Arrays.java:2439)
        at java.util.Arrays.binarySearch(Arrays.java:2379)
    test test_java_arrays ... FAILED
    
    failures:
    
    ---- test_java_arrays stdout ----
        thread 'test_java_arrays' panicked at 'An exception occurred', src/jvm.rs:83:8
    note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
    stack backtrace:
    0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
                at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
    1: std::sys_common::backtrace::_print
                at /checkout/src/libstd/sys_common/backtrace.rs:71
    2: std::panicking::default_hook::{{closure}}
                at /checkout/src/libstd/sys_common/backtrace.rs:60
                at /checkout/src/libstd/panicking.rs:381
    3: std::panicking::default_hook
                at /checkout/src/libstd/panicking.rs:391
    4: std::panicking::rust_panic_with_hook
                at /checkout/src/libstd/panicking.rs:611
    5: std::panicking::begin_panic
                at /checkout/src/libstd/panicking.rs:572
    6: rucaja::jvm::print_and_panic_on_jvm_exception
                at src/jvm.rs:83
    7: rucaja::jvm::Jvm::call_static_int_method
                at src/jvm.rs:303
    8: java_array::test_java_arrays
                at tests/java_array.rs:44
    9: <F as test::FnBox<T>>::call_box
                at /checkout/src/libtest/lib.rs:1480
                at /checkout/src/libcore/ops/function.rs:223
                at /checkout/src/libtest/lib.rs:141
    10: __rust_maybe_catch_panic
                at /checkout/src/libpanic_unwind/lib.rs:99
    
    
    failures:
        test_java_arrays
    
    test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
    
    opened by fpoli 2
  • How to build a Java array of strings

    How to build a Java array of strings

    Is it possible to build a Java array from rucaja?

    I would like to call a Java method that takes an array of strings (eg. void main(String[] args)), but I can't find how to build the `jvalue' of the array.

    ...
    
    let java_string: JvmString = jvm.new_jvm_string("Hello").unwrap();
    
    let args: Vec<jvalue> = vec![
        // ?
    ];
    
    // Call the main
    jvm.call_static_object_method(
        &main_class,
        &main_method,
        args.as_ptr()
    );
    
    opened by fpoli 2
  • Made the build script more flexible across different platforms.

    Made the build script more flexible across different platforms.

    I'm not able to test it on macOS and Windows, but I added some comments in places for someone else to make it better more easily.

    I think we might want to take some influence from this: https://github.com/sfackler/rust-jni-sys/blob/master/systest/build.rs

    opened by treyzania 1
  • Crash

    Crash

    Process 66240 launched: './target/debug/rucaja' (x86_64) Process 66240 stopped * thread #1: tid = 0x3c105d, 0x00007fffe740be80 libsystem_platform.dylib_platform_strncmp + 320, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0) frame #0: 0x00007fffe740be80 libsystem_platform.dylib_platform_strncmp + 320 libsystem_platform.dylib`_platform_strncmp: -> 0x7fffe740be80 <+320>: movzbq (%rdi,%rcx), %rax 0x7fffe740be85 <+325>: movzbq (%rsi,%rcx), %r8 0x7fffe740be8a <+330>: subq %r8, %rax 0x7fffe740be8d <+333>: jne 0x7fffe740be9d ; <+349>

    opened by kud1ing 1
  • Refactoring

    Refactoring

    • [x] move Jvm::new_jvm_string() => JvmString::new()
    • [x] move Jvm::get_static_method() => JvmMethod::get_static_method()
    • [x] move Jvm::get_method() => JvmMethod::get_method()
    • [x] move Jvm::get_constructor() => JvmMethod::new_constructor()
    • [x] move Jvm::get_class() => JvmClass::new()
    • [x] move Jvm::call_*() to JvmMethod
    • [ ] change every struct member of type &Jvm => &JvmAttachment
    opened by kud1ing 0
  • `new_jstring()` should return a non-interned Java String

    `new_jstring()` should return a non-interned Java String

    NewStringUTF() produces a java.security.AccessControlContext and not a java.lang.String. An object of the same type is generated by static_method_that_returns_an_interned_string() in the example.

    We probably need to write a JvmString analogous to JvmObject so that the Java string is retained using NewGlobalRef() for a longer time.

    bug help wanted 
    opened by kud1ing 0
  • Provide easier to use high-level functions

    Provide easier to use high-level functions

    Currently it takes a lot of code in order to call a JVM method: resolve the class, resolve the method, build the arguments from Rust data, possibly convert the result back to Rust data types etc.

    It would be nice to be able to do this with less code in Rust. I think being more wasteful, like resolving classes/method, constructing data with each call, is fine for the sake of convenience. Users can still opt to avoid those calls by using the current wordy interface.

    The official recommendation is - and maybe we should reproduce the tip exlicitely in our documentation - is to use as few JNI calls as possible.

    The signature builder from https://github.com/kud1ing/rucaja/issues/3 supports usability too.

    opened by kud1ing 0
  • Reduce the usage of `unwrap()`

    Reduce the usage of `unwrap()`

    We use unwrap() in some places where the result should "in theory" always be existant.

    We should handle those unlikely cases. Consider:

    • bubble Nones up in functions that return an Option. Consider using ? for Options in Rust 1.22.
    • panic if JVM methods fail when we can not handle it otherwise
    opened by kud1ing 0
  • Determining linkage to `libjvm` is less than perfect, doesn't work on certain Linux distros

    Determining linkage to `libjvm` is less than perfect, doesn't work on certain Linux distros

    This won't work properly my machine as I don't have $JAVA_HOME set and I'm not on macOS.

    On my system (Xubuntu 16.04), libjvm.so lives in /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server. But there's a symlink to the java-8-openjdk-amd64 directory at /usr/lib/jvm/default-java/jre/lib/amd64/server. Different Linux distributions do things different ways so this might not work 100% the same outside of Debian-land.

    My /etc/os-release is:

    NAME="Ubuntu"
    VERSION="16.04.3 LTS (Xenial Xerus)"
    ID=ubuntu
    ID_LIKE=debian
    PRETTY_NAME="Ubuntu 16.04.3 LTS"
    VERSION_ID="16.04"
    HOME_URL="http://www.ubuntu.com/"
    SUPPORT_URL="http://help.ubuntu.com/"
    BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
    VERSION_CODENAME=xenial
    UBUNTU_CODENAME=xenial
    

    The problem is that not all Linux distributions have an OS release file like this, so it's actually really hard to figure out where libjvm.so is supposed to live. But worse-case scenario we can just search through /usr/lib/jvm until we find a directory with libjvm.so in it, as most people don't have enough JVMs installed to make that unreasonably slow.

    I might make a pull request soon with a fix in build.rs that'll probably work on Debians, but I don't really have anything to test it with beyond cargo test.

    opened by treyzania 4
  • Multi-thread use case

    Multi-thread use case

    My code is in a multi-thread setting, inside each thread, there is a need to call jvm functions.

    I find that the "jvm" instance itself cannot be constructed separately inside each thread, by loading the classpath with proper options. In this case, it will panic with a message:

     '`JNI_CreateJavaVM()` signaled an error: JVM exists already'` ...rucaja-0.4.0/src/jvm.rs:186:12
    

    This alternative seems to be putting JVM construction code before spawning threads and then passing as parameters.

    The problem is that I'm not quite sure whether this will cause data race internally at the JVM side.

    Since I know little about JNI and would like to be merely a "user" of rucaja, Could you please advise?

    opened by hongxuchen 6
Rust definitions corresponding to jni.h.

jni-sys Documentation Rust definitions corresponding to jni.h. License Licensed under either of Apache License, Version 2.0, (LICENSE-APACHE or http:/

Steven Fackler 36 Nov 6, 2022
Rust bindings to the Java Native Interface — JNI

JNI Bindings for Rust This project provides complete JNI bindings for Rust, allowing to: Implement native Java methods for JVM and Android in Rust Cal

null 768 Dec 30, 2022
A JVM written in Rust.

Kate Kate is a JVM written in Rust, it's a passion project that aims to improve my knowledge of systems development and JVM internals. Contributions a

Amy 20 Dec 21, 2022
A minimal library for building compiled Node.js add-ons in Rust via Node-API

A minimal library for building compiled Node.js add-ons in Rust via Node-API

Node-API (N-API) for Rust 3.1k Dec 29, 2022
Rust bindings for Supabase JavaScript library via WebAssembly.

supabase-js-rs Rust bindings for Supabase JavaScript library via WebAssembly. Usage Add supabase-js-rs to Cargo.toml supabase-js-rs = { version = "0.1

Valery Stepanov 8 Jan 13, 2023
lzma-rs binding to Node.js via napi-rs.

@napi-rs/lzma lzma-rs binding to Node.js via napi-rs. ?? Help me to become a full-time open-source developer by sponsoring me on Github Install yarn a

LongYinan 8 Aug 16, 2022
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Backtrace Labs 133 Dec 5, 2022
A Rust crate for automatically generating C header files from Rust source file.

Please be aware that this crate is no longer actively maintained, please look into the much more feature rich cbindgen instead. rusty-cheddar rusty-ch

Sean Marshallsay 190 Nov 12, 2022
Rust-ffi-guide - A guide for doing FFI using Rust

Using unsafe for Fun and Profit A guide to traversing the FFI boundary between Rust and other languages. A rendered version is available here. This gu

Michael Bryan 261 Dec 1, 2022
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022
Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

Lightning Digital Entertainment 5 Feb 28, 2024
A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Ryan Hunt 1.7k Jan 3, 2023
Automatically generates Rust FFI bindings to C (and some C++) libraries.

bindgen bindgen automatically generates Rust FFI bindings to C (and some C++) libraries. For example, given the C header doggo.h: typedef struct Doggo

The Rust Programming Language 3.2k Jan 4, 2023
Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

David Tolnay 4.4k Jan 7, 2023
Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Rusterlium 3.5k Jan 7, 2023
Bridge the gap between Haskell and Rust

Curryrs Curryrs (a play on the name of Haskell Curry, rs for Rust libraries, and it's pronunciation couriers) is a library for providing easy to use b

Michael Gattozzi 296 Oct 18, 2022
Rust in Haskell FFI Example

Provides an example for using Rust in Haskell. To use this you'll need cargo, rustc, cabal and GHC installed. To execute the example run the following

Michael Gattozzi 21 Oct 1, 2022
Run Java code from Rust!

Java Native Interface Bindings for Rust This library provides complete FFI bindings to the Java Native Interface, as well as a safe and intuitive wrap

Ben Anderson 66 Nov 28, 2022
Embedding Rust in Java

Java/Rust Example An example project showing how to call into Rust code from Java. OSX Linux Windows Requirements Java 7+ Rust (tested with 1.0, night

drrb 318 Jan 1, 2023