Commit 9a27140
authored
[Mono.Android] JavaCast<T>() should throw on unsupported types (#9145)
Context: dotnet/java-interop#910
Context: dotnet/java-interop@1adb796
Context: 8928f11
Context: 35f41dc
Context: ab412a5
The `.JavaCast<T>()` extension method is for checking type casts
across the JNI boundary. As such, it can be expected to fail.
Failure is indicated by throwing an exception.
var n = new Java.Lang.Integer(42);
var a = n.JavaCast<Java.Lang.IAppendable>(); // this should throw, right?
The last line should throw because, at this point anyway,
[`java.lang.Integer`][0] doesn't implement the
[`java.lang.Appendable`][1] interface.
What *actually* happens as of ab412a5 is that
`n.JavaCast<IAppendable>()` ***does not throw***;
instead, it returns `null`!
This is a ***BREAK*** from .NET 8, in which an exception *is* thrown:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.InvalidCastException: Unable to convert instance of type 'crc64382c01b66da5f22f/MainActivity' to type 'java.lang.Appendable'.
at Java.Lang.IAppendableInvoker.Validate(IntPtr handle)
at Java.Lang.IAppendableInvoker..ctor(IntPtr handle, JniHandleOwnership transfer)
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Constructor(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
--- End of inner exception stack trace ---
at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.ConstructorInfo.Invoke(Object[] parameters)
at Java.Interop.TypeManager.CreateProxy(Type type, IntPtr handle, JniHandleOwnership transfer)
at Java.Interop.TypeManager.CreateInstance(IntPtr handle, JniHandleOwnership transfer, Type targetType)
at Java.Lang.Object.GetObject(IntPtr handle, JniHandleOwnership transfer, Type type)
at Java.Interop.JavaObjectExtensions._JavaCast[IAppendable](IJavaObject instance)
What happened™?!
What happened are (at least) two things:
First is dotnet/java-interop@1adb7964 and 8928f11, which optimized
interface invokers (dotnet/java-interop#910). Part of this
optimization involved *removing* the `Validate(IntPtr)` method from
the interface invoker classes. (Was this part of the optimization?
No. It's just that @jonpryor didn't think that `Validate()` was
needed anymore, and that the checks it performed was already done
elsewhere. Turns Out™, that wasn't true!)
The result of 8928f11 is that `.JavaCast<T>()` would *always* return
a non-`null` value for the requested type. Worse, any method
invocations on that value would ***CRASH THE PROCESS***:
var n = new Java.Lang.Integer(42);
var a = n.JavaCast<Java.Lang.IAppendable>(); // does NOT throw
a.Append('a');
// app dies
`adb logcat` contains:
JNI DETECTED ERROR IN APPLICATION: can't call java.lang.Appendable java.lang.Appendable.append(char) on instance of java.lang.Integer
in call to CallObjectMethodA
Oops.
Secondly, commit 35f41dc actually added the checks that @jonpryor
thought were already there, but as part of these checks
`TypeManager.CreateInstance()` now returns `null` if the Java-side
type checks fail. This means that `.JavaCast<T>()` now returns
`null`, which prevents the above `JNI DETECTED ERROR IN APPLICATION`
crash (good), but means `.JavaCast<T>()` returns `null` which will
likely result in `NullReferenceException`s if the value is used.
Fix `.JavaCast<T>()` by updating `JavaObjectExtensions.JavaCast<T>()`
to throw an `InvalidCastException` if `TypeManager.CreateInstance()`
returns `null`.
Additionally, massively simplify the `.JavaCast<T>()` code paths by
removing the semantic distinction between interface and class types.
This in turn introduces *new* (intentional!) semantic changes.
Suppose we have an `ArrayList` instance:
var list = new Java.Util.ArrayList();
and to ensure "appropriate magic" happens, *alias* `list`:
var alias = new Java.Lang.Object(list.Handle, JniHandleOwnership.DoNotTransfer);
We now have two managed instances referring to the same Java instance.
Next, we want to use `alias` as an `AbstractList`:
var abstractList = alias.JavaCast<Java.Util.AbstractList>();
var newAlias = !object.ReferenceEquals(list, abstractList);
As `java.util.ArrayList` inherits `java.util.AbstractList`, this will
return a non-`null` value. *However*, what *type and instance* will
`abstractList` be?
In .NET 8 and .NET 9 *prior to this commit*, `newAlias` will be true
and `abstractList` will be an `AbstractListInvoker`, meaning there
will be *three* managed instances referring to the same Java-side
`ArrayList`.
Starting with this commit, `abstractList` be the same value as `list`,
have a runtime type of `ArrayList`, and `newAlias` will be false.
[0]: https://developer.android.com/reference/java/lang/Integer
[1]: https://developer.android.com/reference/java/lang/Appendable1 parent ea7d8c2 commit 9a27140
File tree
3 files changed
+32
-44
lines changed- src/Mono.Android/Java.Interop
- tests/Mono.Android-Tests/Java.Interop
3 files changed
+32
-44
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
81 | 81 | | |
82 | 82 | | |
83 | 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 | | - | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
119 | 87 | | |
120 | 88 | | |
121 | 89 | | |
| |||
132 | 100 | | |
133 | 101 | | |
134 | 102 | | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | | - | |
140 | | - | |
141 | | - | |
142 | | - | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
143 | 106 | | |
144 | 107 | | |
145 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
336 | 336 | | |
337 | 337 | | |
338 | 338 | | |
339 | | - | |
340 | 339 | | |
341 | 340 | | |
342 | 341 | | |
| |||
Lines changed: 26 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
43 | 69 | | |
44 | 70 | | |
45 | 71 | | |
| |||
0 commit comments