-
Notifications
You must be signed in to change notification settings - Fork 3
Open
Description
New methods for various classes are continuously added in the Android API, and the programmer may forget to check SDK_INT before calling some method added in a brand new Android version if its android.jar is used for the bindgen. How about checking for the existence of the method with Java reflection in Env::require_method? Currently the usage of each method involves require_method for once (then it is cached), so there would be no significant performance issue.
Initial draft of method existence checking
impl<'env> Env<'env> {
/// Checks if a method exists. If not, JNI `GetMethodID` or `RegisterNatives` will crash the program.
pub(crate) unsafe fn ensure_method_exists(self, class: &JClass, method: &CStr, descriptor: &CStr) -> bool {
static GET_METHODS: OnceLock<JMethodID> = OnceLock::new();
let get_methods = GET_METHODS.get_or_init(|| {
let class_class = self.require_class_jni(c"java/lang/Class").unwrap();
self.require_method(&class_class, c"getMethods", c"()[Ljava/lang/reflect/Method;")
});
let jnienv = self.as_raw();
let arr_methods = ((**jnienv).v1_2.CallObjectMethod)(jnienv, class.as_raw(), get_methods.as_raw());
if self.exception_check_raw().is_err() || arr_methods.is_null() {
return false;
}
let found = Self::find_in_method_array(self, arr_methods, method, descriptor);
((**jnienv).v1_2.DeleteLocalRef)(jnienv, arr_methods);
if found {
return true;
}
static GET_DECLARED_METHODS: OnceLock<JMethodID> = OnceLock::new();
let get_declared_methods = GET_DECLARED_METHODS.get_or_init(|| {
let class_class = self.require_class_jni(c"java/lang/Class").unwrap();
self.require_method(&class_class, c"getDeclaredMethods", c"()[Ljava/lang/reflect/Method;")
});
let arr_methods = ((**jnienv).v1_2.CallObjectMethod)(jnienv, class.as_raw(), get_declared_methods.as_raw());
if self.exception_check_raw().is_err() || arr_methods.is_null() {
return false;
}
let found = Self::find_in_method_array(self, arr_methods, method, descriptor);
((**jnienv).v1_2.DeleteLocalRef)(jnienv, arr_methods);
return found;
}
unsafe fn find_in_method_array(self, arr_methods: jobject, method: &CStr, descriptor: &CStr) -> bool {
static TO_STRING: OnceLock<JMethodID> = OnceLock::new();
let to_string = TO_STRING.get_or_init(|| {
// Prevent class "java.lang.reflect.Method" from being unloaded.
let method_class = Box::leak(Box::new(self.require_class_jni(c"java/lang/reflect/Method").unwrap()));
self.require_method(method_class, c"toString", c"()Ljava/lang/String;")
});
let jnienv = self.as_raw();
let len = ((**jnienv).v1_2.GetArrayLength)(jnienv, arr_methods);
let mut found = false;
for i in 0..len {
let method_obj = ((**jnienv).v1_2.GetObjectArrayElement)(jnienv, arr_methods, i);
if self.exception_check_raw().is_err() || method_obj.is_null() {
continue;
}
let method_jstring = ((**jnienv).v1_2.CallObjectMethod)(jnienv, method_obj, to_string.as_raw());
if self.exception_check_raw().is_err() || method_jstring.is_null() {
((**jnienv).v1_2.DeleteLocalRef)(jnienv, method_obj);
continue;
}
let method_string = StringChars::from_env_jstring(self, method_jstring).to_string_lossy();
((**jnienv).v1_2.DeleteLocalRef)(jnienv, method_jstring);
((**jnienv).v1_2.DeleteLocalRef)(jnienv, method_obj);
let (name, desc) = Self::java_method_string_to_name_desc(&method_string);
if name == method.to_string_lossy() && desc == descriptor.to_string_lossy() {
found = true;
break;
}
}
found
}
// See <https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html#toString-->.
fn java_method_string_to_name_desc(method_string: &str) -> (&str, String) {
let (Some(i_par_l), Some(i_par_r)) = (method_string.find('('), method_string.rfind(')')) else {
return ("", String::new());
};
let params_str = &method_string[i_par_l + 1..i_par_r];
let mut parts: Vec<_> = method_string[..i_par_l].split_whitespace().collect();
let method_name = parts.pop().unwrap_or("").trim().split('.').next_back().unwrap_or("");
let return_type = parts.pop().unwrap_or("").trim();
let mut desc = "(".to_string();
for param_type in params_str.split(',') {
Self::write_type_desc(param_type, &mut desc);
}
desc.push(')');
Self::write_type_desc(return_type, &mut desc);
(method_name, desc)
}
fn write_type_desc(java_name: &str, desc: &mut String) {
let mut param = java_name.trim();
while param.ends_with("[]") {
desc.push('[');
param = ¶m[..param.len() - 2];
}
let mut param_class = None;
#[rustfmt::skip]
let param_prim = match param {
"boolean" => "Z", "byte" => "B", "char" => "C", "short" => "S",
"int" => "I", "long" => "J", "float" => "F", "double" => "D",
"void" => "V", cls => { param_class.replace(cls.replace('.', "/")); "" },
};
if let Some(cls) = param_class {
desc.push('L');
desc.push_str(&cls);
desc.push(';');
} else {
desc.push_str(param_prim);
}
}
}
#[test]
fn java_method_string_to_name_desc_test() {
let method = "public final void java.lang.Object.wait(long) throws java.lang.InterruptedException";
let (name, desc) = Env::java_method_string_to_name_desc(method);
assert!(name == "wait" && desc == "(J)V");
let method = "public java.lang.String[][] com.test.SomeType.someMethod(boolean,java.lang.Object[],long,com.test.SomeType$Sub) ";
let (name, desc) = Env::java_method_string_to_name_desc(method);
assert!(name == "someMethod" && desc == "(Z[Ljava/lang/Object;JLcom/test/SomeType$Sub;)[[Ljava/lang/String;");
}Metadata
Metadata
Assignees
Labels
No labels