Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 59 additions & 9 deletions src/Java.Interop/Java.Interop/JniEnvironment.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -251,12 +252,14 @@ static string JavaClassToJniType (string value)
return value.Replace ('.', '/');
}

[RequiresDynamicCode ("Native method registration via JniNativeMethodRegistration[] requires dynamic code generation. Use the blittable RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>) overload with statically-compiled function pointers for Native AOT compatibility.")]
public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods)
{
RegisterNatives (type, methods, methods == null ? 0 : methods.Length);
}

public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
[RequiresDynamicCode ("Native method registration via JniNativeMethodRegistration[] requires dynamic code generation. Use the blittable RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>) overload with statically-compiled function pointers for Native AOT compatibility.")]
public static unsafe void RegisterNatives (JniObjectReference type, JniNativeMethodRegistration [] methods, int numMethods)
{
if ((numMethods < 0) ||
(numMethods > (methods?.Length ?? 0))) {
Expand All @@ -275,22 +278,60 @@ public static void RegisterNatives (JniObjectReference type, JniNativeMethodRegi
}
#endif // DEBUG

int r = _RegisterNatives (type, methods ?? Array.Empty<JniNativeMethodRegistration>(), 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.
const int MaxStackAllocatedNativeMethods = 32;
bool useStackAllocatedBuffers = numMethods <= MaxStackAllocatedNativeMethods;
Span<JniNativeMethod> natives = useStackAllocatedBuffers
? stackalloc JniNativeMethod [numMethods]
: new JniNativeMethod [numMethods];
Span<IntPtr> unmanagedStrings = useStackAllocatedBuffers
? stackalloc IntPtr [numMethods * 2]
: 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, natives);
// Keep the Marshaler delegates alive at least until JNI has consumed the function pointers.
GC.KeepAlive (methods);
} finally {
Comment on lines +292 to +312
for (int i = 0; i < unmanagedStrings.Length; ++i) {
Marshal.ZeroFreeCoTaskMemUTF8 (unmanagedStrings [i]);
}
}
}

[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "RequiresDynamicCode on RegisterNatives propagates the requirement; this helper is only called from that method.")]
static IntPtr GetFunctionPointerForDelegate (Delegate marshaler) =>
Marshal.GetFunctionPointerForDelegate (marshaler);

/// <summary>
/// Registers JNI native methods using blittable <see cref="JniNativeMethod"/> structs
/// with raw function pointers and UTF-8 name/signature pointers.
/// Calls the JNI RegisterNatives function directly without delegate marshaling.
/// </summary>
public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan<JniNativeMethod> 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<IntPtr, IntPtr, JniNativeMethod*, int, int>)
Expand All @@ -299,10 +340,19 @@ public static unsafe void RegisterNatives (JniObjectReference type, ReadOnlySpan
var registerNatives = (delegate* unmanaged<IntPtr, IntPtr, JniNativeMethod*, int, int>)
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.
Comment on lines +346 to +349
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}.");
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Java.Interop/Java.Interop/JniType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public bool IsInstanceOfType (JniObjectReference value)
JniNativeMethodRegistration[]? methods;
#pragma warning restore 0414

[RequiresDynamicCode ("Native method registration via JniNativeMethodRegistration[] requires dynamic code generation. Use the blittable RegisterNatives(JniObjectReference, ReadOnlySpan<JniNativeMethod>) overload with statically-compiled function pointers for Native AOT compatibility.")]
public void RegisterNativeMethods (params JniNativeMethodRegistration[] methods)
{
AssertValid ();
Expand Down
1 change: 1 addition & 0 deletions src/Java.Interop/Java.Interop/ManagedPeer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Java.Interop {

static readonly JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (ManagedPeer));

[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "ManagedPeer is not compatible with Native AOT; it's only used by reflection-based JniRuntime.JniValueManager implementations.")]
static ManagedPeer ()
{
_members.JniPeerType.RegisterNativeMethods (
Expand Down
3 changes: 3 additions & 0 deletions tests/Java.Interop-Tests/Java.Interop/JniTypeTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;

using Java.Interop;
Expand Down Expand Up @@ -42,6 +43,7 @@ public void Ctor_ThrowsIfTypeNotFound ()
}

[Test]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniType.RegisterNativeMethods API.")]
public unsafe void Dispose_Exceptions ()
{
var t = new JniType ("java/lang/Object");
Expand Down Expand Up @@ -175,6 +177,7 @@ public void RegisterWithRuntime ()
}

[Test]
[UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Test exercises non-AOT-compatible JniType.RegisterNativeMethods API.")]
public void RegisterNativeMethods ()
{
using (var TestType_class = new JniType ("net/dot/jni/test/CallNonvirtualBase")) {
Expand Down