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

Commit a807961

Browse files
committed
[Java.Interop] Dispose, *then* Remove (#708)
Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1193891 A bug was found in Xamarin.Android 11.1.0.2, candidate for Visual Studio 16.8 Preview 3: 1. Create a new "Shell Forms App" 2. Build & Run the app 3. Tap the "Browse" button. Expected results: it works! Actual results: A rather abrupt crash: Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Xamarin.Forms.Platform.Android.PageRenderer from native handle 0x69 (key_handle 0x33efaa1). ---> System.MissingMethodException: No constructor found for Xamarin.Forms.Platform.Android.PageRenderer::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown. --- End of inner exception stack trace --- at Java.Interop.TypeManager.CreateProxy (System.Type type, System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer) at Java.Interop.TypeManager.CreateInstance (System.IntPtr handle, Android.Runtime.JniHandleOwnership transfer, System.Type targetType) --- End of inner exception stack trace --- at (wrapper dynamic-method) Android.Runtime.DynamicMethodNameCounter.64(intptr,intptr) at (wrapper native-to-managed) Android.Runtime.DynamicMethodNameCounter.64(intptr,intptr) The crash was caused by commit 007b35b, which changed `JniRuntime.JniValueManager.DisposePeer()` semantics from: value.Disposed (); RemovePeer (value); and reversed this to: RemovePeer (value); value.Disposed (); (Why this reversal? Unmentioned in 007b35b -- doh! -- but as @jonpryor is the one who did this, "it seemed like a good idea at the time!") (Very clearly, this wasn't a good idea.) The crash was caused by commit 007b35b interacting with Xamarin.Forms' [`VisualElementPackager.Dispose(bool)`][0], which can call back into Java code, potentially causing `this` to need to be returned. When `RemovePeer()` is done *first*, as was done in 007b35b, then there may be no available instance to return, causing the `NotSupportedException` to be thrown. The new `JavaObjectTest.DisposeAccessesThis()` test and associated `Java.InteropTests.GetThis` type & Java peer trigger this scenario. Without the fix, `DisposeAccessesThis()` fails with: System.NotSupportedException : Could not find an appropriate constructable wrapper type for Java type 'com/xamarin/interop/GetThis', targetType='Java.InteropTests.GetThis'. at Java.Interop.JniRuntime+JniValueManager.CreatePeer (Java.Interop.JniObjectReference& reference, Java.Interop.JniObjectReferenceOptions transfer, System.Type targetType) at Java.Interop.JavaPeerableValueMarshaler.CreateGenericValue (Java.Interop.JniObjectReference& reference, Java.Interop.JniObjectReferenceOptions options, System.Type targetType) at Java.Interop.JniRuntime+JniValueManager.GetValue[T] (Java.Interop.JniObjectReference& reference, Java.Interop.JniObjectReferenceOptions options, System.Type targetType) at Java.InteropTests.GetThis.get_This () at Java.InteropTests.GetThis.Dispose (System.Boolean disposing) at Java.Interop.JavaObject.Java.Interop.IJavaPeerable.Disposed () at Java.Interop.JniRuntime+JniValueManager.DisposePeer (Java.Interop.IJavaPeerable value) at Java.Interop.JavaObject.Dispose () demonstrating how `RemovePeer(value)` *must not* happen before `value.Disposed()`. [0]: https://github.com/xamarin/Xamarin.Forms/blob/17881ec93d6b3fb0ee5e1a2be46d7eeadef23529/Xamarin.Forms.Platform.Android/VisualElementPackager.cs#L65-L108
1 parent b4c2a67 commit a807961

5 files changed

Lines changed: 99 additions & 2 deletions

File tree

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,8 @@ public virtual void DisposePeer (IJavaPeerable value)
146146
if(!value.PeerReference.IsValid)
147147
return;
148148

149-
RemovePeer(value);
150-
151149
value.Disposed();
150+
RemovePeer(value);
152151

153152
varh=value.PeerReference;
154153
if(!h.IsValid)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallNonvirtualDerived2.java" />
4141
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallVirtualFromConstructorBase.java" />
4242
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallVirtualFromConstructorDerived.java" />
43+
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\GetThis.java" />
4344
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\SelfRegistration.java" />
4445
<JavaInteropTestJarInclude="$(MSBuildThisFileDirectory)java\com\xamarin\interop\TestType.java" />
4546
</ItemGroup>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
usingSystem;
2+
3+
usingJava.Interop;
4+
5+
namespaceJava.InteropTests
6+
{
7+
[JniTypeSignature(GetThis.JniTypeName)]
8+
publicclassGetThis:JavaObject
9+
{
10+
internalconststringJniTypeName="com/xamarin/interop/GetThis";
11+
12+
bool_isDisposed;
13+
14+
readonlystaticJniPeerMembers_members=newJniPeerMembers(JniTypeName,typeof(GetThis));
15+
16+
publicoverrideJniPeerMembersJniPeerMembers{
17+
get{return_members;}
18+
}
19+
20+
publicGetThis()
21+
{
22+
}
23+
24+
publicunsafeGetThisThis{
25+
get{
26+
varo=_members.InstanceMethods.InvokeNonvirtualObjectMethod("getThis.()Lcom/xamarin/interop/GetThis;",this,null);
27+
returnJniEnvironment.Runtime.ValueManager.GetValue<GetThis>(refo,JniObjectReferenceOptions.CopyAndDispose);
28+
}
29+
}
30+
31+
protectedoverridevoidDispose(booldisposing)
32+
{
33+
if(_isDisposed){
34+
return;
35+
}
36+
_isDisposed=true;
37+
if(disposing){
38+
vart=This;
39+
if(t!=this){
40+
thrownewInvalidOperationException("SHOULD NOT BE REACHED");
41+
}
42+
}
43+
base.Dispose(disposing);
44+
}
45+
}
46+
}
47+

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ public void NestedDisposeInvocations ()
193193
value.Dispose();
194194
value.Dispose();
195195
}
196+
197+
[Test]
198+
publicvoidDisposeAccessesThis()
199+
{
200+
varvalue=newGetThis();
201+
value.Dispose();
202+
value.Dispose();
203+
}
196204
}
197205

198206
classJavaObjectWithNoJavaPeer:JavaObject{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
packagecom.xamarin.interop;
2+
3+
importjava.util.ArrayList;
4+
5+
importcom.xamarin.java_interop.GCUserPeerable;
6+
7+
publicclassGetThisimplementsGCUserPeerable {
8+
9+
staticfinalStringassemblyQualifiedName = "Java.InteropTests.GetThis, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null";
10+
static {
11+
com.xamarin.java_interop.ManagedPeer.registerNativeMembers (
12+
GetThis.class,
13+
assemblyQualifiedName,
14+
"");
15+
}
16+
17+
ArrayList<Object> managedReferences = newArrayList<Object>();
18+
19+
publicGetThis () {
20+
if (GetThis.class == getClass ()) {
21+
com.xamarin.java_interop.ManagedPeer.construct (
22+
this,
23+
"Java.InteropTests.GetThis, Java.Interop-Tests, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",
24+
""
25+
);
26+
}
27+
}
28+
29+
publicfinalGetThisgetThis() {
30+
returnthis;
31+
}
32+
33+
publicvoidjiAddManagedReference (java.lang.Objectobj)
34+
{
35+
managedReferences.add (obj);
36+
}
37+
38+
publicvoidjiClearManagedReferences ()
39+
{
40+
managedReferences.clear ();
41+
}
42+
}

0 commit comments

Comments
 (0)