You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Java.Interop] Support Desugar + interface static methods (#1077)
Context: 1f27ab5
Context: dotnet/android@f6f11a5
Context: dotnet/android#7663 (comment)
Commit 1f27ab5 added partial support for [desugaring][0], which
rewrites Java bytecode so that [default interface methods][1] and
[static methods in interfaces][2] can be supported on pre-Android 7.0
(API-24) devices, as the pre-API-24 ART runtime does not directly
support those bytecode constructs.
The hope was that commit 1f27ab5 in concert with
dotnet/android@f6f11a5a would allow static methods on
interfaces to work, by having
`Java.Interop.JniPeerMembers.JniStaticMethods.GetMethodInfo()`
call `JniRuntime.JniTypeManager.GetStaticMethodFallbackTypes()`.
xamarin/xamarin-android would then override
`GetStaticMethodFallbackTypes()`, allowing `GetMethodInfo()` to
instead resolve the static method from the fallback type, allowing
the static method invocation to work.
> TODO: Update xamarin-android to override
> `GetStaticMethodFallbackTypes()`, to return
> `$"{jniSimpleReference}$-CC"`.
What *actually* happened? Not enough testing happened, such that
when it was *actually* attempted, it blew up bigly:
JNI DETECTED ERROR IN APPLICATION: can't call static int com.xamarin.android.StaticMethodsInterface$-CC.getValue() with class java.lang.Class<com.xamarin.android.StaticMethodsInterface>
in call to CallStaticIntMethodA
from void crc641149b9fe658fbe8e.MainActivity.n_onCreate(android.os.Bundle)
Oops.
What went wrong?
The problem is that [`JNIEnv::CallStatic*Method()`][3] requires that
we provide the `jclass clazz` value for the type that declares the
static method, but we're providing the wrong class!
Specifically, consider this Java interface:
public interface StaticMethodsInterface {
static int getValue() {
return 3;
}
}
In a desugared environment, this is transformed into the *pair* of
types:
public interface StaticMethodsInterface {
}
public class StaticMethodsInterface$-CC {
public static int getValue() {
return 3;
}
}
`GetStaticMethodFallbackTypes("StaticMethodsInterface")` returns
`StaticMethodsInterface$-CC`, allowing `GetMethodInfo()` to lookup
`StaticMethodsInterface$-CC` and resolve the method
`getValue.()I`. However, when
`JniPeerMembers.JniStaticMethods.Invoke*Method()` is later invoked,
the `jclass` value for `StaticMethodsInterface` is provided, *not*
the value for `StaticMethodsInterface$-CC`. It's as if we do:
var from = new Java.Interop.JniType ("com/xamarin/android/StaticMethodsInterface");
var to = new Java.Interop.JniType ("com/xamarin/android/StaticMethodsInterface$-CC");
var m = to.GetStaticMethod ("getValue", "()I");
var r = Java.Interop.JniEnvironment.StaticMethods.CallStaticIntMethod (from.PeerReference, m);
The problem is that we need to use `to.PeerReference`, *not*
`from.PeerReference`!
Fix `GetMethodInfo()` so that when a method is resolved from a
fallback type, the type instance is stored in the
`JniMethodInfo.StaticRedirect` field, and then update the
`Invoke*Method()` methods so that `JniMethodInfo.StaticRedirect` is
passed to `JNIEnv::CallStatic*Method()` if it is set, before
defaulting to `JniPeerMembers.JniPeerType`.
"Optimization opportunity": the approach taken does not attempt to
cache the `JniType` instances which correspond to the fallback types.
Consequently, it is possible that multiple GREFs could be consumed
for the `JniMethodInfo.StaticRedirect` instances, as each instance
will be unique. This was done for expediency, and because @jonpryor
doesn't know if this will be an actual problem in practice.
Unrelatedly, fix the `JniPeerMembers(string, Type, bool)` constructor
so that it participates in type remapping. Without this change,
interface types cannot be renamed by the 1f27ab5 infrastructure.
[0]: https://developer.android.com/studio/write/java8-support#library-desugaring
[1]: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
[2]: https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html#static
[3]: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#CallStatic_type_Method_routines
Copy file name to clipboardExpand all lines: tests/Java.Interop-Tests/Java.Interop/JniPeerMembersTests.cs
+47Lines changed: 47 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -108,6 +108,38 @@ public void ReplaceInstanceMethodWithStaticMethod ()
108
108
// Shouldn't throw; should instead invoke ObjectHelper.getHashCodeHelper(Object)
109
109
o.remappedToStaticHashCode();
110
110
}
111
+
112
+
#if !__ANDROID__
113
+
// Note: this test looks up a static method from one class, then
114
+
// calls `JNIEnv::CallStaticObjectMethod()` passing in a jclass
115
+
// for a *different* class.
116
+
//
117
+
// This appears to work on Desktop JVM.
118
+
//
119
+
// On Android, this will ABORT the app:
120
+
// JNI DETECTED ERROR IN APPLICATION: can't call static int com.xamarin.interop.DesugarAndroidInterface$_CC.getClassName() with class java.lang.Class<com.xamarin.interop.AndroidInterface>
121
+
// in call to CallStaticObjectMethodA
122
+
//
123
+
// *Fascinating* the differences that can appear between JVM implementations
0 commit comments