Skip to content

Commit 06b1d7f

Browse files
dependabot[bot]jonpryorgrendello
authored
[monodroid] typemaps may need to load assemblies (dotnet#8625)
Context: dotnet/java-interop@005c914 Context: dotnet/java-interop#1181 Context: 25d1f00 When attempting to bump to dotnet/java-interop@005c9141, multiple unit tests would fail, e.g. Java.Lang.LinkageError : net.dot.jni.test.CallVirtualFromConstructorDerived ----> System.NotSupportedException : Could not find System.Type corresponding to Java type JniTypeSignature(TypeName=net/dot/jni/test/CallVirtualFromConstructorDerived ArrayRank=0 Keyword=False) . This happened because dotnet/java-interop@005c9141 implicitly required that typemaps exist for `Java.Interop.JavaObject` subclasses. Fair enough; enter xamasrin/java.interop#1181, which added support to `Java.Interop.Tools.JavaCallableWrappers` to emit typemaps for `Java.Interop.JavaObject` subclasses. That caused *crashes* in `tests/Mono.Android-Tests`: E droid.NET_Test: JNI ERROR (app bug): accessed stale Local 0x75 (index 7 in a table of size 7) F droid.NET_Test: java_vm_ext.cc:570] JNI DETECTED ERROR IN APPLICATION: use of deleted local reference 0x75 … F droid.NET_Test: runtime.cc:630] native: #13 pc 00000000003ce865 /apex/com.android.runtime/lib64/libart.so (art::(anonymous namespace)::CheckJNI::GetObjectClass(_JNIEnv*, _jobject*)+837) The immediate cause of the crash was a "use after free" bug within `TypeManager.CreateInstance()` in a never-hit-before error path; the "use after free" bug was fixed in 25d1f00. However, the cause of the hitting a never-hit-before error path is because `EmbeddedAssemblies::typemap_java_to_managed()` would only map Java types to `System.Type` instances for assemblies that have *already been loaded*. If the assembly had not yet been loaded, then `EmbeddedAssemblies::typemap_java_to_managed()` would return `null`, and if the binding it couldn't find happens to be for `java.lang.Object`, we hit the (buggy!) "Where is the Java.Lang.Object wrapper" error condition. Commit 25d1f00 fixes that and a great many other related issues. What's left is `EmbeddedAssemblies::typemap_java_to_managed()`: it should *never* return null *unless* there is no typemap at all. Whether the target assembly has been loaded or not should be irrelevant. Update `EmbeddedAssemblies::typemap_java_to_managed()` so that it will load the target assembly if necessary. Additionally, before we figured out that we had a "use after free" bug, all we had to go on was that *something* related to `JNIEnv::GetObjectClass()` was involved. Review JNI usage around `JNIEnv::GetObjectClass()` and related invocations, and cleanup: * Simplify logic related to `JNIEnv::DeleteLocalRef()`. * Decrease scope of local variables. * Clear variables passed to `JNIEnv.DeleteLocalRef()`. Co-authored-by: Jonathan Pryor <jonpryor@vt.edu> Co-authored-by: Marek Habersack <grendel@twistedcode.net>
1 parent bf73889 commit 06b1d7f

File tree

3 files changed

+36
-9
lines changed

3 files changed

+36
-9
lines changed

src/Mono.Android/Java.Interop/TypeManager.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership
267267
{
268268
Type? type = null;
269269
IntPtr class_ptr = JNIEnv.GetObjectClass (handle);
270-
string class_name = GetClassName (class_ptr);
270+
string? class_name = GetClassName (class_ptr);
271271
lock (TypeManagerMapDictionaries.AccessLock) {
272272
while (class_ptr != IntPtr.Zero && !TypeManagerMapDictionaries.JniToManaged.TryGetValue (class_name, out type)) {
273273

@@ -279,12 +279,18 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership
279279

280280
IntPtr super_class_ptr = JNIEnv.GetSuperclass (class_ptr);
281281
JNIEnv.DeleteLocalRef (class_ptr);
282+
class_name = null;
282283
class_ptr = super_class_ptr;
283-
class_name = GetClassName (class_ptr);
284+
if (class_ptr != IntPtr.Zero) {
285+
class_name = GetClassName (class_ptr);
286+
}
284287
}
285288
}
286289

287-
JNIEnv.DeleteLocalRef (class_ptr);
290+
if (class_ptr != IntPtr.Zero) {
291+
JNIEnv.DeleteLocalRef (class_ptr);
292+
class_ptr = IntPtr.Zero;
293+
}
288294

289295
if (targetType != null &&
290296
(type == null ||

src/monodroid/jni/embedded-assemblies.cc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,30 @@ EmbeddedAssemblies::typemap_java_to_managed (hash_t hash, const MonoString *java
695695

696696
if (module->image == nullptr) {
697697
module->image = mono_image_loaded (module->assembly_name);
698+
699+
if (module->image == nullptr) {
700+
log_debug (LOG_ASSEMBLY, "typemap: assembly '%s' hasn't been loaded yet, attempting a full load", module->assembly_name);
701+
702+
// Fake a request from MonoVM to load the assembly.
703+
MonoAssemblyName *assembly_name = mono_assembly_name_new (module->assembly_name);
704+
MonoAssembly *assm;
705+
706+
if (assembly_name == nullptr) {
707+
log_error (LOG_ASSEMBLY, "typemap: failed to create Mono assembly name for '%s'", module->assembly_name);
708+
assm = nullptr;
709+
} else {
710+
MonoAssemblyLoadContextGCHandle alc_gchandle = mono_alc_get_default_gchandle ();
711+
MonoError mono_error;
712+
assm = embeddedAssemblies.open_from_bundles (assembly_name, alc_gchandle, &mono_error, false /* ref_only */);
713+
}
714+
715+
if (assm == nullptr) {
716+
log_warn (LOG_ASSEMBLY, "typemap: failed to load managed assembly '%s'", module->assembly_name);
717+
} else {
718+
module->image = mono_assembly_get_image (assm);
719+
}
720+
}
721+
698722
if (module->image == nullptr) {
699723
log_error (LOG_ASSEMBLY, "typemap: unable to load assembly '%s' when looking up managed type corresponding to Java type '%s'", module->assembly_name, to_utf8 (java_type_name).get ());
700724
return nullptr;

src/monodroid/jni/osbridge.cc

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -657,15 +657,13 @@ OSBridge::add_reference_jobject (JNIEnv *env, jobject handle, jobject reffed_han
657657

658658
java_class = env->GetObjectClass (handle);
659659
add_method_id = env->GetMethodID (java_class, "monodroidAddReference", "(Ljava/lang/Object;)V");
660+
env->DeleteLocalRef (java_class);
660661
if (add_method_id) {
661662
env->CallVoidMethod (handle, add_method_id, reffed_handle);
662-
env->DeleteLocalRef (java_class);
663-
664663
return 1;
665664
}
666665

667666
env->ExceptionClear ();
668-
env->DeleteLocalRef (java_class);
669667
return 0;
670668
}
671669

@@ -910,7 +908,6 @@ OSBridge::gc_cleanup_after_java_collection (JNIEnv *env, int num_sccs, MonoGCBri
910908
MonoClass *klass;
911909
#endif
912910
MonoObject *obj;
913-
jclass java_class;
914911
jobject jref;
915912
jmethodID clear_method_id;
916913
int i, j, total, alive, refs_added;
@@ -942,8 +939,9 @@ OSBridge::gc_cleanup_after_java_collection (JNIEnv *env, int num_sccs, MonoGCBri
942939
sccs [i]->is_alive = 1;
943940
mono_field_get_value (obj, bridge_info->refs_added, &refs_added);
944941
if (refs_added) {
945-
java_class = env->GetObjectClass (jref);
942+
jclass java_class = env->GetObjectClass (jref);
946943
clear_method_id = env->GetMethodID (java_class, "monodroidClearReferences", "()V");
944+
env->DeleteLocalRef (java_class);
947945
if (clear_method_id) {
948946
env->CallVoidMethod (jref, clear_method_id);
949947
} else {
@@ -957,7 +955,6 @@ OSBridge::gc_cleanup_after_java_collection (JNIEnv *env, int num_sccs, MonoGCBri
957955
}
958956
#endif
959957
}
960-
env->DeleteLocalRef (java_class);
961958
}
962959
} else {
963960
abort_unless (!sccs [i]->is_alive, "Bridge SCC at index %d must NOT be alive", i);

0 commit comments

Comments
 (0)