diff --git a/Documentation/release-notes/4260.md b/Documentation/release-notes/4260.md new file mode 100644 index 00000000000..cf9562f4ec3 --- /dev/null +++ b/Documentation/release-notes/4260.md @@ -0,0 +1,24 @@ +### Linker Behavior Change + +Xamarin.Android has to consolidate changes to the Android APIs and how +they affect C# types. If you consider an example such as: + +* API 28 has an `IFoo` interface in `Mono.Android.dll` for `v9.0`. +* C# code implements `IFoo` in class `Bar`. +* API 29 adds another method to `IFoo` in `Mono.Android.dll` for + `v10.0`. +* Apps built targeting API 30 / `v10.0` will fix up `Bar` to implement + the new method, but throw `Java.Lang.AbstractMethodError` if it was + called. + +If the linker didn't make this change, you would instead hit a crash +when trying to create an instance of type `Bar` such as: + + System.TypeLoadException: VTable setup of type 'Bar' failed. + +Xamarin.Android has some performance improvements to the linker in +this area. Existing apps *should* not be affected. + +Submit a [bug report][bug] if you find an issue in this area. + +[bug]: https://github.com/xamarin/xamarin-android/wiki/Submitting-Bugs,-Feature-Requests,-and-Pull-Requests diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs index 5ba93c9992b..7564acd114c 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs @@ -158,7 +158,7 @@ bool HaveSameSignature (TypeReference iface, MethodDefinition iMethod, MethodDef if (IsInOverrides (iMethod, tMethod)) return true; - if (iMethod.Name != tMethod.Name && (iMethod.DeclaringType == null || (iMethod.DeclaringType.DeclaringType == null ? (string.Format ("{0}.{1}", iface.FullName, iMethod.Name) != tMethod.Name) : (string.Format ("{0}.{1}.{2}", iMethod.DeclaringType.DeclaringType, iface.Name, iMethod.Name) != tMethod.Name)))) + if (iMethod.Name != tMethod.Name) return false; if (!CompareTypes (iMethod.MethodReturnType.ReturnType, tMethod.MethodReturnType.ReturnType)) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs index a0e28bb54fa..cad294d6d29 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs @@ -70,6 +70,69 @@ static void CreateAbstractIfaceImplementation (string assemblyPath, AssemblyDefi } } + [Test] + public void FixAbstractMethodsStep_Explicit () + { + var path = Path.Combine (Path.GetFullPath (XABuildPaths.TestOutputDirectory), "temp", TestName); + var step = new FixAbstractMethodsStep (); + var pipeline = new Pipeline (); + + Directory.CreateDirectory (path); + + using (var context = new LinkContext (pipeline)) { + + context.Resolver.AddSearchDirectory (path); + + var myAssemblyPath = Path.Combine (path, "MyAssembly.dll"); + + using (var android = CreateFauxMonoAndroidAssembly ()) { + android.Write (Path.Combine (path, "Mono.Android.dll")); + CreateExplicitInterface (myAssemblyPath, android); + } + + using (var assm = context.Resolve (myAssemblyPath)) { + step.Process (context); + + var impl = assm.MainModule.GetType ("MyNamespace.MyClass"); + Assert.AreEqual (2, impl.Methods.Count, "MyClass should contain 2 methods"); + var method = impl.Methods.FirstOrDefault (m => m.Name == "MyNamespace.IMyInterface.MyMethod"); + Assert.IsNotNull (method, "MyNamespace.IMyInterface.MyMethod should exist"); + method = impl.Methods.FirstOrDefault (m => m.Name == "MyMissingMethod"); + Assert.IsNotNull (method, "MyMissingMethod should exist"); + } + } + + Directory.Delete (path, true); + } + + static void CreateExplicitInterface (string assemblyPath, AssemblyDefinition android) + { + using (var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("NestedIFaceTest", new Version ()), "NestedIFaceTest", ModuleKind.Dll)) { + var void_type = assm.MainModule.ImportReference (typeof (void)); + + // Create interface + var iface = new TypeDefinition ("MyNamespace", "IMyInterface", TypeAttributes.Interface); + + var iface_method = new MethodDefinition ("MyMethod", MethodAttributes.Abstract, void_type); + iface.Methods.Add (iface_method); + iface.Methods.Add (new MethodDefinition ("MyMissingMethod", MethodAttributes.Abstract, void_type)); + + assm.MainModule.Types.Add (iface); + + // Create implementing class + var jlo = assm.MainModule.Import (android.MainModule.GetType ("Java.Lang.Object")); + var impl = new TypeDefinition ("MyNamespace", "MyClass", TypeAttributes.Public, jlo); + impl.Interfaces.Add (new InterfaceImplementation (iface)); + + var explicit_method = new MethodDefinition ("MyNamespace.IMyInterface.MyMethod", MethodAttributes.Abstract, void_type); + explicit_method.Overrides.Add (new MethodReference (iface_method.Name, void_type, iface)); + impl.Methods.Add (explicit_method); + + assm.MainModule.Types.Add (impl); + assm.Write (assemblyPath); + } + } + static AssemblyDefinition CreateFauxMonoAndroidAssembly () { var assm = AssemblyDefinition.CreateAssembly (new AssemblyNameDefinition ("Mono.Android", new Version ()), "DimTest", ModuleKind.Dll);