Compare commits

..
1 Commits
Author SHA1 Message Date
swaits aeb5568727 manganis: load FFI classes from the activity classloader, not FindClass
JNIEnv::find_class resolves against the system classloader on a thread
attached from native code, which cannot see APK classes (#5629). Route
the generated #[manganis::ffi] type lookup through
load_class_from_classloader, and reimplement that helper to ask the
activity's own ClassLoader.loadClass instead of Class.forName.
2026-09-05 20:34:33 -06:00
4 changed files with 14 additions and 34 deletions

No files matched your search

@@ -17,7 +17,7 @@
{{{ raw_manifest }}}
<application android:hasCode="true" android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="false"
android:extractNativeLibs="true"
android:allowNativeHeapPointerTagging="false"
android:label="@string/app_name"
android:supportsRtl="{{#if supports_rtl}}{{ supports_rtl }}{{else}}true{{/if}}"
@@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.13.0")
classpath("com.android.tools.build:gradle:8.7.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20")
}
}
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+11 -31
View File
@@ -126,41 +126,21 @@ pub fn load_class_from_classloader<'env>(
class_name: &str,
) -> Result<JClass<'env>> {
let binary_name = class_name.replace('/', ".");
// Ask the activity INSTANCE for its classloader. `Activity` is a `Context`, and
// `Context.getClassLoader()` is documented to return a loader for the application
// package unconditionally. Do not route this through `get_object_class(activity)`:
// that calls `Class.getClassLoader()`, which asks which loader loaded the Activity's
// own class and is specified to return null for anything the bootstrap loader
// loaded -- a null receiver that then fails obscurely at the `loadClass` below.
let loader = env.call_method(activity, "getClassLoader", "()Ljava/lang/ClassLoader;", &[]);
let loader = describe_and_clear(env, loader)?.l()?;
let class_name_jstring = new_string(env, &binary_name);
let class_name_jstring = describe_and_clear(env, class_name_jstring)?;
let activity_class = env.get_object_class(activity)?;
let loader = env
.call_method(
&activity_class,
"getClassLoader",
"()Ljava/lang/ClassLoader;",
&[],
)?
.l()?;
let class_name_jstring = new_string(env, &binary_name)?;
let class = env.call_method(
&loader,
"loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;",
&[JValue::Object(&class_name_jstring)],
);
let class = describe_and_clear(env, class)?;
)?;
Ok(class.l()?.into())
}
/// Log and clear a pending Java exception before returning an `Err`.
///
/// jni 0.21 does NOT clear the pending exception when a call returns `Err`
/// (see its `JNIEnv` docs), and any later JNI call made while one is pending
/// aborts the process with "JNI DETECTED ERROR IN APPLICATION: ... called with
/// pending exception". That is what turns a recoverable `ClassNotFoundException`
/// into an unreadable ART abort. Describe it to logcat so the cause is visible,
/// then clear it so the `Err` can travel normally.
fn describe_and_clear<T>(env: &mut JNIEnv<'_>, result: Result<T>) -> Result<T> {
if result.is_err() && env.exception_check().unwrap_or(false) {
let _ = env.exception_describe();
let _ = env.exception_clear();
}
result
}