Skip to content

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

File tree

5 files changed

+99
-2
lines changed

5 files changed

+99
-2
lines changed

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

+1-2
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
var h = value.PeerReference;
154153
if (!h.IsValid)

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

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallNonvirtualDerived2.java" />
4141
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallVirtualFromConstructorBase.java" />
4242
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\CallVirtualFromConstructorDerived.java" />
43+
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\GetThis.java" />
4344
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\SelfRegistration.java" />
4445
<JavaInteropTestJar Include="$(MSBuildThisFileDirectory)java\com\xamarin\interop\TestType.java" />
4546
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
3+
using Java.Interop;
4+
5+
namespace Java.InteropTests
6+
{
7+
[JniTypeSignature (GetThis.JniTypeName)]
8+
public class GetThis : JavaObject
9+
{
10+
internal const string JniTypeName = "com/xamarin/interop/GetThis";
11+
12+
bool _isDisposed;
13+
14+
readonly static JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (GetThis));
15+
16+
public override JniPeerMembers JniPeerMembers {
17+
get {return _members;}
18+
}
19+
20+
public GetThis ()
21+
{
22+
}
23+
24+
public unsafe GetThis This {
25+
get {
26+
var o = _members.InstanceMethods.InvokeNonvirtualObjectMethod ("getThis.()Lcom/xamarin/interop/GetThis;", this, null);
27+
return JniEnvironment.Runtime.ValueManager.GetValue<GetThis> (ref o, JniObjectReferenceOptions.CopyAndDispose);
28+
}
29+
}
30+
31+
protected override void Dispose (bool disposing)
32+
{
33+
if (_isDisposed) {
34+
return;
35+
}
36+
_isDisposed = true;
37+
if (disposing) {
38+
var t = This;
39+
if (t != this) {
40+
throw new InvalidOperationException ("SHOULD NOT BE REACHED");
41+
}
42+
}
43+
base.Dispose (disposing);
44+
}
45+
}
46+
}
47+

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

+8
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+
public void DisposeAccessesThis ()
199+
{
200+
var value = new GetThis ();
201+
value.Dispose ();
202+
value.Dispose ();
203+
}
196204
}
197205

198206
class JavaObjectWithNoJavaPeer : JavaObject {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.xamarin.interop;
2+
3+
import java.util.ArrayList;
4+
5+
import com.xamarin.java_interop.GCUserPeerable;
6+
7+
public class GetThis implements GCUserPeerable {
8+
9+
static final String assemblyQualifiedName = "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 = new ArrayList<Object>();
18+
19+
public GetThis () {
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+
public final GetThis getThis() {
30+
return this;
31+
}
32+
33+
public void jiAddManagedReference (java.lang.Object obj)
34+
{
35+
managedReferences.add (obj);
36+
}
37+
38+
public void jiClearManagedReferences ()
39+
{
40+
managedReferences.clear ();
41+
}
42+
}

0 commit comments

Comments
 (0)