Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9a27140

Browse files
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/Appendable
1 parentea7d8c2 commit9a27140

File tree

3 files changed

+32
-44
lines changed

3 files changed

+32
-44
lines changed

‎src/Mono.Android/Java.Interop/JavaObjectExtensions.cs‎

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -81,41 +81,9 @@ internal static TResult? _JavaCast<
8181
if(instance.Handle==IntPtr.Zero)
8282
thrownewObjectDisposedException(instance.GetType().FullName);
8383

84-
TyperesultType=typeof(TResult);
85-
if(resultType.IsClass){
86-
return(TResult)CastClass(instance,resultType);
87-
}
88-
elseif(resultType.IsInterface){
89-
return(TResult?)Java.Lang.Object.GetObject(instance.Handle,JniHandleOwnership.DoNotTransfer,resultType);
90-
}
91-
else
92-
thrownewNotSupportedException(FormattableString.Invariant($"Unable to convert type '{instance.GetType().FullName}' to '{resultType.FullName}'."));
93-
}
94-
95-
staticIJavaObjectCastClass(
96-
IJavaObjectinstance,
97-
[DynamicallyAccessedMembers(Constructors)]
98-
TyperesultType)
99-
{
100-
varklass=JNIEnv.FindClass(resultType);
101-
try{
102-
if(klass==IntPtr.Zero)
103-
thrownewArgumentException("Unable to determine JNI class for '"+resultType.FullName+"'.","TResult");
104-
if(!JNIEnv.IsInstanceOf(instance.Handle,klass))
105-
thrownewInvalidCastException(
106-
FormattableString.Invariant($"Unable to convert instance of type '{instance.GetType().FullName}' to type '{resultType.FullName}'."));
107-
}finally{
108-
JNIEnv.DeleteGlobalRef(klass);
109-
}
110-
111-
if(resultType.IsAbstract){
112-
// TODO: keep in sync with TypeManager.CreateInstance() algorithm
113-
varinvokerType=GetInvokerType(resultType);
114-
if(invokerType==null)
115-
thrownewArgumentException("Unable to get Invoker for abstract type '"+resultType.FullName+"'.","TResult");
116-
resultType=invokerType;
117-
}
118-
return(IJavaObject)TypeManager.CreateProxy(resultType,instance.Handle,JniHandleOwnership.DoNotTransfer);
84+
return(TResult)Java.Lang.Object.GetObject(instance.Handle,JniHandleOwnership.DoNotTransfer,typeof(TResult))??
85+
thrownewInvalidCastException(
86+
FormattableString.Invariant($"Unable to convert instance of type '{instance.GetType().FullName}' to type '{typeof(TResult).FullName}'."));
11987
}
12088

12189
internalstaticIJavaObject?JavaCast(
@@ -132,14 +100,9 @@ static IJavaObject CastClass (
132100
if(resultType.IsAssignableFrom(instance.GetType()))
133101
returninstance;
134102

135-
if(resultType.IsClass){
136-
returnCastClass(instance,resultType);
137-
}
138-
elseif(resultType.IsInterface){
139-
return(IJavaObject?)Java.Lang.Object.GetObject(instance.Handle,JniHandleOwnership.DoNotTransfer,resultType);
140-
}
141-
else
142-
thrownewNotSupportedException(FormattableString.Invariant($"Unable to convert type '{instance.GetType().FullName}' to '{resultType.FullName}'."));
103+
return(IJavaObject?)Java.Lang.Object.GetObject(instance.Handle,JniHandleOwnership.DoNotTransfer,resultType)??
104+
thrownewInvalidCastException(
105+
FormattableString.Invariant($"Unable to convert instance of type '{instance.GetType().FullName}' to type '{resultType.FullName}'."));
143106
}
144107

145108
// typeof(Foo) -> FooInvoker

‎src/Mono.Android/Java.Interop/TypeManager.cs‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ internal static IJavaPeerable CreateInstance (IntPtr handle, JniHandleOwnership
336336

337337
handleClass=JniEnvironment.Types.GetObjectClass(newJniObjectReference(handle));
338338
if(!JniEnvironment.Types.IsAssignableFrom(handleClass,typeClass)){
339-
Logger.Log(LogLevel.Info,"*jonp*",$"# jonp: can't assign `{GetClassName(handleClass.Handle)}` to `{GetClassName(typeClass.Handle)}`");
340339
returnnull;
341340
}
342341
}finally{

‎tests/Mono.Android-Tests/Java.Interop/JavaObjectExtensionsTests.cs‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,32 @@ public void JavaCast_InterfaceCast ()
4040
}
4141
}
4242

43+
[Test]
44+
publicvoidJavaCast_BadInterfaceCast()
45+
{
46+
usingvarn=newJava.Lang.Integer(42);
47+
vare=Assert.Catch<Exception>(()=>JavaObjectExtensions.JavaCast<Java.Lang.IAppendable>(n));
48+
if(eisSystem.Reflection.TargetInvocationExceptiontie){
49+
// .NET 8 behavior
50+
Assert.IsTrue(tie.InnerExceptionisInvalidCastException);
51+
}elseif(eisInvalidCastExceptionice){
52+
// Yay
53+
}else{
54+
Assert.Fail($"Unexpected exception type:{e.GetType()}:{e.ToString()}");
55+
}
56+
}
57+
58+
[Test]
59+
publicvoidJavaCast_ObtainOriginalInstance()
60+
{
61+
usingvarlist=newJava.Util.ArrayList();
62+
usingvarcopy=newJava.Lang.Object(list.Handle,JniHandleOwnership.DoNotTransfer);
63+
usingvaral=JavaObjectExtensions.JavaCast<Java.Util.AbstractList>(copy);
64+
65+
// Semantic change: in .NET 8, `al` is a new `AbstractListInvoker` instance, not `list`
66+
Assert.AreSame(list,al);
67+
}
68+
4369
[Test]
4470
publicvoidJavaCast_InvalidTypeCastThrows()
4571
{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp