- Notifications
You must be signed in to change notification settings - Fork564
Commit9a27140
authored
[Mono.Android] JavaCast<T>() should throw on unsupported types (#9145)
Context:dotnet/java-interop#910Context:dotnet/java-interop@1adb796Context:8928f11Context:35f41dcContext:ab412a5The `.JavaCast<T>()` extension method is for checking type castsacross 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 ofab412a5 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 isdotnet/java-interop@1adb7964 and8928f11, which optimizedinterface invokers (dotnet/java-interop#910). Part of thisoptimization involved *removing* the `Validate(IntPtr)` method fromthe interface invoker classes. (Was this part of the optimization?No. It's just that@jonpryor didn't think that `Validate()` wasneeded anymore, and that the checks it performed was already doneelsewhere. Turns Out™, that wasn't true!)The result of8928f11 is that `.JavaCast<T>()` would *always* returna non-`null` value for the requested type. Worse, any methodinvocations on that value would ***CRASH THE PROCESS***:var n = new Java.Lang.Integer(42);var a = n.JavaCast<Java.Lang.IAppendable>(); // does NOT throwa.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 CallObjectMethodAOops.Secondly, commit35f41dc actually added the checks that@jonpryorthought were already there, but as part of these checks`TypeManager.CreateInstance()` now returns `null` if the Java-sidetype 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 willlikely 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 byremoving 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 willreturn 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 trueand `abstractList` will be an `AbstractListInvoker`, meaning therewill 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 parentea7d8c2 commit9a27140
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
Comments
(0)