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

2 files changed

Lines changed: 151 additions & 73 deletions

File tree

‎samples/NativeAOT/MainApplication.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class MainApplication : Application
1111
publicMainApplication(IntPtrhandle,JniHandleOwnershiptransfer)
1212
:base(handle,transfer)
1313
{
14+
Log.Debug("NativeAOT",$"Application..ctor({handle.ToString("x2")}, {transfer})");
1415
}
1516

1617
publicoverridevoidOnCreate()

‎samples/NativeAOT/NativeAotValueManager.cs‎

Lines changed: 150 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ namespace NativeAOT;
1717

1818
internalclassNativeAotValueManager:JniRuntime.JniValueManager
1919
{
20+
constDynamicallyAccessedMemberTypesConstructors=DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.NonPublicConstructors;
21+
2022
readonlyNativeAotTypeManagerTypeManager;
2123
Dictionary<int,List<IJavaPeerable>>?RegisteredInstances=newDictionary<int,List<IJavaPeerable>>();
2224

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

254256
publicoverrideIJavaPeerable?CreatePeer(
255257
refJniObjectReferencereference,
256-
JniObjectReferenceOptionsoptions,
257-
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.NonPublicConstructors)]
258+
JniObjectReferenceOptionstransfer,
259+
[DynamicallyAccessedMembers(Constructors)]
258260
Type?targetType)
259261
{
260-
if(!reference.IsValid)
262+
if(!reference.IsValid){
261263
returnnull;
264+
}
262265

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

268-
internalIJavaPeerable?CreateInstance(
269-
IntPtrhandle,
270-
JniHandleOwnershiptransfer,
271-
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.NonPublicConstructors)]
272-
Type?targetType)
273-
{
274-
if(targetType.IsInterface||targetType.IsAbstract){
275-
varinvokerType=JavaObjectExtensions.GetInvokerType(targetType);
276-
if(invokerType==null)
277-
thrownewNotSupportedException("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+
thrownewArgumentException($"targetType `{targetType.AssemblyQualifiedName}` must implement IJavaPeerable!",nameof(targetType));
281271

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

287-
JniObjectReferencetypeClass=default;
288-
JniObjectReferencehandleClass=default;
277+
varrefClass=JniEnvironment.Types.GetObjectClass(reference);
278+
JniObjectReferencetargetClass;
289279
try{
290-
try{
291-
typeClass=JniEnvironment.Types.FindClass(typeSig.SimpleReference);
292-
}catch(Exceptione){
293-
thrownewArgumentException($"Could not find Java class `{typeSig.SimpleReference}`.",
294-
nameof(targetType),
295-
e);
296-
}
280+
targetClass=JniEnvironment.Types.FindClass(targetSig.SimpleReference);
281+
}catch(Exceptione){
282+
JniObjectReference.Dispose(refrefClass);
283+
thrownewArgumentException($"Could not find Java class `{targetSig.SimpleReference}`.",
284+
nameof(targetType),
285+
e);
286+
}
287+
288+
if(!JniEnvironment.Types.IsAssignableFrom(refClass,targetClass)){
289+
JniObjectReference.Dispose(refrefClass);
290+
JniObjectReference.Dispose(reftargetClass);
291+
returnnull;
292+
}
293+
294+
JniObjectReference.Dispose(reftargetClass);
295+
296+
varproxy=CreatePeerProxy(refrefClass,targetType,refreference,transfer);
297+
298+
if(proxy==null){
299+
thrownewNotSupportedException(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+
returnproxy;
305+
}
306+
307+
[return:DynamicallyAccessedMembers(Constructors)]
308+
staticTypeGetPeerType([DynamicallyAccessedMembers(Constructors)]Typetype)
309+
{
310+
if(type==typeof(object))
311+
returntypeof(global::Java.Interop.JavaObject);
312+
if(type==typeof(IJavaPeerable))
313+
returntypeof(global::Java.Interop.JavaObject);
314+
if(type==typeof(Exception))
315+
returntypeof(global::Java.Interop.JavaException);
316+
returntype;
317+
}
318+
319+
staticreadonlyTypeByRefJniObjectReference=typeof(JniObjectReference).MakeByRefType();
320+
321+
IJavaPeerable?CreatePeerProxy(
322+
refJniObjectReferenceklass,
323+
[DynamicallyAccessedMembers(Constructors)]
324+
TypefallbackType,
325+
refJniObjectReferencereference,
326+
JniObjectReferenceOptionsoptions)
327+
{
328+
varjniTypeName=JniEnvironment.Types.GetJniTypeNameFromClass(klass);
297329

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

307-
IJavaPeerable?result=null;
355+
returnTryCreatePeerProxy(fallbackType,refreference,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(MissingMethodExceptione){
315-
varkey_handle=JNIEnv.IdentityHash(handle);
316-
JNIEnv.DeleteRef(handle,transfer);
317-
thrownewNotSupportedException(FormattableString.Invariant(
318-
$"Unable to activate instance of type {targetType} from native handle 0x{handle:x} (key_handle 0x{key_handle:x})."),e);
358+
staticConstructorInfo?GetActivationConstructor(
359+
[DynamicallyAccessedMembers(Constructors)]
360+
Typetype)
361+
{
362+
if(type.IsAbstract||type.IsInterface){
363+
type=GetInvokerType(type)??type;
319364
}
320-
returnresult;
365+
foreach(varcintype.GetConstructors(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance)){
366+
varp=c.GetParameters();
367+
if(p.Length==2&&p[0].ParameterType==ByRefJniObjectReference&&p[1].ParameterType==typeof(JniObjectReferenceOptions))
368+
returnc;
369+
if(p.Length==2&&p[0].ParameterType==typeof(IntPtr)&&p[1].ParameterType==typeof(JniHandleOwnership))
370+
returnc;
371+
}
372+
returnnull;
321373
}
322374

375+
[return:DynamicallyAccessedMembers(Constructors)]
376+
staticType?GetInvokerType(Typetype)
377+
{
378+
// https://github.com/xamarin/xamarin-android/blob/5472eec991cc075e4b0c09cd98a2331fb93aa0f3/src/Microsoft.Android.Sdk.ILLink/MarkJavaObjects.cs#L176-L186
379+
conststringmakeGenericTypeMessage="Generic 'Invoker' types are preserved by the MarkJavaObjects trimmer step.";
380+
381+
[UnconditionalSuppressMessage("Trimming","IL2055",Justification=makeGenericTypeMessage)]
382+
[return:DynamicallyAccessedMembers(Constructors)]
383+
staticTypeMakeGenericType(
384+
[DynamicallyAccessedMembers(Constructors)]
385+
Typetype,
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+
varsignature=type.GetCustomAttribute<JniTypeSignatureAttribute>();
393+
if(signature==null||signature.InvokerType==null){
394+
returnnull;
395+
}
396+
397+
Type[]arguments=type.GetGenericArguments();
398+
if(arguments.Length==0)
399+
returnsignature.InvokerType;
400+
401+
returnMakeGenericType(signature.InvokerType,arguments);
402+
}
403+
404+
constBindingFlagsActivationConstructorBindingFlags=BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance;
405+
406+
323407
staticreadonlyType[]XAConstructorSignature=newType[]{typeof(IntPtr),typeof(JniHandleOwnership)};
324408
staticreadonlyType[]JIConstructorSignature=newType[]{typeof(JniObjectReference).MakeByRefType(),typeof(JniObjectReferenceOptions)};
325409

326-
internalstaticobjectCreateProxy(
327-
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.NonPublicConstructors)]
328-
Typetype,
329-
IntPtrhandle,
330-
JniHandleOwnershiptransfer)
410+
protectedvirtualIJavaPeerable?TryCreatePeerProxy(Typetype,refJniObjectReferencereference,JniObjectReferenceOptionsoptions)
331411
{
332-
// Skip Activator.CreateInstance() as that requires public constructors,
333-
// and we want to hide some constructors for sanity reasons.
334-
BindingFlagsflags=BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance;
335-
varc=type.GetConstructor(flags,null,XAConstructorSignature,null);
412+
varc=type.GetConstructor(ActivationConstructorBindingFlags,null,XAConstructorSignature,null);
336413
if(c!=null){
337-
returnc.Invoke(newobject[]{handle,transfer});
414+
varargs=newobject[]{
415+
reference.Handle,
416+
JniHandleOwnership.DoNotTransfer,
417+
};
418+
varp=(IJavaPeerable)c.Invoke(args);
419+
JniObjectReference.Dispose(refreference,options);
420+
returnp;
338421
}
339-
c=type.GetConstructor(flags,null,JIConstructorSignature,null);
422+
c=type.GetConstructor(ActivationConstructorBindingFlags,null,JIConstructorSignature,null);
340423
if(c!=null){
341-
JniObjectReferencer=newJniObjectReference(handle);
342-
JniObjectReferenceOptionso=JniObjectReferenceOptions.Copy;
343-
varpeer=(IJavaPeerable)c.Invoke(newobject[]{r,o});
344-
JNIEnv.DeleteRef(handle,transfer);
345-
returnpeer;
424+
varargs=newobject[]{
425+
reference,
426+
options,
427+
};
428+
varp=(IJavaPeerable)c.Invoke(args);
429+
reference=(JniObjectReference)args[0];
430+
returnp;
346431
}
347-
thrownewMissingMethodException(
348-
"No constructor found for "+type.FullName+"::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)",
349-
CreateJavaLocationException());
350-
}
351-
352-
staticExceptionCreateJavaLocationException()
353-
{
354-
using(varloc=newJava.Lang.Error("Java callstack:"))
355-
returnnewJavaLocationException(loc.ToString());
432+
returnnull;
356433
}
357434
}

0 commit comments

Comments
 (0)