diff --git a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs index d8b9f652d..bcd76a417 100644 --- a/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs +++ b/src/Java.Interop/Java.Interop/JniEnvironment.Types.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Collections.Generic; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; @@ -256,7 +257,7 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi RegisterNatives (type, methods, methods == null ? 0 : methods.Length); } - public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods) + public static unsafe void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods) { if ((numMethods < 0) || (numMethods > (methods?.Length ?? 0))) { @@ -275,14 +276,45 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi } #endif // DEBUG - int r = _RegisterNatives (type, methods ?? Array.Empty(), numMethods); + if (numMethods == 0 || methods == null) { + return; + } - if (r != 0) { - throw new InvalidOperationException ( - string.Format ("Could not register native methods for class '{0}'; JNIEnv::RegisterNatives() returned {1}.", GetJniTypeNameFromClass (type), r)); + // Marshal the non-blittable JniNativeMethodRegistration[] into blittable JniNativeMethod + // values and dispatch to the blittable overload, instead of invoking the JNI + // `RegisterNatives` function pointer with a non-blittable managed-array parameter. + // The runtime marshalling stub synthesized for such a `delegate* unmanaged<>` call is + // miscompiled by crossgen2 under composite ReadyToRun + PGO: the JniNativeMethod `name` + // pointers end up referencing the managed `string` objects instead of marshalled UTF-8 + // data, which corrupts the registered method names. See https://github.com/dotnet/android/issues/11633. + var natives = new JniNativeMethod [numMethods]; + var unmanagedStrings = new IntPtr [numMethods * 2]; + try { + for (int i = 0; i < numMethods; ++i) { + var m = methods [i]; + IntPtr name = Marshal.StringToCoTaskMemUTF8 (m.Name); + IntPtr sig = Marshal.StringToCoTaskMemUTF8 (m.Signature); + unmanagedStrings [i * 2] = name; + unmanagedStrings [i * 2 + 1] = sig; + natives [i] = new JniNativeMethod ((byte*) name, (byte*) sig, GetFunctionPointerForDelegate (m.Marshaler)); + } + RegisterNatives (type, new ReadOnlySpan (natives, 0, numMethods)); + // Keep the Marshaler delegates alive at least until JNI has consumed the function pointers. + GC.KeepAlive (methods); + } finally { + for (int i = 0; i < unmanagedStrings.Length; ++i) { + Marshal.ZeroFreeCoTaskMemUTF8 (unmanagedStrings [i]); + } } } + // Native method registration via JniNativeMethodRegistration[] only runs on JIT-capable + // runtimes (MonoVM/CoreCLR). Under NativeAOT, native methods are registered through the + // trimmable type map using statically-compiled function pointers, so this path is never reached. + [UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Not reached under NativeAOT; only JIT-capable runtimes register via JniNativeMethodRegistration[].")] + static IntPtr GetFunctionPointerForDelegate (Delegate marshaler) => + Marshal.GetFunctionPointerForDelegate (marshaler); + /// /// Registers JNI native methods using blittable structs /// with raw function pointers and UTF-8 name/signature pointers. @@ -290,7 +322,11 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi /// public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan methods) { + if (!type.IsValid) + throw new ArgumentException ("Handle must be valid.", nameof (type)); + IntPtr env = JniEnvironment.EnvironmentPointer; + int r; fixed (JniNativeMethod* methodsPtr = methods) { #if FEATURE_JNIENVIRONMENT_JI_FUNCTION_POINTERS var registerNatives = (delegate* unmanaged) @@ -299,10 +335,19 @@ public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan var registerNatives = (delegate* unmanaged) JniEnvironment.CurrentInfo.Invoker.env.RegisterNatives; #endif - int r = registerNatives (env, type.Handle, methodsPtr, methods.Length); - if (r != 0) { - throw new InvalidOperationException ($"Could not register native methods for class '{GetJniTypeNameFromClass (type)}'; JNIEnv::RegisterNatives() returned {r}."); - } + r = registerNatives (env, type.Handle, methodsPtr, methods.Length); + } + + // Surface (and clear) any pending Java exception raised by JNI::RegisterNatives() + // — e.g. NoSuchMethodError — before falling back to the return-code check, matching + // the behavior of the generated `_RegisterNatives` wrapper. Leaving a pending + // exception in the JNIEnv would make subsequent JNI calls fail or abort. + var thrown = JniEnvironment.GetExceptionForLastThrowable (); + if (thrown != null) + ExceptionDispatchInfo.Capture (thrown).Throw (); + + if (r != 0) { + throw new InvalidOperationException ($"Could not register native methods for class '{GetJniTypeNameFromClass (type)}'; JNIEnv::RegisterNatives() returned {r}."); } }