Skip to content

Commit 694e975

Browse files
committed
Fix Application subclass usage.
Context: https://discord.com/channels/732297728826277939/732297837953679412/1334614545871929345 PR #9716 was crashing with a stack overflow: I NativeAotFromAndroid: at Java.Interop.JniEnvironment.InstanceMethods.CallVoidMethod(JniObjectReference, JniMethodInfo, JniArgumentValue*) + 0xa8 I NativeAotFromAndroid: at Java.Interop.JniPeerMembers.JniInstanceMethods.InvokeVirtualVoidMethod(String, IJavaPeerable, JniArgumentValue*) + 0x184 I NativeAotFromAndroid: at Android.App.Application.n_OnCreate(IntPtr jnienv, IntPtr native__this) + 0xa8 I NativeAotFromAndroid: at libNativeAOT!<BaseAddress>+0x4f3e44 The cause was the topmost frame: `CallVoidMethod()`, which performs a *virtual* method invocation. The stack overflow was that Java `MainApplication.onCreate()` called C# `Application.n_OnCreate()`, which called `InvokeVirtualVoidMethod()`, which did a *virtual* invocation back on `MainApplication.onCreate()`, … `InvokeVirtualVoidMethod()` should have been calling `CallNonvirtualVoidMethod()`; why wasn't it? Further investigation showed: Created PeerReference=0x2d06/G IdentityHashCode=0x8edcb07 Instance=0x957d2a Instance.Type=Android.App.Application, Java.Type=my/MainApplication which at a glance seems correct, but isn't: the `Instance.Type` for a `Java.Type` of `my/MainApplication` should be `MainApplication`, *not* `Android.App.Application`! Because the runtime type of this value was `Application`, it was warranted and expected that `InvokeVirtualVoidMethod()` would do a virtual invocation! So, why did the avove `Created PeerReference …` line show the wrong type? Because `NativeAotTypeManager.CreatePeer()` needs to check for bindings of the the runtime type of the Java handle *before* using the `targetType` parameter, because the targetType parameter will *never* be for that of a custom subclass. Copy *lots* of code from dotnet/java-interop -- showing that this needs some major cleanup & refactoring -- so that we properly check the runtime type of `reference` + base classes when trying to determine the type of the proxy to create. This fixes the stack overflow.
1 parent d54546a commit 694e975

File tree

2 files changed

+151
-73
lines changed

2 files changed

+151
-73
lines changed

samples/NativeAOT/MainApplication.cs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class MainApplication : Application
1111
public MainApplication (IntPtr handle, JniHandleOwnership transfer)
1212
: base (handle, transfer)
1313
{
14+
Log.Debug ("NativeAOT", $"Application..ctor({handle.ToString ("x2")}, {transfer})");
1415
}
1516

1617
public override void OnCreate ()

samples/NativeAOT/NativeAotValueManager.cs

+150-73
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ namespace NativeAOT;
1717

1818
internal class NativeAotValueManager : JniRuntime.JniValueManager
1919
{
20+
const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
21+
2022
readonly NativeAotTypeManager TypeManager;
2123
Dictionary<int, List<IJavaPeerable>>? RegisteredInstances = new Dictionary<int, List<IJavaPeerable>>();
2224

@@ -253,105 +255,180 @@ public override List<JniSurfacedPeerInfo> GetSurfacedPeers ()
253255

254256
public override IJavaPeerable? CreatePeer (
255257
ref JniObjectReference reference,
256-
JniObjectReferenceOptions options,
257-
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
258+
JniObjectReferenceOptions transfer,
259+
[DynamicallyAccessedMembers (Constructors)]
258260
Type? targetType)
259261
{
260-
if (!reference.IsValid)
262+
if (!reference.IsValid) {
261263
return null;
264+
}
262265

263-
var peer = CreateInstance (reference.Handle, JniHandleOwnership.DoNotTransfer, targetType);
264-
JniObjectReference.Dispose (ref reference, options);
265-
return peer;
266-
}
266+
targetType = targetType ?? typeof (global::Java.Interop.JavaObject);
267+
targetType = GetPeerType (targetType);
267268

268-
internal IJavaPeerable? CreateInstance (
269-
IntPtr handle,
270-
JniHandleOwnership transfer,
271-
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
272-
Type? targetType)
273-
{
274-
if (targetType.IsInterface || targetType.IsAbstract) {
275-
var invokerType = JavaObjectExtensions.GetInvokerType (targetType);
276-
if (invokerType == null)
277-
throw new NotSupportedException ("Unable to find Invoker for type '" + targetType.FullName + "'. Was it linked away?",
278-
CreateJavaLocationException ());
279-
targetType = invokerType;
280-
}
269+
if (!typeof (IJavaPeerable).IsAssignableFrom (targetType))
270+
throw new ArgumentException ($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!", nameof (targetType));
281271

282-
var typeSig = TypeManager.GetTypeSignature (targetType);
283-
if (!typeSig.IsValid || typeSig.SimpleReference == null) {
272+
var targetSig = Runtime.TypeManager.GetTypeSignature (targetType);
273+
if (!targetSig.IsValid || targetSig.SimpleReference == null) {
284274
throw new ArgumentException ($"Could not determine Java type corresponding to `{targetType.AssemblyQualifiedName}`.", nameof (targetType));
285275
}
286276

287-
JniObjectReference typeClass = default;
288-
JniObjectReference handleClass = default;
277+
var refClass = JniEnvironment.Types.GetObjectClass (reference);
278+
JniObjectReference targetClass;
289279
try {
290-
try {
291-
typeClass = JniEnvironment.Types.FindClass (typeSig.SimpleReference);
292-
} catch (Exception e) {
293-
throw new ArgumentException ($"Could not find Java class `{typeSig.SimpleReference}`.",
294-
nameof (targetType),
295-
e);
296-
}
280+
targetClass = JniEnvironment.Types.FindClass (targetSig.SimpleReference);
281+
} catch (Exception e) {
282+
JniObjectReference.Dispose (ref refClass);
283+
throw new ArgumentException ($"Could not find Java class `{targetSig.SimpleReference}`.",
284+
nameof (targetType),
285+
e);
286+
}
287+
288+
if (!JniEnvironment.Types.IsAssignableFrom (refClass, targetClass)) {
289+
JniObjectReference.Dispose (ref refClass);
290+
JniObjectReference.Dispose (ref targetClass);
291+
return null;
292+
}
293+
294+
JniObjectReference.Dispose (ref targetClass);
295+
296+
var proxy = CreatePeerProxy (ref refClass, targetType, ref reference, transfer);
297+
298+
if (proxy == null) {
299+
throw new NotSupportedException (string.Format ("Could not find an appropriate constructable wrapper type for Java type '{0}', targetType='{1}'.",
300+
JniEnvironment.Types.GetJniTypeNameFromInstance (reference), targetType));
301+
}
302+
303+
proxy.SetJniManagedPeerState (proxy.JniManagedPeerState | JniManagedPeerStates.Replaceable);
304+
return proxy;
305+
}
306+
307+
[return: DynamicallyAccessedMembers (Constructors)]
308+
static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
309+
{
310+
if (type == typeof (object))
311+
return typeof (global::Java.Interop.JavaObject);
312+
if (type == typeof (IJavaPeerable))
313+
return typeof (global::Java.Interop.JavaObject);
314+
if (type == typeof (Exception))
315+
return typeof (global::Java.Interop.JavaException);
316+
return type;
317+
}
318+
319+
static readonly Type ByRefJniObjectReference = typeof (JniObjectReference).MakeByRefType ();
320+
321+
IJavaPeerable? CreatePeerProxy (
322+
ref JniObjectReference klass,
323+
[DynamicallyAccessedMembers (Constructors)]
324+
Type fallbackType,
325+
ref JniObjectReference reference,
326+
JniObjectReferenceOptions options)
327+
{
328+
var jniTypeName = JniEnvironment.Types.GetJniTypeNameFromClass (klass);
297329

298-
handleClass = JniEnvironment.Types.GetObjectClass (new JniObjectReference (handle));
299-
if (!JniEnvironment.Types.IsAssignableFrom (handleClass, typeClass)) {
330+
Type? type = null;
331+
while (jniTypeName != null) {
332+
JniTypeSignature sig;
333+
if (!JniTypeSignature.TryParse (jniTypeName, out sig))
300334
return null;
335+
336+
type = Runtime.TypeManager.GetType (sig);
337+
338+
if (type != null) {
339+
var peer = TryCreatePeerProxy (type, ref reference, options);
340+
if (peer != null) {
341+
return peer;
342+
}
301343
}
302-
} finally {
303-
JniObjectReference.Dispose (ref handleClass);
304-
JniObjectReference.Dispose (ref typeClass);
344+
345+
var super = JniEnvironment.Types.GetSuperclass (klass);
346+
jniTypeName = super.IsValid
347+
? JniEnvironment.Types.GetJniTypeNameFromClass (super)
348+
: null;
349+
350+
JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);
351+
klass = super;
305352
}
353+
JniObjectReference.Dispose (ref klass, JniObjectReferenceOptions.CopyAndDispose);
306354

307-
IJavaPeerable? result = null;
355+
return TryCreatePeerProxy (fallbackType, ref reference, options);
356+
}
308357

309-
try {
310-
result = (IJavaPeerable) CreateProxy (targetType, handle, transfer);
311-
//if (JNIEnv.IsGCUserPeer (result.PeerReference.Handle)) {
312-
result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);
313-
//}
314-
} catch (MissingMethodException e) {
315-
var key_handle = JNIEnv.IdentityHash (handle);
316-
JNIEnv.DeleteRef (handle, transfer);
317-
throw new NotSupportedException (FormattableString.Invariant (
318-
$"Unable to activate instance of type {targetType} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."), e);
358+
static ConstructorInfo? GetActivationConstructor (
359+
[DynamicallyAccessedMembers (Constructors)]
360+
Type type)
361+
{
362+
if (type.IsAbstract || type.IsInterface) {
363+
type = GetInvokerType (type) ?? type;
319364
}
320-
return result;
365+
foreach (var c in type.GetConstructors (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
366+
var p = c.GetParameters ();
367+
if (p.Length == 2 && p [0].ParameterType == ByRefJniObjectReference && p [1].ParameterType == typeof (JniObjectReferenceOptions))
368+
return c;
369+
if (p.Length == 2 && p [0].ParameterType == typeof (IntPtr) && p [1].ParameterType == typeof (JniHandleOwnership))
370+
return c;
371+
}
372+
return null;
321373
}
322374

375+
[return: DynamicallyAccessedMembers (Constructors)]
376+
static Type? GetInvokerType (Type type)
377+
{
378+
// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
379+
const string makeGenericTypeMessage = "Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";
380+
381+
[UnconditionalSuppressMessage ("Trimming", "IL2055", Justification = makeGenericTypeMessage)]
382+
[return: DynamicallyAccessedMembers (Constructors)]
383+
static Type MakeGenericType (
384+
[DynamicallyAccessedMembers (Constructors)]
385+
Type type,
386+
Type [] arguments) =>
387+
// FIXME: https://github.com/dotnet/java-interop/issues/1192
388+
#pragma warning disable IL3050
389+
type.MakeGenericType (arguments);
390+
#pragma warning restore IL3050
391+
392+
var signature = type.GetCustomAttribute<JniTypeSignatureAttribute> ();
393+
if (signature == null || signature.InvokerType == null) {
394+
return null;
395+
}
396+
397+
Type[] arguments = type.GetGenericArguments ();
398+
if (arguments.Length == 0)
399+
return signature.InvokerType;
400+
401+
return MakeGenericType (signature.InvokerType, arguments);
402+
}
403+
404+
const BindingFlags ActivationConstructorBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
405+
406+
323407
static readonly Type[] XAConstructorSignature = new Type [] { typeof (IntPtr), typeof (JniHandleOwnership) };
324408
static readonly Type[] JIConstructorSignature = new Type [] { typeof (JniObjectReference).MakeByRefType (), typeof (JniObjectReferenceOptions) };
325409

326-
internal static object CreateProxy (
327-
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]
328-
Type type,
329-
IntPtr handle,
330-
JniHandleOwnership transfer)
410+
protected virtual IJavaPeerable? TryCreatePeerProxy (Type type, ref JniObjectReference reference, JniObjectReferenceOptions options)
331411
{
332-
// Skip Activator.CreateInstance() as that requires public constructors,
333-
// and we want to hide some constructors for sanity reasons.
334-
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
335-
var c = type.GetConstructor (flags, null, XAConstructorSignature, null);
412+
var c = type.GetConstructor (ActivationConstructorBindingFlags, null, XAConstructorSignature, null);
336413
if (c != null) {
337-
return c.Invoke (new object [] { handle, transfer });
414+
var args = new object[] {
415+
reference.Handle,
416+
JniHandleOwnership.DoNotTransfer,
417+
};
418+
var p = (IJavaPeerable) c.Invoke (args);
419+
JniObjectReference.Dispose (ref reference, options);
420+
return p;
338421
}
339-
c = type.GetConstructor (flags, null, JIConstructorSignature, null);
422+
c = type.GetConstructor (ActivationConstructorBindingFlags, null, JIConstructorSignature, null);
340423
if (c != null) {
341-
JniObjectReference r = new JniObjectReference (handle);
342-
JniObjectReferenceOptions o = JniObjectReferenceOptions.Copy;
343-
var peer = (IJavaPeerable) c.Invoke (new object [] { r, o });
344-
JNIEnv.DeleteRef (handle, transfer);
345-
return peer;
424+
var args = new object[] {
425+
reference,
426+
options,
427+
};
428+
var p = (IJavaPeerable) c.Invoke (args);
429+
reference = (JniObjectReference) args [0];
430+
return p;
346431
}
347-
throw new MissingMethodException (
348-
"No constructor found for " + type.FullName + "::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)",
349-
CreateJavaLocationException ());
350-
}
351-
352-
static Exception CreateJavaLocationException ()
353-
{
354-
using (var loc = new Java.Lang.Error ("Java callstack:"))
355-
return new JavaLocationException (loc.ToString ());
432+
return null;
356433
}
357434
}

0 commit comments

Comments
 (0)