forked from dotnet/java-interop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Java.Interop] Support Desugar + interface static methods (dotnet#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
- Loading branch information
Showing
8 changed files
with
120 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<Project> | ||
|
||
<Target Name="BuildInteropTestJar" | ||
BeforeTargets="Build" | ||
Inputs="@(JavaInteropTestJar)" | ||
Outputs="$(OutputPath)interop-test.jar"> | ||
<MakeDir Directories="$(IntermediateOutputPath)it-classes" /> | ||
<ItemGroup> | ||
<_Source Include="@(JavaInteropTestJar->Replace('%5c', '/'))" /> | ||
</ItemGroup> | ||
<WriteLinesToFile | ||
File="$(IntermediateOutputPath)_java_sources.txt" | ||
Lines="@(_Source)" | ||
Overwrite="True" | ||
/> | ||
<Exec Command=""$(JavaCPath)" $(_JavacSourceOptions) -d "$(IntermediateOutputPath)it-classes" -classpath "$(OutputPath)../$(Configuration)/java-interop.jar" "@$(IntermediateOutputPath)_java_sources.txt"" /> | ||
<Delete Files="$(IntermediateOutputPath)_java_sources.txt" /> | ||
<Exec Command=""$(JarPath)" cf "$(OutputPath)interop-test.jar" -C "$(IntermediateOutputPath)it-classes" ." /> | ||
</Target> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
tests/Java.Interop-Tests/java/com/xamarin/interop/AndroidInterface.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.xamarin.interop; | ||
|
||
// When Android Desugaring is enabled -- the default when targeting API-25 and earlier -- | ||
// certain Java constructs result in Java bytecode rewriting. | ||
// Interface static methods are *moved* into $-CC types. | ||
public interface AndroidInterface { | ||
|
||
// When Desugaring is enabled, this is moved to `AndroidInterface$-CC.getClassName()`, | ||
// and the original `AndroidInterface.getClassName()` *no longer exists*. | ||
|
||
// public static String getClassName() { | ||
// return "AndroidInterface"; | ||
// } | ||
} |
8 changes: 8 additions & 0 deletions
8
tests/Java.Interop-Tests/java/com/xamarin/interop/DesugarAndroidInterface$_CC.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.xamarin.interop; | ||
|
||
public class DesugarAndroidInterface$_CC { | ||
|
||
public static String getClassName() { | ||
return "DesugarAndroidInterface$-CC"; | ||
} | ||
} |