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

Commit d5dfa0a

Browse files
authored
[Java.Interop] Java.Lang.Object, Mono.Android Unification Changes (#1293)
Context: dotnet/android#9640 Context: 25de1f3 Context: 3043d89dotnet/android#9640 completes "unification" with dotnet/java-interop, updating `Java.Lang.Object` to inherit `Java.Interop.JavaObject`, just like `src/Java.Base`. The biggest impediment with this unification step is that the semantics for referring to a Java instance are quite different: * .NET for Android née Xamarin.Android née Mono for Android uses an `(IntPtr, JniHandleOwnership)` pair to specify the JNI object reference pointer value and what should be done with it. The *type* of the JNI object reference is *inferred* by the `JniHandleOwnership` value, e.g. `JniHandleOwnership.TransferLocalRef` means that the `IntPtr` value is a JNI local reference. * dotnet/java-interop uses a `(ref JniObjectReference, JniObjectReferenceOptions)` pair to specify the JNI object reference and what can be done with it. The `JniObjectReference` value contains the type, but -- due to "pointer trickery"; see 25de1f3 -- it might be *so* invalid that it cannot be touched at all, and `JniObjectReferenceOptions` will let us know. Which brings us to the conundrum: how do we implement the `Java.Lang.Throwable(IntPtr, JniHandleOwnership)` constructor? namespace Java.Lang { public class Throwable { public Throwable(IntPtr handle, JniHandleOwnership transfer); } } The "obvious and wrong" solution would be to use `ref` with a temporary… public Throwable(IntPtr handle, JniHandleOwnership transfer) : base (ref new JniObjectReference(handle), …) { } …but that won't compile. Additionally, we want to be able to provide `message` and `innerException` parameters to `System.Exception`, but the `JavaException(ref JniObjectReference, JniObjectReferenceOptions)` constructor requires a valid JNI Object Reference to do that. If `Java.Lang.Throwable` tries to "work around" this by using the `JavaException(string, Exception)` constructor: public Throwable(IntPtr handle, JniHandleOwnership transfer) : base (_GetMessage (handle), _GetInnerException (handle)) { … } then the `JavaException(string, Exception)` constructor will try to invoke the `E(String)` constructor on the Java side, which: 1. Is semantically wrong, because we may already have a Java instance in `handle`, so why are we creating a new instance? But- 2. This fails for constructor subclasses which don't provide a `E(String)` constructor! Specifically, the [`JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered`][0] unit test starts crashing for "bizarre" reasons. Fix these issues by adding the new `JavaException` constructor: namespace Java.Interop { partial class JavaException { protected JavaException ( ref JniObjectReference reference, JniObjectReferenceOptions transfer, JniObjectReference throwableOverride); } } The new `throwableOverride` parameter is used to obtain the `message` and `innerException` values to provide to the `System.Exception(string, Exception)` constructor, allowing `reference` to be a "do not touch" value. Additionally, add a `protected SetJavaStackTrace()` method which is used to set the `JavaException.JavaStackTrace` property based on a `JniObjectReference` value. Update `Java.Interop.JavaObject` to be `[Serializable]`, and to mark its fields as *`[NonSerialized]`*. This is needed to fix the `InstallAndRunTests.JsonDeserializationCreatesJavaHandle()` tests: E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.Runtime.Serialization.InvalidDataContractException]: AttributedTypesCannotInheritFromNonAttributedSerializableTypes, Java.Lang.Object, Java.Interop.JavaObject Finally, update `Java.Interop.JniRuntime.JniValueManager.ConstructPeer()` to *not* attempt to call `AddPeer()`/etc. when `peer.JniManagedPeerState` has `JniManagedPeerStates.Activatable` set. Failure to do so was causing `BindingTests.JavaSideActivation()` to fail, as it was "losing" a GREF. `JavaSideActivation()` is the scenario described in 3043d89, in which: 1. The static Java method `CallMethodFromCtor.NewInstance()` creates an instance of `ConstructorTest`, from Java. 2. The default constructor in the Java Callable Wrapper (JCW) for `ConstructorTest` chains to the Java `CallMethodFromCtor()` default constructor. 3. The Java `CallMethodFromCtor()` default constructor invokes the method `calledFromCtor()`, which is overridden in the C# `ConstructorTest.CalledFromCtor()` method. 4. This causes the `CallMethodFromCtor.n_CalledFromCtor()` marshal method to be invoked, which calls `Java.Lang.Object.GetObject<CallMethodFromCtor>()`, which is akin to `runtime.ValueManager.GetValue<CallMethodFromCtor>(…)`. 5. `Object.GetObject<CallMethodFromCtor>()` doesn't find an already existing object mapping for the instance we're still trying to construct, so it creates a "proxy" object via [`TypeManager.CreateInstance()`][2], which winds up invoking the `ConstructorTest(IntPtr, JniHandleOwnership)` "activation" constructor to create the "proxy". The activation constructor eventually invokes `JavaObject.Construct()` / `JniRuntime.JniValueManager.ConstructPeer()`. This is the first "important" GREF created. Once the proxy is created, it's state is set to `JniManagedPeerStates.Activatable`. 6. The `ConstructorTest.CalledFromCtor()` C# override is invoked, control returns to the Java `CallMethodFromCtor` constructor, which completes, allowing the JCW `ConstructorTest` constructor to execute, which calls [`TypeManager.n_Activate(…)`][3], which is equivalent to `ManagedPeer.Construct()`. 7. `TypeManager.n_Activate(…)` sees that the instance "needs activation", and invokes the C# `ConstructorTest()` default constructor. This eventually hits (again) `JniRuntime.JniValueManager.ConstructPeer()`. The problem was that on the second `JniRuntime.JniValueManager.ConstructPeer()` invocation in (7), another GREF was created, replacing the GREF created in (5), but without *disposing* of the GREF created in (5). This caused a GREF leak, which the `JavaSideActivation()` test was looking for. Updating `JniRuntime.JniValueManager.ConstructPeer()` to bail when it encounters a Peer with `JniManagedPeerStates.Activatable` avoids the GREF creation in (7), thus avoiding the GREF leak. [0]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs#L285-L305 [1]: https://github.com/dotnet/android/blob/main/tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/BindingTests.cs#L97-L115 [2]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/src/Mono.Android/Java.Interop/TypeManager.cs#L240-L332 [3]: https://github.com/dotnet/android/blob/f9f421b109b2a0f8f805ed14ab1c8dbe651f563e/src/Mono.Android/Java.Interop/TypeManager.cs#L132-L167
1 parent c86ae26 commit d5dfa0a

4 files changed

Lines changed: 60 additions & 15 deletions

File tree

‎src/Java.Interop/Java.Interop/JavaException.cs‎

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public unsafe JavaException ()
3838
varpeer=JniPeerMembers.InstanceMethods.StartCreateInstance("()V",GetType(),null);
3939
Construct(refpeer,JniObjectReferenceOptions.CopyAndDispose);
4040
JniPeerMembers.InstanceMethods.FinishCreateInstance("()V",this,null);
41-
JavaStackTrace=GetJavaStack(PeerReference);
41+
SetJavaStackTrace();
4242
}
4343

4444
publicunsafeJavaException(stringmessage)
@@ -55,7 +55,7 @@ public unsafe JavaException (string message)
5555
}finally{
5656
JniObjectReference.Dispose(refnative_message,JniObjectReferenceOptions.CopyAndDispose);
5757
}
58-
JavaStackTrace=GetJavaStack(PeerReference);
58+
SetJavaStackTrace();
5959
}
6060

6161
publicunsafeJavaException(stringmessage,ExceptioninnerException)
@@ -72,15 +72,22 @@ public unsafe JavaException (string message, Exception innerException)
7272
}finally{
7373
JniObjectReference.Dispose(refnative_message,JniObjectReferenceOptions.CopyAndDispose);
7474
}
75-
JavaStackTrace=GetJavaStack(PeerReference);
75+
SetJavaStackTrace();
76+
}
77+
78+
protectedJavaException(refJniObjectReferencereference,JniObjectReferenceOptionstransfer,JniObjectReferencethrowableOverride)
79+
:base(GetMessage(throwableOverride),GetCause(throwableOverride))
80+
{
81+
Construct(refreference,transfer);
82+
SetJavaStackTrace(throwableOverride);
7683
}
7784

7885
publicJavaException(refJniObjectReferencereference,JniObjectReferenceOptionstransfer)
7986
:base(GetMessage(refreference,transfer),GetCause(refreference,transfer))
8087
{
8188
Construct(refreference,transfer);
8289
if(PeerReference.IsValid)
83-
JavaStackTrace=GetJavaStack(PeerReference);
90+
SetJavaStackTrace();
8491
}
8592

8693
protectedvoidConstruct(refJniObjectReferencereference,JniObjectReferenceOptionsoptions)
@@ -183,6 +190,13 @@ public override unsafe int GetHashCode ()
183190
{
184191
if(transfer==JniObjectReferenceOptions.None)
185192
returnnull;
193+
returnGetMessage(reference);
194+
}
195+
196+
staticstring?GetMessage(JniObjectReferencereference)
197+
{
198+
if(!reference.IsValid)
199+
returnnull;
186200

187201
varm=_members.InstanceMethods.GetMethodInfo("getMessage.()Ljava/lang/String;");
188202
vars=JniEnvironment.InstanceMethods.CallObjectMethod(reference,m);
@@ -193,12 +207,30 @@ public override unsafe int GetHashCode ()
193207
{
194208
if(transfer==JniObjectReferenceOptions.None)
195209
returnnull;
210+
returnGetCause(reference);
211+
}
212+
213+
staticException?GetCause(JniObjectReferencereference)
214+
{
215+
if(!reference.IsValid)
216+
returnnull;
196217

197218
varm=_members.InstanceMethods.GetMethodInfo("getCause.()Ljava/lang/Throwable;");
198219
vare=JniEnvironment.InstanceMethods.CallObjectMethod(reference,m);
199220
returnJniEnvironment.Runtime.GetExceptionForThrowable(refe,JniObjectReferenceOptions.CopyAndDispose);
200221
}
201222

223+
protectedvoidSetJavaStackTrace(JniObjectReferencepeerReferenceOverride=default)
224+
{
225+
if(!peerReferenceOverride.IsValid){
226+
peerReferenceOverride=PeerReference;
227+
}
228+
if(!peerReferenceOverride.IsValid){
229+
return;
230+
}
231+
JavaStackTrace=GetJavaStack(peerReferenceOverride);
232+
}
233+
202234
unsafestring?GetJavaStack(JniObjectReferencehandle)
203235
{
204236
using(varStringWriter_class=newJniType("java/io/StringWriter"))

‎src/Java.Interop/Java.Interop/JavaObject.cs‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,35 @@
22

33
usingSystem;
44
usingSystem.Diagnostics.CodeAnalysis;
5+
usingSystem.Runtime.Serialization;
56

67
namespaceJava.Interop
78
{
89
[JniTypeSignature("java/lang/Object",GenerateJavaPeer=false)]
10+
[Serializable]
911
unsafepublicclassJavaObject:IJavaPeerable
1012
{
1113
internalconstDynamicallyAccessedMemberTypesConstructors=DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.NonPublicConstructors;
1214

1315
readonlystaticJniPeerMembers_members=newJniPeerMembers("java/lang/Object",typeof(JavaObject));
1416

15-
publicintJniIdentityHashCode{get;privateset;}
16-
publicJniManagedPeerStatesJniManagedPeerState{get;privateset;}
17+
[NonSerialized]intidentityHashCode;
18+
[NonSerialized]JniManagedPeerStatesmanagedPeerState;
19+
20+
publicintJniIdentityHashCode=>identityHashCode;
21+
22+
publicJniManagedPeerStatesJniManagedPeerState=>managedPeerState;
1723

1824
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
19-
JniObjectReferencereference;
25+
[NonSerialized]JniObjectReferencereference;
2026
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
2127
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
22-
IntPtrhandle;
23-
JniObjectReferenceTypehandle_type;
28+
[NonSerialized]IntPtrhandle;
29+
[NonSerialized]JniObjectReferenceTypehandle_type;
2430
#pragma warning disable 0169
2531
// Used by JavaInteropGCBridge
26-
IntPtrweak_handle;
27-
intrefs_added;
32+
[NonSerialized]IntPtrweak_handle;
33+
[NonSerialized]intrefs_added;
2834
#pragma warning restore 0169
2935
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
3036

@@ -151,12 +157,12 @@ void IJavaPeerable.Finalized ()
151157

152158
voidIJavaPeerable.SetJniIdentityHashCode(intvalue)
153159
{
154-
JniIdentityHashCode=value;
160+
identityHashCode=value;
155161
}
156162

157163
voidIJavaPeerable.SetJniManagedPeerState(JniManagedPeerStatesvalue)
158164
{
159-
JniManagedPeerState=value;
165+
managedPeerState=value;
160166
}
161167

162168
voidIJavaPeerable.SetPeerReference(JniObjectReferencereference)

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,13 @@ public void ConstructPeer (IJavaPeerable peer, ref JniObjectReference reference,
9797

9898
varnewRef=peer.PeerReference;
9999
if(newRef.IsValid){
100-
// Activation! See ManagedPeer.RunConstructor
101-
peer.SetJniManagedPeerState(peer.JniManagedPeerState|JniManagedPeerStates.Activatable);
100+
JniObjectReference.Dispose(refreference,options);
101+
102+
// Activation? See ManagedPeer.Construct, CreatePeer
103+
// Instance was already added, don't add again
104+
if(peer.JniManagedPeerState.HasFlag(JniManagedPeerStates.Activatable)){
105+
return;
106+
}
102107
JniObjectReference.Dispose(refreference,options);
103108
newRef=newRef.NewGlobalRef();
104109
}elseif(options==JniObjectReferenceOptions.None){

‎src/Java.Interop/PublicAPI.Unshipped.txt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ static Java.Interop.JniEnvironment.BeginMarshalMethod(nint jnienv, out Java.Inte
33
static Java.Interop.JniEnvironment.EndMarshalMethod(ref Java.Interop.JniTransition transition) -> void
44
virtual Java.Interop.JniRuntime.OnEnterMarshalMethod() -> void
55
virtual Java.Interop.JniRuntime.OnUserUnhandledException(ref Java.Interop.JniTransition transition, System.Exception! e) -> void
6+
Java.Interop.JavaException.JavaException(ref Java.Interop.JniObjectReference reference, Java.Interop.JniObjectReferenceOptions transfer, Java.Interop.JniObjectReference throwableOverride) -> void
7+
Java.Interop.JavaException.SetJavaStackTrace(Java.Interop.JniObjectReference peerReferenceOverride = default(Java.Interop.JniObjectReference)) -> void
68
Java.Interop.JniTypeSignatureAttribute.InvokerType.get -> System.Type?
79
Java.Interop.JniTypeSignatureAttribute.InvokerType.set -> void

0 commit comments

Comments
 (0)