-
Notifications
You must be signed in to change notification settings - Fork 561
[linker] improve method detection in FixAbstractMethodsStep #861
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[linker] improve method detection in FixAbstractMethodsStep #861
Conversation
|
As discussed elsewhere, I do not like explicit string comparison, as it's likely to be wrong. For example, the IL name of explicitly-implemented interface methods is compiler-specific, so even if Instead, the fix should probably involve |
4f63cbb to
528219b
Compare
|
build |
|
I will also extend the test case. I need to fix a linker bug first though. |
528219b to
5e0a0d8
Compare
|
build |
5e0a0d8 to
8a6f16e
Compare
|
added unit test |
| } | ||
|
|
||
| var mi = ic.GetType ().GetMethod ("MethodWithCursor"); | ||
| var mi2 = ic.GetType ().GetMethod ("global::Test.Bindings.ICursor.MethodWithCursor"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is compiler-specific, and will fail if/when the compiler changes.
For example, given:
class T : IEnumerable {
IEnumerator global::System.Collections.IEnumerable.GetEnumerator()
{
return null;
}
}When compiling the above with mcs, we get:
.method private hidebysig newslot virtual final
instance class [mscorlib]System.Collections.IEnumerator
System.Collections.IEnumerable.GetEnumerator() cil managed
compare to csc (Roslyn):
.method private hidebysig newslot virtual final
instance class [mscorlib]System.Collections.IEnumerator
'global::System.Collections.IEnumerable.GetEnumerator'() cil managed
Use mcs, and this test will break. That's not a good test.
The forms team run into an issue with debugger in VS [1]. During
investigation it turned out that our FixAbstractMethodsStep injects 2
methods, which were not needed, as the methods were already
implemented. The bug itself looks like an issue, where original mdb
file was used in the apk, instead of the one written by our linker.
This change improves the method detection, where explicit interface
methods name starts with `global::` [2]. In this particular case it
were these methods:
global::Android.Views.View.IOnTouchListener.OnTouch
global::Android.Views.View.IOnClickListener.OnClick
This is possibly compiler specific, so we rather look in
MethodDefinition's Overrides to find the interface method and compare
it to the interface method we are looking for.
[1] https://bugzilla.xamarin.com/show_bug.cgi?id=59293
[2] excerpt from ikdasm output:
.method private hidebysig newslot virtual final
instance void 'global::Android.Views.View.IOnClickListener.OnClick'(class [Mono.Android]Android.Views.View v) cil managed
{
.override [Mono.Android]Android.Views.View/IOnClickListener::OnClick
8a6f16e to
e4848bd
Compare
|
build |
…t#861)_wow Context: https://bugzilla.xamarin.com/show_bug.cgi?id=22074 Context: https://bugzilla.xamarin.com/show_bug.cgi?id=59293 Context: https://developer.xamarin.com/releases/android/xamarin.android_6/xamarin.android_6.1/#Improved_Java_Interface_Versioning_Support **Background**: Until [C# gains support for default interface methods][ifaces], there is an "impedance mismatch" between Java interfaces and C# interfaces: Java methods can be added to interfaces *without* requiring that existing classes implementing those interfaces implement the "new" methods. (*This is not* Java default interface methods!) [ifaces]: https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md Consider [`android.database.Cursor`][cursor]: [cursor]: https://developer.android.com/reference/android/database/Cursor.html ```java // API-1 interface Cursor { void close(); // ... } class MyCursor implements Cursor { public void close() {} } ``` The `Cursor` interface had several methods added to it over time -- *before* the Android SDK supported default interface methods! -- e.g. the addition of `Cursor.getType()` in API-11: ```java interface Cursor { // in API-1 void close(); // ... // Added in API-11 int getType(int columnIndex); } ``` Question: What happens when `MyCursor.getType()` is invoked, if/when `MyCursor` *has not* been recompiled? ```java Cursor c = new MyCursor(); // Implements API-1 API, *not* API-11! c.getType(0); // Method added in API-11! ``` What happens is that an [`AbstractMethodError`][ame] is thrown. [ame]: https://developer.android.com/reference/java/lang/AbstractMethodError.html Compare to C#, in which *the type cannot be loaded* if an interface has a method added: [ERROR] FATAL UNHANDLED EXCEPTION: System.TypeLoadException: VTable setup of type MyCursor failed What this meant is [Bug #22074][b22074]: if you have a Xamarin.Android library, and that library: 1. Has `$(TargetFrameworkVersion)`=v2.3 (API-10), and 2. Has a type which implements `Android.Database.ICursor` and you then use the type (2) in an App which has `$(TargetFrameworkVersion)`=v4.4, the app would crash with a `TypeLoadException`. [b22074]: https://bugzilla.xamarin.com/show_bug.cgi?id=22074 The fix, [introduced in Xamarin.Android 6.1][xa6.1], is the `FixAbstractMethodsStep` step, which looks at every type in every assembly to be included in the `.apk`, and checks to see if that type is "missing" any abstract methods. If any such type is found, then the "missing" method is *inserted*, and it will throw an `AbstractMethodError`. [xa6.1]: https://developer.xamarin.com/releases/android/xamarin.android_6/xamarin.android_6.1/#Improved_Java_Interface_Versioning_Support For example, if an assembly `Lib.dll` built against API-10 has: ```csharp public class MyCursor : Java.Lang.Object, Android.Database.ICursor { // Implement `ICursor` methods from API-10 } ``` then when `Lib.dll` is packaged as part of an API-11+ app the `FixAbstractMethodsStep` step will *add* the following method: ```csharp int ICursor.GetType(int columnIndex) { throw new Java.Lang.AbstractMethodError(); } ``` **The Problem**: The Xamarin.Forms team ran into an issue with [the debugger in Visual Studio][b59293]. During investigation it turned out that our `FixAbstractMethodsStep` injects two methods which were not needed, as the methods were already implemented. [b59293]: https://bugzilla.xamarin.com/show_bug.cgi?id=59293 **The Fix**: Improve "missing abstract method" detection by looking in `MethodDefinition.Overrides` to find the interface method and compare it to the interface method we are looking for, not just using the method name. This will be more resilient to compiler changes -- not all compilers emit the same name for explicitly implemented interface methods -- and prevent us from emitting explicit interface methods when they're already present.
Context: #6131 (review) Context: fcb9ea3 Changes: http://github.com/xamarin/Java.Interop/compare/4fb7c147f8c6eb9bf94d9bfb8305c7d2a7a9fb33...dd1ef455ee4fbfa7e17f34c51cbe2ef24459e2e6 * dotnet/java-interop@dd1ef455: Bump to mono/linker/main@b888d67 Mono.Cecil 0.11.4 (#861) Commit fcb9ea3 didn't fix the build breakage. Bump to xamarin/Java.Interop/main@dd1ef455 and set `$(_XamarinAndroidCecilVersion)`, which will override the Mono.Cecil NuGet package version within the Java.Interop build. This will hopefully ensure/allow everything to now use Cecil 0.11.4. TODO: This Quick-And-Dirty approach hardcodes `$(_XamarinAndroidCecilVersion)`=0.11.4, meaning we now have two different properties for the same thing: Directory.Build.props: <MonoCecilVersion>0.11.4</MonoCecilVersion> build-tools/scripts/Configuration.Java.Interop.Override.props: <_XamarinAndroidCecilVersion>0.11.4</_XamarinAndroidCecilVersion> This is "undesirable". Is there a way to update `xaprepare` so that we can generate `external/Java.Interop/Configuration.Override.props` so that `$(_XamarinAndroidCecilVersion)`=`$(MonoCecilVersion)`? (Assuming that this approach even works…)
Changes: dotnet/installer@cc10fae...3b43390 Changes: dotnet/linker@6eae019...0cb9250 Changes: dotnet/runtime@d019e70...cf52b7e Updates: * Microsoft.Dotnet.Sdk.Internal: from 6.0.100-rc.1.21376.3 to 6.0.100-rc.1.21379.2 * Microsoft.NET.ILLink.Tasks: from 6.0.100-preview.6.21370.1 to 6.0.100-preview.6.21378.1 * Microsoft.NETCore.App.Ref: from 6.0.0-rc.1.21374.7 to 6.0.0-rc.1.21378.2 Bump to Mono.Cecil 0.11.4. Hopefully fixes a [build break][0]: CSC : error CS1705: Assembly 'illink' with identity 'illink, Version=6.0.100.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' uses 'Mono.Cecil, Version=0.11.4.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e' which has a higher version than referenced assembly 'Mono.Cecil' with identity 'Mono.Cecil, Version=0.11.3.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e' Bump `$(MonoCecilVersion)` to compensate? Note: an actual fix very likely requires a Java.Interop bump, as Java.Interop references Cecil 0.11.3 (same as the error message) while `$(MonoCecilVersion)` is 0.11.2 (not present in the error). [0]: #6131 (review) Context: #6131 (review) Context: fcb9ea3 Changes: http://github.com/xamarin/Java.Interop/compare/4fb7c147f8c6eb9bf94d9bfb8305c7d2a7a9fb33...dd1ef455ee4fbfa7e17f34c51cbe2ef24459e2e6 * dotnet/java-interop@dd1ef45: Bump to dotnet/linker@b888d67 Mono.Cecil 0.11.4 (#861) Commit fcb9ea3 didn't fix the build breakage. Bump to dotnet/java-interop@dd1ef45 and set `$(_XamarinAndroidCecilVersion)`, which will override the Mono.Cecil NuGet package version within the Java.Interop build. This will hopefully ensure/allow everything to now use Cecil 0.11.4. TODO: This Quick-And-Dirty approach hardcodes `$(_XamarinAndroidCecilVersion)`=0.11.4, meaning we now have two different properties for the same thing: Directory.Build.props: <MonoCecilVersion>0.11.4</MonoCecilVersion> build-tools/scripts/Configuration.Java.Interop.Override.props: <_XamarinAndroidCecilVersion>0.11.4</_XamarinAndroidCecilVersion> This is "undesirable". Is there a way to update `xaprepare` so that we can generate `external/Java.Interop/Configuration.Override.props` so that `$(_XamarinAndroidCecilVersion)`=`$(MonoCecilVersion)`? (Assuming that this approach even works…) Co-authored-by: Jonathan Pryor <jonpryor@vt.edu>
The forms team run into an issue with debugger in VS [1]. During
investigation it turned out that our FixAbstractMethodsStep injects 2
methods, which were not needed, as the methods were already
implemented. The bug itself looks like an issue, where original mdb
file was used in the apk, instead of the one written by our linker.
This change improves the method detection, where method name starts
with
global::. In this particular case it were these methods:[1] https://bugzilla.xamarin.com/show_bug.cgi?id=59293