Skip to content

Commit

Permalink
#98: Fill up the J4rsError with actual exceptions when error comes fr…
Browse files Browse the repository at this point in the history
…om Java.
  • Loading branch information
astonbitecode committed Apr 18, 2024
1 parent 0865d91 commit 7361629
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 32 deletions.
65 changes: 39 additions & 26 deletions java/src/main/java/org/astonbitecode/j4rs/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
*/
package org.astonbitecode.j4rs.utils;

import org.astonbitecode.j4rs.api.dtos.GeneratedArg;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;

import org.astonbitecode.j4rs.api.dtos.GeneratedArg;

public class Utils {

private static boolean IsAndroid;
Expand All @@ -33,30 +35,30 @@ public class Utils {

public static Class<?> forNameEnhanced(final String className) throws ClassNotFoundException {
switch (className) {
case "boolean":
return boolean.class;
case "byte":
return byte.class;
case "short":
return short.class;
case "int":
return int.class;
case "long":
return long.class;
case "float":
return float.class;
case "double":
return double.class;
case "char":
return char.class;
case "void":
return void.class;
default:
if (!IsAndroid) {
return Class.forName(className, true, ClassLoader.getSystemClassLoader());
} else {
return Class.forName(className);
}
case "boolean":
return boolean.class;
case "byte":
return byte.class;
case "short":
return short.class;
case "int":
return int.class;
case "long":
return long.class;
case "float":
return float.class;
case "double":
return double.class;
case "char":
return char.class;
case "void":
return void.class;
default:
if (!IsAndroid) {
return Class.forName(className, true, ClassLoader.getSystemClassLoader());
} else {
return Class.forName(className);
}
}
}

Expand All @@ -67,4 +69,15 @@ public static Class<?> forNameEnhanced(final String className) throws ClassNotFo
public static Class<?> forNameBasedOnArgs(final GeneratedArg[] params) {
return Arrays.stream(params).map(arg -> arg.getClazz()).reduce((a, b) -> a).orElse(Void.class);
}

public static String throwableToString(Throwable throwable) {
if (throwable != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
} else {
return "Cannot create String out of a null Throwable";
}
}
}
Binary file modified rust/jassets/j4rs-0.19.0-SNAPSHOT-jar-with-dependencies.jar
Binary file not shown.
33 changes: 28 additions & 5 deletions rust/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ impl Jvm {
let ed = cache::get_jni_exception_describe().or_else(|| {
cache::set_jni_exception_describe(Some((**jni_environment).v1_6.ExceptionDescribe))
});
let _ = cache::get_jni_exception_occured().or_else(|| {
cache::set_jni_exception_occured(Some((**jni_environment).v1_6.ExceptionOccurred))
});
let exclear = cache::get_jni_exception_clear().or_else(|| {
cache::set_jni_exception_clear(Some((**jni_environment).v1_6.ExceptionClear))
});
Expand Down Expand Up @@ -1338,18 +1341,26 @@ impl Jvm {
pub(crate) fn do_return<T>(jni_env: *mut JNIEnv, to_return: T) -> errors::Result<T> {
unsafe {
if (opt_to_res(cache::get_jni_exception_check())?)(jni_env) == JNI_TRUE {
(opt_to_res(cache::get_jni_exception_describe())?)(jni_env);
let throwable = (opt_to_res(cache::get_jni_exception_occured())?)(jni_env);
let throwable_string = Self::get_throwable_string(throwable, jni_env)?;
(opt_to_res(cache::get_jni_exception_clear())?)(jni_env);
Err(J4RsError::JavaError(
"An Exception was thrown by Java... Please check the logs or the console."
.to_string(),
))
Err(J4RsError::JavaError(throwable_string))
} else {
Ok(to_return)
}
}
}

unsafe fn get_throwable_string(throwable: jobject, jni_env: *mut JNIEnv) -> errors::Result<String> {
let java_string = (opt_to_res(cache::get_jni_call_static_object_method())?)(
jni_env,
cache::get_utils_class()?,
cache::get_utils_exception_to_string_method()?,
throwable,
);
jni_utils::string_from_jobject(java_string, jni_env)
}

// Retrieves a JNIEnv in the case that a JVM is already created even from another thread.
fn get_created_vm() -> Option<*mut JNIEnv> {
unsafe {
Expand Down Expand Up @@ -2088,4 +2099,16 @@ mod api_unit_tests {
let _ = jvm.create_instance("java.lang.String", &[inv_arg1])?;
Ok(())
}

#[test]
fn exception_string_in_the_result() -> errors::Result<()> {
let jvm = create_tests_jvm()?;

let res = jvm.create_instance("non.Existing", InvocationArg::empty());
assert!(res.is_err());
let exception_sttring = format!("{}",res.err().unwrap());
assert!(exception_sttring.contains("Cannot create instance of non.Existing"));

Ok(())
}
}
76 changes: 75 additions & 1 deletion rust/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use std::sync::Mutex;

use jni_sys::{
self, jboolean, jbyte, jclass, jdouble, jfloat, jint, jlong, jmethodID, jobject, jobjectArray,
jshort, jsize, jstring, JNIEnv,
jshort, jsize, jstring, jthrowable, JNIEnv,
};
use libc::c_char;

Expand All @@ -28,6 +28,7 @@ use crate::{api_tweaks as tweaks, errors, jni_utils, utils};

pub(crate) const INST_CLASS_NAME: &'static str =
"org/astonbitecode/j4rs/api/instantiation/NativeInstantiationImpl";
pub(crate) const UTILS_CLASS_NAME: &'static str = "org/astonbitecode/j4rs/utils/Utils";
pub(crate) const INVO_BASE_NAME: &'static str = "org/astonbitecode/j4rs/api/InstanceBase";
pub(crate) const INVO_IFACE_NAME: &'static str = "org/astonbitecode/j4rs/api/Instance";
pub(crate) const UNKNOWN_FOR_RUST: &'static str = "known_in_java_world";
Expand Down Expand Up @@ -99,6 +100,7 @@ pub(crate) type JniSetObjectArrayElement = unsafe extern "system" fn(
);
pub(crate) type JniExceptionCheck = unsafe extern "system" fn(_: *mut JNIEnv) -> jboolean;
pub(crate) type JniExceptionDescribe = unsafe extern "system" fn(_: *mut JNIEnv);
pub(crate) type JniExceptionOccured = unsafe extern "system" fn(_: *mut JNIEnv) -> jthrowable;
pub(crate) type JniExceptionClear = unsafe extern "system" fn(_: *mut JNIEnv);
pub(crate) type JniDeleteLocalRef = unsafe extern "system" fn(_: *mut JNIEnv, _: jobject) -> ();
pub(crate) type JniDeleteGlobalRef = unsafe extern "system" fn(_: *mut JNIEnv, _: jobject) -> ();
Expand Down Expand Up @@ -139,12 +141,17 @@ thread_local! {
pub(crate) static JNI_SET_OBJECT_ARRAY_ELEMENT: RefCell<Option<JniSetObjectArrayElement>> = RefCell::new(None);
pub(crate) static JNI_EXCEPTION_CHECK: RefCell<Option<JniExceptionCheck>> = RefCell::new(None);
pub(crate) static JNI_EXCEPTION_DESCRIBE: RefCell<Option<JniExceptionDescribe>> = RefCell::new(None);
pub(crate) static JNI_EXCEPTION_OCCURED: RefCell<Option<JniExceptionOccured>> = RefCell::new(None);
pub(crate) static JNI_EXCEPTION_CLEAR: RefCell<Option<JniExceptionClear>> = RefCell::new(None);
pub(crate) static JNI_DELETE_LOCAL_REF: RefCell<Option<JniDeleteLocalRef>> = RefCell::new(None);
pub(crate) static JNI_DELETE_GLOBAL_REF: RefCell<Option<JniDeleteGlobalRef>> = RefCell::new(None);
pub(crate) static JNI_NEW_GLOBAL_REF: RefCell<Option<JniNewGlobalRef>> = RefCell::new(None);
pub(crate) static JNI_THROW_NEW: RefCell<Option<JniThrowNew>> = RefCell::new(None);
pub(crate) static JNI_IS_SAME_OBJECT: RefCell<Option<JniIsSameObject>> = RefCell::new(None);
// This is the Utils class.
pub(crate) static UTILS_CLASS: RefCell<Option<jclass>> = RefCell::new(None);
// Utils throwableToString method
pub(crate) static UTILS_THROWABLE_TO_STRING_METHOD: RefCell<Option<jmethodID>> = RefCell::new(None);
// This is the factory class. It creates instances using reflection. Currently the `NativeInstantiationImpl`.
pub(crate) static FACTORY_CLASS: RefCell<Option<jclass>> = RefCell::new(None);
// The constructor method of the `NativeInstantiationImpl`.
Expand Down Expand Up @@ -527,6 +534,20 @@ pub(crate) fn get_jni_exception_describe() -> Option<JniExceptionDescribe> {
JNI_EXCEPTION_DESCRIBE.with(|opt| *opt.borrow())
}

pub(crate) fn set_jni_exception_occured(
j: Option<JniExceptionOccured>,
) -> Option<JniExceptionOccured> {
debug("Called set_jni_exception_occured");
JNI_EXCEPTION_OCCURED.with(|opt| {
*opt.borrow_mut() = j;
});
get_jni_exception_occured()
}

pub(crate) fn get_jni_exception_occured() -> Option<JniExceptionOccured> {
JNI_EXCEPTION_OCCURED.with(|opt| *opt.borrow())
}

pub(crate) fn set_jni_exception_clear(j: Option<JniExceptionClear>) -> Option<JniExceptionClear> {
debug("Called set_jni_exception_clear");
JNI_EXCEPTION_CLEAR.with(|opt| {
Expand Down Expand Up @@ -620,6 +641,59 @@ pub(crate) fn get_factory_class() -> errors::Result<jclass> {
)
}

pub(crate) fn set_utils_class(j: jclass) {
debug("Called set_utils_class");
UTILS_CLASS.with(|opt| {
*opt.borrow_mut() = Some(j);
});
}

pub(crate) fn get_utils_class() -> errors::Result<jclass> {
get_cached!(
UTILS_CLASS,
{
let env = get_thread_local_env()?;
let c = tweaks::find_class(env, UTILS_CLASS_NAME)?;
jni_utils::create_global_ref_from_local_ref(c, env)?
},
set_utils_class
)
}

pub(crate) fn set_utils_exception_to_string_method(j: jmethodID) {
debug("Called set_utils_exception_to_string_method");
UTILS_THROWABLE_TO_STRING_METHOD.with(|opt| {
*opt.borrow_mut() = Some(j);
});
}

pub(crate) fn get_utils_exception_to_string_method() -> errors::Result<jmethodID> {
get_cached!(
UTILS_THROWABLE_TO_STRING_METHOD,
{
let env = get_thread_local_env()?;
let throwable_to_string_method_signature = format!(
"(Ljava/lang/Throwable;)Ljava/lang/String;"
);
let cstr1 = utils::to_c_string("throwableToString");
let cstr2 = utils::to_c_string(&throwable_to_string_method_signature);
let j = unsafe {
(opt_to_res(get_jni_get_static_method_id())?)(
env,
get_utils_class()?,
cstr1,
cstr2,
)
};
utils::drop_c_string(cstr1);
utils::drop_c_string(cstr2);

j
},
set_utils_exception_to_string_method
)
}

pub(crate) fn set_invocation_arg_class(j: jclass) {
debug("Called set_invocation_arg_class");
INVOCATION_ARG_CLASS.with(|opt| {
Expand Down

0 comments on commit 7361629

Please sign in to comment.