use std::path::{Path, PathBuf};
use jni::{
jni_sig, jni_str,
objects::{JClass, JObject, JString, JThrowable, JValue},
};
use crate::{Error, Result, TaskMonitorOptions};
pub(crate) type JavaHandle = i64;
pub(crate) fn java_string(env: &mut jni::Env<'_>, value: JObject<'_>) -> Result<String> {
let value = env.cast_local::<JString>(value)?;
Ok(value.try_to_string(env)?)
}
pub(crate) fn java_optional_string(
env: &mut jni::Env<'_>,
value: JObject<'_>,
) -> Result<Option<String>> {
if value.is_null() {
return Ok(None);
}
Ok(Some(java_string(env, value)?))
}
pub(crate) fn bridge_jar_path() -> Result<PathBuf> {
option_env!("GHIDRA_BRIDGE_JAR")
.map(PathBuf::from)
.ok_or_else(|| {
Error::invalid_input(
"Ghidra bridge jar was not built; set GHIDRA_INSTALL_DIR at build time",
)
})
}
pub(crate) fn path_string(path: &Path) -> String {
path.display().to_string()
}
pub(crate) fn java_int(value: u64, field: &'static str) -> Result<i32> {
i32::try_from(value)
.map_err(|_| Error::invalid_input(format!("{field} must fit in a Java int")))
}
pub(crate) fn java_monitor_timeout_seconds(options: &TaskMonitorOptions) -> Result<i32> {
match options.timeout_seconds()? {
Some(seconds) => java_int(seconds, "task monitor timeout"),
None => Ok(-1),
}
}
pub(crate) fn jni_error(
env: &mut jni::Env<'_>,
operation: &'static str,
error: jni::errors::Error,
) -> Error {
if env.exception_check() {
let Some(exception) = env.exception_occurred() else {
return Error::Jni(error);
};
env.exception_clear();
return java_exception(env, operation, exception).unwrap_or_else(|diagnostic_error| {
Error::JavaException {
operation,
class_name: "java.lang.Throwable".to_string(),
message: format!(
"failed to inspect Java exception: {diagnostic_error}; original JNI error: {error}"
),
stack_trace: String::new(),
}
});
}
Error::Jni(error)
}
pub(crate) fn find_bridge_class<'local>(env: &mut jni::Env<'local>) -> Result<JClass<'local>> {
let loader = env
.call_static_method(
jni_str!("java/lang/ClassLoader"),
jni_str!("getSystemClassLoader"),
jni_sig!("()Ljava/lang/ClassLoader;"),
&[],
)?
.l()?;
let name = env.new_string("org.ghidra.api.GhidraApiBridge")?;
let name = JObject::from(name);
let class = match env.call_static_method(
jni_str!("java/lang/Class"),
jni_str!("forName"),
jni_sig!("(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
&[
JValue::Object(&name),
JValue::Bool(true),
JValue::Object(&loader),
],
) {
Ok(class) => class,
Err(error) => {
return Err(jni_error(
env,
"failed to load org.ghidra.api.GhidraApiBridge",
error,
));
}
};
env.cast_local::<JClass>(class.l()?).map_err(Error::from)
}
fn java_exception(
env: &mut jni::Env<'_>,
operation: &'static str,
exception: JThrowable<'_>,
) -> Result<Error> {
let class = env
.call_method(
&exception,
jni_str!("getClass"),
jni_sig!("()Ljava/lang/Class;"),
&[],
)?
.l()?;
let class_name = env
.call_method(
&class,
jni_str!("getName"),
jni_sig!("()Ljava/lang/String;"),
&[],
)?
.l()?;
let class_name = java_string(env, class_name)?;
let message = env
.call_method(
&exception,
jni_str!("getMessage"),
jni_sig!("()Ljava/lang/String;"),
&[],
)?
.l()?;
let message = java_optional_string(env, message)?.unwrap_or_default();
let stack_trace = stack_trace(env, &exception)?;
Ok(Error::JavaException {
operation,
class_name,
message,
stack_trace,
})
}
fn stack_trace(env: &mut jni::Env<'_>, exception: &JThrowable<'_>) -> Result<String> {
let writer = env.new_object(jni_str!("java/io/StringWriter"), jni_sig!("()V"), &[])?;
let print_writer = env.new_object(
jni_str!("java/io/PrintWriter"),
jni_sig!("(Ljava/io/Writer;)V"),
&[JValue::Object(&writer)],
)?;
env.call_method(
exception,
jni_str!("printStackTrace"),
jni_sig!("(Ljava/io/PrintWriter;)V"),
&[JValue::Object(&print_writer)],
)?;
let stack_trace = env
.call_method(
&writer,
jni_str!("toString"),
jni_sig!("()Ljava/lang/String;"),
&[],
)?
.l()?;
java_string(env, stack_trace)
}