Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Commit 2a7183a

Browse files
authored
[Java.Interop] CreatePeer() must satisfy targetType (#1308)
Context: dotnet/android#9747 Context: https://discord.com/channels/732297728826277939/732297837953679412/1339638847864176640 Context: https://discord.com/channels/732297728826277939/732297837953679412/1340011105510101063 While trying to get a MAUI sample running atop NativeAOT, we're observing the following crash: E AndroidRuntime: net.dot.jni.internal.JavaProxyThrowable: System.InvalidCastException: Arg_InvalidCastException E AndroidRuntime: at Java.Lang.Object._GetObject[T](IntPtr, JniHandleOwnership) + 0x64 E AndroidRuntime: at Microsoft.Maui.WindowOverlay.Initialize() + 0x168 Further investigation shows that the crash is from accessing the `Activity.WindowManager` property: namespace Android.App; partial class Activity { public virtual unsafe Android.Views.IWindowManager? WindowManager { // Metadata.xml XPath method reference: path="/api/package[@name='android.app']/class[@name='Activity']/method[@name='getWindowManager' and count(parameter)=0]" [Register ("getWindowManager", "()Landroid/view/WindowManager;", "GetGetWindowManagerHandler")] get { const string __id = "getWindowManager.()Landroid/view/WindowManager;"; try { var __rm = _members.InstanceMethods.InvokeVirtualObjectMethod (__id, this, null); return global::Java.Lang.Object.GetObject<Android.Views.IWindowManager> (__rm.Handle, JniHandleOwnership.TransferLocalRef); } finally { } } } } `Object.GetObject<T>()` is now a wrapper around `JniRuntime.JniValueManager.GetPeer()`, so the problem, rephrased, is that in this: var peer = JniEnvironment.Runtime.ValueManager.GetPeer ( ref h, JniObjectReferenceOptions.CopyAndDispose, targetType:typeof (IWindowManager)); var wm = (IWindowManager) peer; `peer` is a value which *does not implement* `IWindowManager`. It was, in fact, returning a `Java.Lang.Object` instance (!). Consequently, the cast to `IWindowManager` throws. The cause of the bug is that `JniRuntime.JniValueManager.CreatePeer()` was not checking that the `Type` returned form `Runtime.TypeManager.GetType(JniTypeSignature)` was compatible with `targetType`; instead, it returned the first type in the inheritance chain that had an activation constructor. This was `Java.Lang.Object`. Later, when `_GetObject<T>()` tried to cast the return value of `JniRuntime.JniValueManager.GetPeer()` to `IWindowManager`, it failed. Fix this by updating `CreatePeer()` to check that the `Type` from `JniRuntime.JniTypeManager.GetType(JniTypeSignature)` is assignable to `targetType`. This ensures that we *don't* return a `Java.Lang.Object` instance, allowing the cast to succeed. Update `JniRuntimeJniValueManagerContract` to test these new semantics.
1 parent d62008d commit 2a7183a

5 files changed

Lines changed: 76 additions & 3 deletions

File tree

‎src/Java.Interop/Java.Interop/JniRuntime.JniValueManager.cs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
348348
IJavaPeerable?CreatePeerInstance(
349349
refJniObjectReferenceklass,
350350
[DynamicallyAccessedMembers(Constructors)]
351-
TypefallbackType,
351+
TypetargetType,
352352
refJniObjectReferencereference,
353353
JniObjectReferenceOptionstransfer)
354354
{
@@ -362,7 +362,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
362362

363363
type=Runtime.TypeManager.GetType(sig);
364364

365-
if(type!=null){
365+
if(type!=null&&targetType.IsAssignableFrom(type)){
366366
varpeer=TryCreatePeerInstance(refreference,transfer,type);
367367

368368
if(peer!=null){
@@ -381,7 +381,7 @@ static Type GetPeerType ([DynamicallyAccessedMembers (Constructors)] Type type)
381381
}
382382
JniObjectReference.Dispose(refklass,JniObjectReferenceOptions.CopyAndDispose);
383383

384-
returnTryCreatePeerInstance(refreference,transfer,fallbackType);
384+
returnTryCreatePeerInstance(refreference,transfer,targetType);
385385
}
386386

387387
IJavaPeerable?TryCreatePeerInstance(

‎tests/Java.Interop-Tests/Java.Interop-Tests.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
</ItemGroup>
3737

3838
<ItemGroup>
39+
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\net\dot\jni\test\AnotherJavaInterfaceImpl.java" />
3940
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\net\dot\jni\test\CrossReferenceBridge.java" />
4041
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\net\dot\jni\test\CallNonvirtualBase.java" />
4142
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\net\dot\jni\test\CallNonvirtualDerived.java" />

‎tests/Java.Interop-Tests/Java.Interop/JavaVMFixture.cs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class JavaVMFixtureTypeManager : JniRuntime.JniTypeManager {
3737
[GenericHolder<int>.JniTypeName]=typeof(GenericHolder<>),
3838
[RenameClassBase.JniTypeName]=typeof(RenameClassBase),
3939
[RenameClassDerived.JniTypeName]=typeof(RenameClassDerived),
40+
[AnotherJavaInterfaceImpl.JniTypeName]=typeof(AnotherJavaInterfaceImpl),
4041
[CallVirtualFromConstructorBase.JniTypeName]=typeof(CallVirtualFromConstructorBase),
4142
[CallVirtualFromConstructorDerived.JniTypeName]=typeof(CallVirtualFromConstructorDerived),
4243
[CrossReferenceBridge.JniTypeName]=typeof(CrossReferenceBridge),

‎tests/Java.Interop-Tests/Java.Interop/JniRuntimeJniValueManagerContract.cs‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,30 @@ public void CollectPeers ()
133133
// TODO
134134
}
135135

136+
[Test]
137+
publicvoidCreatePeer_InvalidHandleReturnsNull()
138+
{
139+
varr=newJniObjectReference();
140+
varo=valueManager.CreatePeer(refr,JniObjectReferenceOptions.Copy,null);
141+
Assert.IsNull(o);
142+
}
143+
144+
[Test]
145+
publicunsafevoidCreatePeer_UsesFallbackType()
146+
{
147+
usingvart=newJniType(AnotherJavaInterfaceImpl.JniTypeName);
148+
149+
varctor=t.GetConstructor("()V");
150+
varlref=t.NewObject(ctor,null);
151+
152+
usingvarp=valueManager.CreatePeer(reflref,JniObjectReferenceOptions.CopyAndDispose,typeof(IJavaInterface));
153+
154+
Assert.IsFalse(lref.IsValid);// .CopyAndDispose disposes
155+
156+
Assert.IsNotNull(p);
157+
Assert.AreSame(typeof(IJavaInterfaceInvoker),p!.GetType());
158+
}
159+
136160
[Test]
137161
publicvoidCreateValue()
138162
{
@@ -294,4 +318,38 @@ public class JniRuntimeJniValueManagerContract_NoGCIntegration : JniRuntimeJniVa
294318
protectedoverrideTypeValueManagerType=>ManagedValueManagerType;
295319
}
296320
#endif // !__ANDROID__
321+
322+
// Note: Java side implements JavaInterface, while managed binding DOES NOT.
323+
// This is so that `CreatePeer(…, typeof(IJavaInterface))` tests don't use an existing AnotherJavaInterfaceImpl instance.
324+
//
325+
// This is mostly identical to MyJavaInterfaceImpl; the important difference is that
326+
// it contains an activation constructor, while MyJavaInterfaceImpl does not.
327+
// MyJavaInterfaceImpl can't have one, as that's what provokes the NotSupportedException in the JavaAs() tests.
328+
//
329+
// We want one here so that in "bad" `CreatePeer()` implementations, we'll find this peer and construct it
330+
// before verifying that it satisfies the targetType requirement.
331+
[JniTypeSignature(JniTypeName,GenerateJavaPeer=false)]
332+
publicclassAnotherJavaInterfaceImpl:JavaObject{
333+
internalconststringJniTypeName="net/dot/jni/test/AnotherJavaInterfaceImpl";
334+
335+
internalstaticreadonlyJniPeerMembers_members=newJniPeerMembers(JniTypeName,typeof(AnotherJavaInterfaceImpl));
336+
337+
publicoverrideJniPeerMembersJniPeerMembers{
338+
get{return_members;}
339+
}
340+
341+
AnotherJavaInterfaceImpl(refJniObjectReferencereference,JniObjectReferenceOptionsoptions)
342+
:base(refreference,options)
343+
{
344+
}
345+
346+
publicunsafeAnotherJavaInterfaceImpl()
347+
:base(ref*InvalidJniObjectReference,JniObjectReferenceOptions.None)
348+
{
349+
conststringid="()V";
350+
varpeer=_members.InstanceMethods.StartCreateInstance(id,GetType(),null);
351+
Construct(refpeer,JniObjectReferenceOptions.CopyAndDispose);
352+
_members.InstanceMethods.FinishCreateInstance(id,this,null);
353+
}
354+
}
297355
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
packagenet.dot.jni.test;
2+
3+
publicclassAnotherJavaInterfaceImpl
4+
implementsJavaInterface, Cloneable
5+
{
6+
publicStringgetValue() {
7+
return"Another hello from Java!";
8+
}
9+
10+
publicObjectclone() {
11+
returnthis;
12+
}
13+
}

0 commit comments

Comments
 (0)