From 56045a834080acf0f337391138fa05e1d62a0d66 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 20 Apr 2026 16:21:41 +0200 Subject: [PATCH] [Java.Interop] Convert dot-separated class names before calling FindClass On Android userdebug emulators with CheckJNI enabled, ART validates class name format in FindClass at the native level. Passing a dot-separated name (e.g. "java.lang.Object") causes SIGABRT before the managed Class.forName() fallback can execute. Fix by converting '.' to '/' in the class name before calling raw FindClass, so CheckJNI always sees valid JNI-format names. The Class.forName() fallback still handles classes not findable by FindClass (e.g. custom class loaders). Fixes: https://github.com/dotnet/android/pull/11157#issuecomment-4281203031 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Java.Interop/JniEnvironment.Types.cs | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs index 1f29d21cd..1463a76c1 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs @@ -51,7 +51,10 @@ static unsafe JniObjectReference TryFindClass (string classname, bool throwOnErr var info = JniEnvironment.CurrentInfo; #if FEATURE_JNIENVIRONMENT_JI_PINVOKES || FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS - if (TryRawFindClass (info.EnvironmentPointer, classname, out var c, out var thrown)) { + // Convert dot-separated names (e.g. "java.lang.Object") to JNI form ("java/lang/Object") + // before calling FindClass, because ART's CheckJNI aborts the process on dot-separated names. + var jniClassName = classname.Contains ('.') ? classname.Replace ('.', '/') : classname; + if (TryRawFindClass (info.EnvironmentPointer, jniClassName, out var c, out var thrown)) { var r = new JniObjectReference (c, JniObjectReferenceType.Local); JniEnvironment.LogCreateLocalRef (r); return r; @@ -382,32 +385,55 @@ static unsafe JniObjectReference TryFindClass (ReadOnlySpan classname, boo throw new ArgumentException ("'classname' cannot be a zero-length string.", nameof (classname)); var info = JniEnvironment.CurrentInfo; - fixed (byte* _classname_ptr = classname) { - var c = JniNativeMethods.FindClass (info.EnvironmentPointer, (IntPtr) _classname_ptr); - var thrown = JniNativeMethods.ExceptionOccurred (info.EnvironmentPointer); - if (thrown == IntPtr.Zero) { - var r = new JniObjectReference (c, JniObjectReferenceType.Local); - JniEnvironment.LogCreateLocalRef (r); - return r; - } - RawExceptionClear (info.EnvironmentPointer); - var javaName = NewJavaNameFromUtf8 (info.EnvironmentPointer, classname); - try { - if (TryLoadClassWithFallback (info, thrown, javaName, throwOnError, out var result)) - return result; - } finally { - JniObjectReference.Dispose (ref javaName); - } - if (!throwOnError) - return default; - - var terminator = classname.IndexOf ((byte) 0); - var errorClassName = terminator >= 0 - ? Encoding.UTF8.GetString (classname.Slice (0, terminator)) - : Encoding.UTF8.GetString (classname); - throw new InvalidOperationException ($"Could not find Java class '{errorClassName}'."); + var terminator = classname.IndexOf ((byte) 0); + var nameLength = terminator >= 0 ? terminator : classname.Length; + + // Convert dot-separated names (e.g. "java.lang.Object"u8) to JNI form ("java/lang/Object") + // before calling FindClass, because ART's CheckJNI aborts the process on dot-separated names. + bool hasDots = classname.Slice (0, nameLength).IndexOf ((byte) '.') >= 0; + if (!hasDots) { + return TryFindClassFromPtr (info, classname, classname, terminator, throwOnError); } + + Span jniClassName = nameLength + 1 <= 256 + ? stackalloc byte [nameLength + 1] + : new byte [nameLength + 1]; + for (int i = 0; i < nameLength; ++i) + jniClassName [i] = classname [i] == (byte) '.' ? (byte) '/' : classname [i]; + jniClassName [nameLength] = 0; + + return TryFindClassFromPtr (info, classname, jniClassName, terminator, throwOnError); + } + + static unsafe JniObjectReference TryFindClassFromPtr (JniEnvironmentInfo info, ReadOnlySpan classname, ReadOnlySpan findClassSpan, int terminator, bool throwOnError) + { + IntPtr c; + fixed (byte* _classname_ptr = findClassSpan) { + c = JniNativeMethods.FindClass (info.EnvironmentPointer, (IntPtr) _classname_ptr); + } + var thrown = JniNativeMethods.ExceptionOccurred (info.EnvironmentPointer); + if (thrown == IntPtr.Zero) { + var r = new JniObjectReference (c, JniObjectReferenceType.Local); + JniEnvironment.LogCreateLocalRef (r); + return r; + } + + RawExceptionClear (info.EnvironmentPointer); + var javaName = NewJavaNameFromUtf8 (info.EnvironmentPointer, classname); + try { + if (TryLoadClassWithFallback (info, thrown, javaName, throwOnError, out var result)) + return result; + } finally { + JniObjectReference.Dispose (ref javaName); + } + if (!throwOnError) + return default; + + var errorClassName = terminator >= 0 + ? Encoding.UTF8.GetString (classname.Slice (0, terminator)) + : Encoding.UTF8.GetString (classname); + throw new InvalidOperationException ($"Could not find Java class '{errorClassName}'."); } #endif // FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS }