- Notifications
You must be signed in to change notification settings - Fork63
Commitd3d3a1b
authored
[Java.Interop] JNIEnv::NewObject and Replaceable instances (#1323)
Context:3043d89Context:dec35f5Context:dotnet/android#9862Context:dotnet/android#9862 (comment)Context:dotnet/android#10004Context:https://github.com/xamarin/monodroid/commit/326509e56d4e582c53bbe5dfe6d5c741a27f1af5Context:https://github.com/xamarin/monodroid/commit/940136ebf1318a7c57a855e2728ce2703c0240afEver get the feeling that everything is inextricably related?JNI has two pattens for create an instance of a Java type: 1. [`JNIEnv::NewObject(jclass clazz, jmethodID methodID, const jvalue* args)`][0] 2. [`JNIEnv::AllocObject(jclass clazz)`][1] + [`JNIEnv::CallNonvirtualVoidMethod(jobject obj, jclass clazz, jmethodID methodID, const jvalue* args)`][2]In both patterns: * `clazz` is the `java.lang.Class` of the type to create. * `methodID` is the constructor to execute * `args` are the constructor arguments.In .NET terms: * `JNIEnv::NewObject()` is equivalent to using `System.Reflection.ConstructorInfo.Invoke(object?[]?)`, while * `JNIEnv::AllocObject()` + `JNIEnv::CallNonvirtualVoidMethod()` is equivalent to using `System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(Type)` + `System.Reflection.MethodBase.Invoke(object?, object?[]?)`.Why prefer one over the other?When hand-writing your JNI code, `JNIEnv::NewObject()` is easier.This is less of a concern when a code generator is used.The *real* reason to *avoid* `JNIEnv::NewObject()` whenever possibleis the [Java Activation][3] scenario, summarized as the "are you sureyou want to do this?" [^1] scenario of invoking a virtual method from theconstructor:class Base { public Base() { VirtualMethod(); } public virtual void VirtualMethod() {}}class Derived : Base { public override void VirtualMethod() {}}Java and C# are identical here: when a constructor invokes a virtualmethod, the most derived method implementation is used, which willoccur *before* the constructor of the derived type has *started*execution. (With lots of quibbling about field initializers…)Thus, assume you have a Java `CallVirtualFromConstructorBase` type,which has its constructor Do The Wrong Thing™ and invoke a virtualmethod from the constructor, and that method is overridden in C#?// Javapublic class CallVirtualFromConstructorBase { public CallVirtualFromConstructorBase(int value) { calledFromConstructor(value); } public void calledFromConstructor(int value) { }}// C#public class CallVirtualFromConstructorDerived : CallVirtualFromConstructorBase { public CallVirtualFromConstructorDerived(int value) : base (value) { } public override void CalledFromConstructor(int value) { }}What happens with:var p = new CallVirtualFromConstructorDerived(42);The answer depends on whether or not `JNIEnv::NewObject()` is used.If `JNIEnv::NewObject()` is *not* used (the default!) 1. `CallVirtualFromConstructorDerived(int)` constructor begins execution, immediately calls `base(value)`. 2. `CallVirtualFromConstructorBase(int)` constructor runs, uses `JNIEnv::AllocObject()` to *create* (but not construct!) Java `CallVirtualFromConstructorDerived` instance. 3. `JavaObject.Construct(ref JniObjectReference, JniObjectReferenceOptions)` invoked, creating a mapping between the C# instance created in (1) and the Java instance created in (2). 4. `CallVirtualFromConstructorBase(int)` C# constructor calls `JniPeerMembers.InstanceMethods.FinishGenericCreateInstance()`, which eventually invokes `JNIEnv::CallNonvirtualVoidMethod()` with the Java `CallVirtualFromConstructorDerived(int)` ctor. 5. Java `CallVirtualFromConstructorDerived(int)` constructor invokes Java `CallVirtualFromConstructorBase(int)` constructor, which invokes `CallVirtualFromConstructorDerived.calledFromConstructor()`. 6. Marshal method (356485e) for `CallVirtualFromConstructorBase.CalledFromConstructor()` invoked, *immediately* calls `JniRuntime.JniValueManager.GetPeer()` (e288589) to obtain an instance upon which to invoke `.CalledFromConstructor()`, finds the instance mapping from (3), invokes `CallVirtualFromConstructorDerived.CalledFromConstructor()` override. 7. Marshal Method for `CalledFromConstructor()` returns, Java `CallVirtualFromConstructorBase(int)` constructor finishes, Java `CallVirtualFromConstructorDerived(int)` constructor finishes, `JNIEnv::CallNonvirtualVoidMethod()` finishes. 8. `CallVirtualFromConstructorDerived` instance finishes construction.If `JNIEnv::NewObject()` is used: 1. `CallVirtualFromConstructorDerived(int)` constructor begins execution, immediately calls `base(value)`. Note that this is the first created `CallVirtualFromConstructorDerived` instance, but it hasn't been registered yet. 2. `CallVirtualFromConstructorBase(int)` constructor runs, uses `JNIEnv::NewObject()` to construct Java `CallVirtualFromConstructorDerived` instance. 3. `JNIEnv::NewObject()` invokes Java `CallVirtualFromConstructorDerived(int)` constructor, which invokes `CallVirtualFromConstructorBase(int)` constructor, which invokes `CallVirtualFromConstructorDerived.calledFromConstructor()`. 4. Marshal method (356485e) for `CallVirtualFromConstructorBase.CalledFromConstructor()` invoked, *immediately* calls `JniRuntime.JniValueManager.GetPeer()` (e288589) to obtain an instance upon which to invoke `.CalledFromConstructor()`. Here is where things go "off the rails" compared to the `JNIEnv::AllocObject()` code path: There is no such instance -- we're still in the middle of constructing it! -- so we look for an "activation constructor". 5. `CallVirtualFromConstructorDerived(ref JniObjectReference, JniObjectReferenceOptions)` activation constructor executed. This is the *second* `CallVirtualFromConstructorDerived` instance created, and registers a mapping from the Java instance that we started constructing in (3) to what we'll call the "activation intermediary". The activation intermediary instance is marked as "Replaceable". 6. `CallVirtualFromConstructorDerived.CalledFromConstructor()` method override invoked on the activation intermediary. 7. Marshal Method for `CalledFromConstructor()` returns, Java `CallVirtualFromConstructorBase(int)` constructor finishes, Java `CallVirtualFromConstructorDerived(int)` constructor finishes, `JNIEnv::NewObject()` returns instance. 8. C# `CallVirtualFromConstructorBase(int)` constructor calls `JavaObject.Construct(ref JniObjectReference, JniObjectReferenceOptions)`, to create a mapping between (3) and (1). In .NET for Android, this causes the C# instance created in (1) to *replace* the C# instance created in (5), which allows "Replaceable" instance to be replaced. In dotnet/java-interop, this replacement *didn't* happen, which meant that `ValueManager.PeekPeer(p.PeerReference)` would return the activation intermediary, *not* `p`, which confuses everyone. 9. `CallVirtualFromConstructorDerived` instance finishes construction.For awhile, dotnet/java-interop did not fully support this scenarioaround `JNIEnv::NewObject()`. Additionally, support for using`JNIEnv::NewObject()` as part of`JniPeerMembers.JniInstanceMethods.StartCreateInstance()` was*removed* indec35f5.Which brings us todotnet/android#9862: where there is an observed"race condition" around `Android.App.Application` subclass creation.*Two* instances of `AndroidApp` were created, one from the "normal"app startup:at crc647fae2f69c19dcd0d.AndroidApp.n_onCreate(Native Method)at crc647fae2f69c19dcd0d.AndroidApp.onCreate(AndroidApp.java:25)at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1316)and another from an `androidx.work.WorkerFactory`:at mono.android.TypeManager.n_activate(Native Method)at mono.android.TypeManager.Activate(TypeManager.java:7)at crc647fae2f69c19dcd0d.SyncWorker.<init>(SyncWorker.java:23)at java.lang.reflect.Constructor.newInstance0(Native Method)at java.lang.reflect.Constructor.newInstance(Constructor.java:343)at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)However, what was odd about this "race condition" was that the*second* instance created would reliably win!Further investigation suggested that this was less of a"race condition" and more a bug in `AndroidValueManager`, wherein when"Replaceable" instances were created, an existing instance would*always* be replaced, even if the new instance was also Replaceable!This feels bananas; yes, Replaceable should be replaceable, but theexpectation was that it would be replaced by *non*-Replaceableinstances, not just any instance that came along later.Update `JniRuntimeJniValueManagerContract` to add a new`CreatePeer_ReplaceableDoesNotReplace()` test to codify the desiredsemantic that Replaceable instances do not replace Replaceableinstances.Surprisingly, this new test did not fail on java-interop, as`ManagedValueManager.AddPeer()` bails early when `PeekPeer()` findsa value, while `AndroidValueManager.AddPeer()` does not bail early.An obvious fix for `CreatePeer_ReplaceableDoesNotReplace()` withindotnet/android would be to adopt the "`AddPeer()` calls `PeekPeer()`"logic from java-interop. The problem is that doing so breaks[`ObjectTest.JnienvCreateInstance_RegistersMultipleInstances()`][5],as seen indotnet/android#10004!`JnienvCreateInstance_RegistersMultipleInstances()` in turn failswhen `PeekPeer()` is used because follows the `JNIEnv::NewObject()`[construction codepath][6]!public CreateInstance_OverrideAbsListView_Adapter (Context context) : base ( JNIEnv.CreateInstance ( JcwType, "(Landroid/content/Context;)V", new JValue (context)), JniHandleOwnership.TransferLocalRef){ AdapterValue = new ArrayAdapter (context, 0);}as `JNIEnv.CreateInstance()` uses `JNIEnv.NewObject()`.We thus have a conundrum: how do we fix *both*`CreatePeer_ReplaceableDoesNotReplace()` *and*`JnienvCreateInstance_RegistersMultipleInstances()`?The answer is to add proper support for the `JNIEnv::NewObject()`construction scenario to dotnet/java-interop, which in turn requires"lowering" the setting of `.Replaceable`. Previously, we would set`.Replaceable` *after* the activation constructor was invoked:// dotnet/android TypeManager.CreateInstance(), paraphrasingvar result = CreateProxy (type, handle, transfer);result.SetJniManagedPeerState (JniManagedPeerStates.Replaceable | JniManagedPeerStates.Activatable);return result;This is *too late*, as during execution of the activation constructor,the instance thinks it *isn't* replaceable, and thus creation of a newinstance via the activation constructor will replace an alreadyexisting replaceable instance; it's not until *after* the constructorfinished executing that we'd set `.Replaceable`.To fix this, update `JniRuntime.JniValueManager.TryCreatePeerInstance()`to first create an *uninitialized* instance, set `.Replaceable`, and*then* invoke the activation constructor. This allows`JniRuntime.JniValueManager.AddPeer()` to check to see if the newvalue is also replaceable, and ignore the replacement if appropriate.This in turn requires replacing:partial class /* JniRuntime. */ JniValueManager { protected virtual IJavaPeerable? TryCreatePeer () ref JniObjectReference reference, JniObjectReferenceOptions options, Type type);}with:partial class /* JniRuntime. */ JniValueManager { protected virtual bool TryConstructPeer () IJavaPeerable self, ref JniObjectReference reference, JniObjectReferenceOptions options, Type type);}This is fine because we haven't shipped `TryCreatePeer()` in a stablerelease yet.[^1]: See also [Framework Design Guidelines > Constructor Design][4]: > ❌ AVOID calling virtual members on an object inside its constructor. > Calling a virtual member will cause the most derived override to be > called, even if the constructor of the most derived type has not > been fully run yet.[0]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewObject[1]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#AllocObject[2]:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#CallNonvirtual_type_Method_routines[3]:https://learn.microsoft.com/en-us/previous-versions/xamarin/android/internals/architecture#java-activation[4]:https://learn.microsoft.com/dotnet/standard/design-guidelines/constructor[5]:https://github.com/dotnet/android/blob/9ad492a42b384519a8b1f1987adae82335536d9c/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs#L68-L79[6]:https://github.com/dotnet/android/blob/9ad492a42b384519a8b1f1987adae82335536d9c/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs#L151-L1601 parent8221b7d commitd3d3a1b
File tree
7 files changed
+270
-30
lines changed- src
- Java.Interop
- Java.Interop
- Java.Runtime.Environment/Java.Interop
- tests/Java.Interop-Tests/Java.Interop
7 files changed
+270
-30
lines changedLines changed: 26 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
401 | 401 | | |
402 | 402 | | |
403 | 403 | | |
404 | | - | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
405 | 425 | | |
406 | 426 | | |
407 | 427 | | |
408 | 428 | | |
409 | 429 | | |
410 | 430 | | |
411 | 431 | | |
412 | | - | |
| 432 | + | |
| 433 | + | |
413 | 434 | | |
414 | 435 | | |
415 | 436 | | |
| |||
421 | 442 | | |
422 | 443 | | |
423 | 444 | | |
424 | | - | |
| 445 | + | |
425 | 446 | | |
426 | | - | |
| 447 | + | |
427 | 448 | | |
428 | | - | |
| 449 | + | |
429 | 450 | | |
430 | 451 | | |
431 | 452 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
Lines changed: 4 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | 60 | | |
64 | 61 | | |
65 | 62 | | |
| |||
80 | 77 | | |
81 | 78 | | |
82 | 79 | | |
83 | | - | |
| 80 | + | |
84 | 81 | | |
85 | 82 | | |
86 | 83 | | |
| |||
91 | 88 | | |
92 | 89 | | |
93 | 90 | | |
94 | | - | |
| 91 | + | |
95 | 92 | | |
96 | 93 | | |
97 | 94 | | |
98 | | - | |
| 95 | + | |
| 96 | + | |
99 | 97 | | |
100 | 98 | | |
101 | 99 | | |
| |||
Lines changed: 20 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
19 | 25 | | |
20 | 26 | | |
21 | 27 | | |
22 | 28 | | |
23 | 29 | | |
24 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
25 | 43 | | |
26 | | - | |
| 44 | + | |
27 | 45 | | |
28 | 46 | | |
29 | 47 | | |
| |||
Lines changed: 27 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
2 | 5 | | |
3 | 6 | | |
4 | 7 | | |
| |||
24 | 27 | | |
25 | 28 | | |
26 | 29 | | |
27 | | - | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
28 | 36 | | |
29 | 37 | | |
30 | | - | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
31 | 46 | | |
32 | 47 | | |
33 | 48 | | |
34 | 49 | | |
35 | 50 | | |
36 | 51 | | |
37 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
38 | 56 | | |
39 | 57 | | |
40 | 58 | | |
41 | 59 | | |
| 60 | + | |
| 61 | + | |
42 | 62 | | |
43 | 63 | | |
44 | 64 | | |
| |||
47 | 67 | | |
48 | 68 | | |
49 | 69 | | |
| 70 | + | |
| 71 | + | |
50 | 72 | | |
51 | 73 | | |
52 | 74 | | |
53 | 75 | | |
54 | 76 | | |
55 | 77 | | |
56 | 78 | | |
57 | | - | |
| 79 | + | |
58 | 80 | | |
59 | 81 | | |
60 | 82 | | |
| |||
63 | 85 | | |
64 | 86 | | |
65 | 87 | | |
66 | | - | |
| 88 | + | |
67 | 89 | | |
68 | 90 | | |
69 | 91 | | |
| |||
Lines changed: 132 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
10 | 15 | | |
11 | 16 | | |
12 | 17 | | |
13 | | - | |
| 18 | + | |
14 | 19 | | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
19 | 61 | | |
20 | 62 | | |
21 | 63 | | |
22 | | - | |
| 64 | + | |
23 | 65 | | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
29 | 150 | | |
30 | 151 | | |
31 | 152 | | |
| |||
0 commit comments
Comments
(0)