diff --git a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj index 92c92c0ee94..c92b0e32b1a 100644 --- a/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj +++ b/src/Microsoft.Android.Sdk.ILLink/Microsoft.Android.Sdk.ILLink.csproj @@ -16,7 +16,6 @@ - diff --git a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml index aa0a341e0bb..124edd61d93 100644 --- a/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml +++ b/src/Microsoft.Android.Sdk.ILLink/PreserveLists/Mono.Android.xml @@ -34,6 +34,9 @@ --> + + + diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsHelper.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsHelper.cs new file mode 100644 index 00000000000..599728580c6 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsHelper.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; + +using Resources = Xamarin.Android.Tasks.Properties.Resources; + +namespace MonoDroid.Tuner +{ + static class FixAbstractMethodsHelper + { + internal static bool FixAbstractMethods (AssemblyDefinition assembly, IMetadataResolver cache, ref MethodDefinition? abstractMethodErrorCtor, Func getMonoAndroidAssembly, Action logMessage) + { + bool changed = false; + foreach (var type in assembly.MainModule.Types) { + if (MightNeedFix (type, cache)) + changed |= FixAbstractMethods (type, cache, ref abstractMethodErrorCtor, getMonoAndroidAssembly, logMessage); + } + return changed; + } + + internal static void CheckAppDomainUsage (AssemblyDefinition assembly, Action warn, HashSet warnedAssemblies) + { + if (!warnedAssemblies.Add (assembly.Name.Name)) + return; + if (!assembly.MainModule.HasTypeReference ("System.AppDomain")) + return; + + foreach (var mr in assembly.MainModule.GetMemberReferences ()) { + if (mr.ToString ().Contains ("System.AppDomain System.AppDomain::CreateDomain")) { + warn (string.Format (CultureInfo.CurrentCulture, Resources.XA2000, assembly)); + break; + } + } + } + + static bool MightNeedFix (TypeDefinition type, IMetadataResolver cache) + { + return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", cache); + } + + static bool CompareTypes (TypeReference iType, TypeReference tType, IMetadataResolver cache) + { + if (iType.IsGenericParameter) + return true; + + if (iType.IsArray) { + if (!tType.IsArray) + return false; + return CompareTypes (iType.GetElementType (), tType.GetElementType (), cache); + } + + if (iType.IsByReference) { + if (!tType.IsByReference) + return false; + return CompareTypes (iType.GetElementType (), tType.GetElementType (), cache); + } + + if (iType.Name != tType.Name) + return false; + + if (iType.Namespace != tType.Namespace) + return false; + + var iTypeDef = cache.Resolve (iType); + if (iTypeDef == null) + return false; + + var tTypeDef = cache.Resolve (tType); + if (tTypeDef == null) + return false; + + if (iTypeDef.Module.FileName != tTypeDef.Module.FileName) + return false; + + if (iType is GenericInstanceType iGType && tType is GenericInstanceType tGType) { + if (iGType.GenericArguments.Count != tGType.GenericArguments.Count) + return false; + for (int i = 0; i < iGType.GenericArguments.Count; i++) { + if (iGType.GenericArguments [i].IsGenericParameter) + continue; + if (!CompareTypes (iGType.GenericArguments [i], tGType.GenericArguments [i], cache)) + return false; + } + } + + return true; + } + + static bool IsInOverrides (MethodDefinition iMethod, MethodDefinition tMethod, IMetadataResolver cache) + { + if (!tMethod.HasOverrides) + return false; + + foreach (var o in tMethod.Overrides) + if (o != null && iMethod.Name == o.Name && iMethod == cache.Resolve (o)) + return true; + + return false; + } + + static bool HaveSameSignature (TypeReference iface, MethodDefinition iMethod, MethodDefinition tMethod, IMetadataResolver cache) + { + if (IsInOverrides (iMethod, tMethod, cache)) + return true; + + if (iMethod.Name != tMethod.Name) + return false; + + if (!CompareTypes (iMethod.MethodReturnType.ReturnType, tMethod.MethodReturnType.ReturnType, cache)) + return false; + + if (iMethod.Parameters.Count != tMethod.Parameters.Count || iMethod.GenericParameters.Count != tMethod.GenericParameters.Count) + return false; + + if (iMethod.HasParameters) { + List m1p = new List (iMethod.Parameters); + List m2p = new List (tMethod.Parameters); + + for (int i = 0; i < m1p.Count; i++) { + if (!CompareTypes (m1p [i].ParameterType, m2p [i].ParameterType, cache)) + return false; + } + } + + if (iMethod.HasGenericParameters) { + List m1p = new List (iMethod.GenericParameters); + List m2p = new List (tMethod.GenericParameters); + + for (int i = 0; i < m1p.Count; i++) + if (!CompareTypes (m1p [i], m2p [i], cache)) + return false; + } + + return true; + } + + static bool FixAbstractMethods (TypeDefinition type, IMetadataResolver cache, ref MethodDefinition? abstractMethodErrorCtor, Func getMonoAndroidAssembly, Action logMessage) + { + if (!type.HasInterfaces) + return false; + + bool rv = false; + List typeMethods = new List (type.Methods); + foreach (var baseType in type.GetBaseTypes (cache)) + typeMethods.AddRange (baseType.Methods); + + foreach (var ifaceInfo in type.Interfaces) { + var iface = ifaceInfo.InterfaceType; + var ifaceDef = cache.Resolve (iface); + if (ifaceDef == null) { + logMessage ($"Unable to unresolve interface: {iface.FullName}"); + continue; + } + if (ifaceDef.HasGenericParameters) + continue; + + foreach (var iMethod in ifaceDef.Methods.Where (m => m.IsAbstract)) { + bool exists = false; + + foreach (var tMethod in typeMethods) { + if (HaveSameSignature (iface, iMethod, tMethod, cache)) { + exists = true; + break; + } + } + + if (!exists) { + abstractMethodErrorCtor ??= GetAbstractMethodErrorConstructor (getMonoAndroidAssembly); + AddNewExceptionMethod (type, iMethod, abstractMethodErrorCtor, logMessage); + rv = true; + } + } + } + + return rv; + } + + static TypeReference TryImportType (TypeDefinition declaringType, TypeReference type) + { + if (type.IsGenericParameter) + return type; + + return declaringType.Module.ImportReference (type); + } + + static void AddNewExceptionMethod (TypeDefinition type, MethodDefinition method, MethodDefinition abstractMethodErrorCtor, Action logMessage) + { + var newMethod = new MethodDefinition (method.Name, (method.Attributes | MethodAttributes.Final) & ~MethodAttributes.Abstract, TryImportType (type, method.ReturnType)); + + foreach (var paramater in method.Parameters) + newMethod.Parameters.Add (new ParameterDefinition (paramater.Name, paramater.Attributes, TryImportType (type, paramater.ParameterType))); + + var ilP = newMethod.Body.GetILProcessor (); + + ilP.Append (ilP.Create (Mono.Cecil.Cil.OpCodes.Newobj, type.Module.ImportReference (abstractMethodErrorCtor))); + ilP.Append (ilP.Create (Mono.Cecil.Cil.OpCodes.Throw)); + + type.Methods.Add (newMethod); + + logMessage ($"Added method: {method} to type: {type.FullName} scope: {type.Scope}"); + } + + static MethodDefinition GetAbstractMethodErrorConstructor (Func getMonoAndroidAssembly) + { + var assembly = getMonoAndroidAssembly (); + if (assembly != null) { + var errorException = assembly.MainModule.GetType ("Java.Lang.AbstractMethodError"); + if (errorException != null) { + foreach (var method in errorException.Methods) { + if (method.Name == ".ctor" && !method.HasParameters) { + return method; + } + } + } + } + + throw new Exception ("Unable to find Java.Lang.AbstractMethodError constructor in Mono.Android assembly"); + } + } +} 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 467533a0a59..ba7b64602aa 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixAbstractMethodsStep.cs @@ -1,340 +1,29 @@ -#nullable disable - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Java.Interop.Tools.Cecil; using Mono.Cecil; -using Mono.Linker; using Mono.Linker.Steps; -using Mono.Tuner; using Xamarin.Android.Tasks; -#if ILLINK -using Resources = Microsoft.Android.Sdk.ILLink.Properties.Resources; -#else // !ILLINK -using Resources = Xamarin.Android.Tasks.Properties.Resources; -#endif // !ILLINK - namespace MonoDroid.Tuner { /// - /// NOTE: this step is subclassed so it can be called directly from Xamarin.Android.Build.Tasks + /// Simplified FixAbstractMethodsStep for the no-trim path (LinkAssembliesNoShrink). + /// Core logic is in . /// - public class FixAbstractMethodsStep : BaseMarkHandler -#if !ILLINK - , IAssemblyModifierPipelineStep -#endif // !ILLINK + public class FixAbstractMethodsStep : BaseStep, IAssemblyModifierPipelineStep { - public override void Initialize (LinkContext context, MarkContext markContext) - { - base.Initialize (context, markContext); - markContext.RegisterMarkTypeAction (type => ProcessType (type)); - } - - bool CheckShouldProcessAssembly (AssemblyDefinition assembly) - { - if (!Annotations.HasAction (assembly)) - Annotations.SetAction (assembly, AssemblyAction.Skip); - - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) - return false; - - CheckAppDomainUsage (assembly, (string msg) => -#if ILLINK - Context.LogMessage (MessageContainer.CreateCustomWarningMessage (Context, msg, 6200, new MessageOrigin (), WarnVersion.ILLink5)) -#else // !ILLINK - Context.LogWarning ("XA2000", msg) -#endif // !ILLINK - ); - - return assembly.MainModule.HasTypeReference ("Java.Lang.Object"); - } - - void UpdateAssemblyAction (AssemblyDefinition assembly) - { - if (Annotations.GetAction (assembly) == AssemblyAction.Copy) - Annotations.SetAction (assembly, AssemblyAction.Save); - } + MethodDefinition? abstractMethodErrorCtor; - void ProcessType (TypeDefinition type) - { - var assembly = type.Module.Assembly; - if (!CheckShouldProcessAssembly (assembly)) - return; - - if (!MightNeedFix (type)) - return; - - if (!FixAbstractMethods (type)) - return; - - UpdateAssemblyAction (assembly); - MarkAbstractMethodErrorType (); - } - - internal bool FixAbstractMethods (AssemblyDefinition assembly) - { - bool changed = false; - foreach (var type in assembly.MainModule.Types) { - if (MightNeedFix (type)) - changed |= FixAbstractMethods (type); - } - return changed; - } - -#if !ILLINK public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) { // Only run this step on non-main user Android assemblies if (context.IsMainAssembly || !context.IsAndroidUserAssembly) return; - context.IsAssemblyModified |= FixAbstractMethods (assembly); - } -#endif // !ILLINK - - readonly HashSet warnedAssemblies = new (StringComparer.Ordinal); - - internal void CheckAppDomainUsage (AssemblyDefinition assembly, Action warn) - { - if (!warnedAssemblies.Add (assembly.Name.Name)) - return; - if (!assembly.MainModule.HasTypeReference ("System.AppDomain")) - return; - - foreach (var mr in assembly.MainModule.GetMemberReferences ()) { - if (mr.ToString ().Contains ("System.AppDomain System.AppDomain::CreateDomain")) { - warn (string.Format (CultureInfo.CurrentCulture, Resources.XA2000, assembly)); - break; - } - } - } - - bool MightNeedFix (TypeDefinition type) - { - return !type.IsAbstract && type.IsSubclassOf ("Java.Lang.Object", cache); - } - - bool CompareTypes (TypeReference iType, TypeReference tType) - { - if (iType.IsGenericParameter) - return true; - - if (iType.IsArray) { - if (!tType.IsArray) - return false; - return CompareTypes (iType.GetElementType (), tType.GetElementType ()); - } - - if (iType.IsByReference) { - if (!tType.IsByReference) - return false; - return CompareTypes (iType.GetElementType (), tType.GetElementType ()); - } - - if (iType.Name != tType.Name) - return false; - - if (iType.Namespace != tType.Namespace) - return false; - - TypeDefinition iTypeDef = cache.Resolve (iType); - if (iTypeDef == null) - return false; - - TypeDefinition tTypeDef = cache.Resolve (tType); - if (tTypeDef == null) - return false; - - if (iTypeDef.Module.FileName != tTypeDef.Module.FileName) - return false; - - if (iType is Mono.Cecil.GenericInstanceType && tType is Mono.Cecil.GenericInstanceType) { - GenericInstanceType iGType = iType as GenericInstanceType; - GenericInstanceType tGType = tType as GenericInstanceType; - - if (iGType.GenericArguments.Count != tGType.GenericArguments.Count) - return false; - for (int i = 0; i < iGType.GenericArguments.Count; i++) { - if (iGType.GenericArguments [i].IsGenericParameter) - continue; - if (!CompareTypes (iGType.GenericArguments [i], tGType.GenericArguments [i])) - return false; - } - } - - return true; - } - - bool IsInOverrides (MethodDefinition iMethod, MethodDefinition tMethod) - { - if (!tMethod.HasOverrides) - return false; - - foreach (var o in tMethod.Overrides) - if (o != null && iMethod.Name == o.Name && iMethod == cache.Resolve (o)) - return true; - - return false; - } - - bool HaveSameSignature (TypeReference iface, MethodDefinition iMethod, MethodDefinition tMethod) - { - if (IsInOverrides (iMethod, tMethod)) - return true; - - if (iMethod.Name != tMethod.Name) - return false; - - if (!CompareTypes (iMethod.MethodReturnType.ReturnType, tMethod.MethodReturnType.ReturnType)) - return false; - - if (iMethod.Parameters.Count != tMethod.Parameters.Count || iMethod.GenericParameters.Count != tMethod.GenericParameters.Count) - return false; - - if (iMethod.HasParameters) { - List m1p = new List (iMethod.Parameters); - List m2p = new List (tMethod.Parameters); - - for (int i = 0; i < m1p.Count; i++) { - if (!CompareTypes (m1p [i].ParameterType, m2p [i].ParameterType)) - return false; - } - } - - if (iMethod.HasGenericParameters) { - List m1p = new List (iMethod.GenericParameters); - List m2p = new List (tMethod.GenericParameters); - - for (int i = 0; i < m1p.Count; i++) - if (!CompareTypes (m1p [i], m2p [i])) - return false; - } - - return true; - } - - bool FixAbstractMethods (TypeDefinition type) - { - if (!type.HasInterfaces) - return false; - - bool rv = false; - List typeMethods = new List (type.Methods); - foreach (var baseType in type.GetBaseTypes (cache)) - typeMethods.AddRange (baseType.Methods); - - foreach (var ifaceInfo in type.Interfaces) { - var iface = ifaceInfo.InterfaceType; - var ifaceDef = cache.Resolve (iface); - if (ifaceDef == null) { - LogMessage ($"Unable to unresolve interface: {iface.FullName}"); - continue; - } - if (ifaceDef.HasGenericParameters) - continue; - - foreach (var iMethod in ifaceDef.Methods.Where (m => m.IsAbstract)) { - bool exists = false; - - foreach (var tMethod in typeMethods) { - if (HaveSameSignature (iface, iMethod, tMethod)) { - exists = true; - break; - } - } - - if (!exists) { - AddNewExceptionMethod (type, iMethod); - rv = true; - } - } - } - - return rv; - } - - TypeReference TryImportType (TypeDefinition declaringType, TypeReference type) - { - if (type.IsGenericParameter) - return type; - - return declaringType.Module.ImportReference (type); - } - - void AddNewExceptionMethod (TypeDefinition type, MethodDefinition method) - { - var newMethod = new MethodDefinition (method.Name, (method.Attributes | MethodAttributes.Final) & ~MethodAttributes.Abstract, TryImportType (type, method.ReturnType)); - - foreach (var paramater in method.Parameters) - newMethod.Parameters.Add (new ParameterDefinition (paramater.Name, paramater.Attributes, TryImportType (type, paramater.ParameterType))); - - var ilP = newMethod.Body.GetILProcessor (); - - ilP.Append (ilP.Create (Mono.Cecil.Cil.OpCodes.Newobj, type.Module.ImportReference (AbstractMethodErrorConstructor))); - ilP.Append (ilP.Create (Mono.Cecil.Cil.OpCodes.Throw)); - - type.Methods.Add (newMethod); - - LogMessage ($"Added method: {method} to type: {type.FullName} scope: {type.Scope}"); - } - - MethodDefinition abstractMethodErrorConstructor; - - MethodDefinition AbstractMethodErrorConstructor { - get { - if (abstractMethodErrorConstructor != null) - return abstractMethodErrorConstructor; - - var assembly = GetMonoAndroidAssembly (); - if (assembly != null) { - var errorException = assembly.MainModule.GetType ("Java.Lang.AbstractMethodError"); - if (errorException != null) { - foreach (var method in errorException.Methods) { - if (method.Name == ".ctor" && !method.HasParameters) { - abstractMethodErrorConstructor = method; - break; - } - } - } - } - - if (abstractMethodErrorConstructor == null) - throw new Exception ("Unable to find Java.Lang.AbstractMethodError constructor in Mono.Android assembly"); - - return abstractMethodErrorConstructor; - } - } - - bool markedAbstractMethodErrorType; - - void MarkAbstractMethodErrorType () - { - if (markedAbstractMethodErrorType) - return; - markedAbstractMethodErrorType = true; - - - var td = AbstractMethodErrorConstructor.DeclaringType; - Annotations.Mark (td); - Annotations.SetPreserve (td, TypePreserve.Nothing); - Annotations.AddPreservedMethod (td, AbstractMethodErrorConstructor); - } - - public virtual void LogMessage (string message) - { - Context.LogMessage (message); - } - - AssemblyDefinition GetMonoAndroidAssembly () - { -#if !ILLINK - return Context.Resolver.GetAssembly ("Mono.Android.dll"); -#else // ILLINK - return Context.GetLoadedAssembly ("Mono.Android"); -#endif // ILLINK + context.IsAssemblyModified |= FixAbstractMethodsHelper.FixAbstractMethods ( + assembly, + Context, + ref abstractMethodErrorCtor, + () => Context.GetAssembly ("Mono.Android"), + (msg) => LogMessage (msg)); } } } diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingFixAbstractMethodsStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingFixAbstractMethodsStep.cs new file mode 100644 index 00000000000..add24c98cac --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/PostTrimmingFixAbstractMethodsStep.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using Java.Interop.Tools.Cecil; +using Mono.Cecil; +using Xamarin.Android.Tasks; + +namespace MonoDroid.Tuner; + +/// +/// Post-trimming version of FixAbstractMethods that calls FixAbstractMethodsHelper directly. +/// Runs in after ILLink in the per-RID inner build. +/// The helper has its own type-level guards (MightNeedFix checks IsSubclassOf Java.Lang.Object). +/// +class PostTrimmingFixAbstractMethodsStep : IAssemblyModifierPipelineStep +{ + readonly IMetadataResolver cache; + readonly Func getMonoAndroidAssembly; + readonly Action logMessage; + readonly Action warn; + readonly HashSet warnedAssemblies = new (StringComparer.Ordinal); + MethodDefinition? abstractMethodErrorCtor; + + public PostTrimmingFixAbstractMethodsStep (IMetadataResolver cache, Func getMonoAndroidAssembly, Action logMessage, Action warn) + { + this.cache = cache; + this.getMonoAndroidAssembly = getMonoAndroidAssembly; + this.logMessage = logMessage; + this.warn = warn; + } + + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) + return; + + FixAbstractMethodsHelper.CheckAppDomainUsage (assembly, warn, warnedAssemblies); + + if (!assembly.MainModule.HasTypeReference ("Java.Lang.Object")) + return; + + context.IsAssemblyModified |= FixAbstractMethodsHelper.FixAbstractMethods (assembly, cache, ref abstractMethodErrorCtor, getMonoAndroidAssembly, logMessage); + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets index 84e68d8bf6f..f7706bd4be0 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets @@ -195,7 +195,6 @@ <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.PreserveApplications" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveRegistrations" /> <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="Microsoft.Android.Sdk.ILLink.PreserveJavaInterfaces" /> - <_TrimmerCustomSteps Include="$(_AndroidLinkerCustomStepAssembly)" Type="MonoDroid.Tuner.FixAbstractMethodsStep" /> <_TrimmerCustomSteps diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs index 9dc7e99e635..671cca41e60 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs @@ -22,7 +22,7 @@ protected override void BuildPipeline (AssemblyPipeline pipeline, MSBuildLinkCon { // FixAbstractMethodsStep var fixAbstractMethodsStep = new FixAbstractMethodsStep (); - fixAbstractMethodsStep.Initialize (context, new EmptyMarkContext ()); + fixAbstractMethodsStep.Initialize (context); pipeline.Steps.Add (fixAbstractMethodsStep); // FixLegacyResourceDesignerStep diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index 7a33ea3c7be..8ba7b9d2544 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -15,7 +15,7 @@ namespace Xamarin.Android.Tasks; /// /// This opens each assembly once (via DirectoryAssemblyResolver with ReadWrite) and /// runs all registered steps on it, then writes modified assemblies in-place. Currently -/// runs StripEmbeddedLibrariesStep and (optionally) AddKeepAlivesStep. +/// runs StripEmbeddedLibrariesStep, FixAbstractMethodsStep, and (optionally) AddKeepAlivesStep. /// /// Runs in the inner build after ILLink but before ReadyToRun/crossgen2 compilation, /// so that R2R images are generated from the already-modified assemblies. @@ -47,6 +47,27 @@ public override bool RunTask () var steps = new List (); steps.Add (new StripEmbeddedLibrariesStep (Log)); + + // Fix abstract methods: add stub implementations that throw Java.Lang.AbstractMethodError + // for unimplemented interface methods on Java.Lang.Object subclasses. + // Memoize the Mono.Android resolution so the attempt happens at most once. + AssemblyDefinition? monoAndroidAssembly = null; + bool monoAndroidResolutionAttempted = false; + steps.Add (new PostTrimmingFixAbstractMethodsStep (cache, + () => { + if (!monoAndroidResolutionAttempted) { + monoAndroidResolutionAttempted = true; + try { + monoAndroidAssembly = resolver.Resolve (AssemblyNameReference.Parse ("Mono.Android")); + } catch (AssemblyResolutionException ex) { + Log.LogErrorFromException (ex, showStackTrace: false); + } + } + return monoAndroidAssembly; + }, + (msg) => Log.LogDebugMessage (msg), + (msg) => Log.LogCodedWarning ("XA2000", msg))); + if (AddKeepAlives) { // Memoize the corlib resolution so the attempt (and any error logging) happens at most once, // regardless of how many assemblies/methods need KeepAlive injection. 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 1c995304248..3f5986c207c 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 @@ -7,8 +7,6 @@ using Java.Interop.Tools.Cecil; using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Linker; -using Mono.Linker.Steps; using Mono.Tuner; using MonoDroid.Tuner; using NUnit.Framework; @@ -27,30 +25,34 @@ void Logger (TraceLevel level, string message) => public void FixAbstractMethodsStep_SkipDimMembers () { var path = Path.Combine (Root, "temp", TestName); - var step = new FixAbstractMethodsStep (); Directory.CreateDirectory (path); - using (var resolver = new DirectoryAssemblyResolver (Logger, false)) - using (var context = new LinkContext (resolver)) { - step.Initialize (context, new EmptyMarkContext ()); + using (var resolver = new DirectoryAssemblyResolver (Logger, false)) { + var cache = new TypeDefinitionCache (); resolver.SearchDirectories.Add (path); var myAssemblyPath = Path.Combine (path, "MyAssembly.dll"); + var monoAndroidPath = Path.Combine (path, "Mono.Android.dll"); using (var android = CreateFauxMonoAndroidAssembly ()) { - android.Write (Path.Combine (path, "Mono.Android.dll")); + android.Write (monoAndroidPath); CreateAbstractIfaceImplementation (myAssemblyPath, android); } - using (var assm = context.Resolve ("MyAssembly")) { - step.FixAbstractMethods (assm); + var assm = resolver.GetAssembly (myAssemblyPath); + MethodDefinition? errorCtor = null; + FixAbstractMethodsHelper.FixAbstractMethods ( + assm, + cache, + ref errorCtor, + () => resolver.GetAssembly (monoAndroidPath), + (msg) => TestContext.WriteLine (msg)); - var impl = assm.MainModule.GetType ("MyNamespace.MyClass"); + var impl = assm.MainModule.GetType ("MyNamespace.MyClass"); - Assert.IsTrue (impl.Methods.Any (m => m.Name == "MyAbstractMethod"), "We should have generated an override for MyAbstractMethod"); - Assert.IsFalse (impl.Methods.Any (m => m.Name == "MyDefaultMethod"), "We should not have generated an override for MyDefaultMethod"); - } + Assert.IsTrue (impl.Methods.Any (m => m.Name == "MyAbstractMethod"), "We should have generated an override for MyAbstractMethod"); + Assert.IsFalse (impl.Methods.Any (m => m.Name == "MyDefaultMethod"), "We should not have generated an override for MyDefaultMethod"); } Directory.Delete (path, true); @@ -86,32 +88,36 @@ static void CreateAbstractIfaceImplementation (string assemblyPath, AssemblyDefi public void FixAbstractMethodsStep_Explicit () { var path = Path.Combine (Root, "temp", TestName); - var step = new FixAbstractMethodsStep (); Directory.CreateDirectory (path); - using (var resolver = new DirectoryAssemblyResolver (Logger, false)) - using (var context = new LinkContext (resolver)) { - step.Initialize (context, new EmptyMarkContext ()); + using (var resolver = new DirectoryAssemblyResolver (Logger, false)) { + var cache = new TypeDefinitionCache (); resolver.SearchDirectories.Add (path); var myAssemblyPath = Path.Combine (path, "MyAssembly.dll"); + var monoAndroidPath = Path.Combine (path, "Mono.Android.dll"); using (var android = CreateFauxMonoAndroidAssembly ()) { - android.Write (Path.Combine (path, "Mono.Android.dll")); + android.Write (monoAndroidPath); CreateExplicitInterface (myAssemblyPath, android); } - using (var assm = context.Resolve ("MyAssembly")) { - step.FixAbstractMethods (assm); - - 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"); - } + var assm = resolver.GetAssembly (myAssemblyPath); + MethodDefinition? errorCtor = null; + FixAbstractMethodsHelper.FixAbstractMethods ( + assm, + cache, + ref errorCtor, + () => resolver.GetAssembly (monoAndroidPath), + (msg) => TestContext.WriteLine (msg)); + + 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); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj index c9157be3712..bdc11cd5507 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj @@ -45,7 +45,7 @@ - + @@ -56,6 +56,8 @@ + +