diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs index 51e25383a0d..04d5c2357e7 100644 --- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs +++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/RemoveResourceDesignerStep.cs @@ -1,323 +1,129 @@ #nullable disable +using Mono.Cecil; +using Mono.Linker; +using Mono.Linker.Steps; using System; +using System.Linq; +using Xamarin.Android.Tasks; using System.Collections.Generic; -using System.Globalization; -using System.Text.RegularExpressions; -using Mono.Cecil; using Mono.Cecil.Cil; -using Mono.Collections.Generic; -using Xamarin.Android.Tasks; - -namespace MonoDroid.Tuner; +using System.Text.RegularExpressions; -/// -/// Inlines resource designer constants and removes designer classes. -/// The core logic is adapted from the original ILLink RemoveResourceDesignerStep / LinkDesignerBase. -/// -class RemoveResourceDesignerStep : IAssemblyModifierPipelineStep +namespace MonoDroid.Tuner { - readonly IList allAssemblies; - readonly Action logMessage; - readonly Regex opCodeRegex = new Regex (@"([\w]+): ([\w]+) ([\w.]+) ([\w:./]+)"); - - TypeDefinition mainDesigner = null; - AssemblyDefinition mainAssembly = null; - CustomAttribute mainDesignerAttribute; - Dictionary designerConstants; - bool designerLoaded; - - public RemoveResourceDesignerStep (IList allAssemblies, Action logMessage) + public class RemoveResourceDesignerStep : LinkDesignerBase, IAssemblyModifierPipelineStep { - this.allAssemblies = allAssemblies; - this.logMessage = logMessage; - } + TypeDefinition mainDesigner = null; + AssemblyDefinition mainAssembly = null; + CustomAttribute mainDesignerAttribute; + Dictionary designerConstants; + Regex opCodeRegex = new Regex (@"([\w]+): ([\w]+) ([\w.]+) ([\w:./]+)"); - public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) - { - LoadDesigner (); - context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly); - } + IList allAssemblies; + Action log; - void LoadDesigner () - { - if (designerLoaded) - return; - designerLoaded = true; - - foreach (var asm in allAssemblies) { - if (FindResourceDesigner (asm, mainApplication: true, designer: out mainDesigner, designerAttribute: out mainDesignerAttribute)) { - mainAssembly = asm; - break; - } - } - if (mainDesigner == null) { - logMessage (" Main Designer not found."); - return; + public RemoveResourceDesignerStep (IList allAssemblies, Action logger) + { + this.allAssemblies = allAssemblies; + this.log = logger; } - logMessage ($" Main Designer found {mainDesigner.FullName}."); - designerConstants = BuildResourceDesignerFieldLookup (mainDesigner); - } - - bool ProcessAssemblyDesigner (AssemblyDefinition assembly) - { - if (mainDesigner == null) - return false; - if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) - return false; - logMessage ($" Fixing up {assembly.Name.Name}"); - TypeDefinition localDesigner = null; - CustomAttribute designerAttribute; - if (assembly != mainAssembly) { - logMessage ($" {assembly.Name.Name} is not the main assembly. "); - if (!FindResourceDesigner (assembly, mainApplication: false, designer: out localDesigner, designerAttribute: out designerAttribute)) { - logMessage ($" {assembly.Name.Name} does not have a designer file."); - return false; - } - } else { - logMessage ($" {assembly.Name.Name} is the main assembly. "); - localDesigner = mainDesigner; - designerAttribute = mainDesignerAttribute; + public override void LogMessage (string message) + { + log (message); } - logMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}."); - - FixupAssemblyTypes (assembly, localDesigner); - - ClearDesignerClass (localDesigner); - if (designerAttribute != null) { - assembly.CustomAttributes.Remove (designerAttribute); + public void ProcessAssembly (AssemblyDefinition assembly, StepContext context) + { + LoadDesigner (); + context.IsAssemblyModified |= ProcessAssemblyDesigner (assembly); } - return true; - } - - // ---- Methods below are from LinkDesignerBase, adapted for non-ILLink use ---- - bool FindResourceDesigner (AssemblyDefinition assembly, bool mainApplication, out TypeDefinition designer, out CustomAttribute designerAttribute) - { - string designerFullName = null; - designer = null; - designerAttribute = null; - foreach (CustomAttribute attribute in assembly.CustomAttributes) + protected override void LoadDesigner () { - if (attribute.AttributeType.FullName == "Android.Runtime.ResourceDesignerAttribute") - { - designerAttribute = attribute; - if (attribute.HasProperties) - { - foreach (var p in attribute.Properties) - { - if (p.Name == "IsApplication" && (bool)p.Argument.Value == (mainApplication ? mainApplication : (bool)p.Argument.Value)) - { - designerFullName = attribute.ConstructorArguments[0].Value.ToString (); - break; - } - } + if (mainAssembly != null) + return; + // resolve the MainAssembly Resource designer TypeDefinition + foreach(var asm in allAssemblies) { + if (FindResourceDesigner (asm, mainApplication: true, designer: out mainDesigner, designerAttribute: out mainDesignerAttribute)) { + mainAssembly = asm; + break; } - break; - } - } - - if (string.IsNullOrEmpty(designerFullName)) { - logMessage ($"Inspecting member references for assembly: {assembly.FullName};"); - var memberRefs = assembly.MainModule.GetMemberReferences (); - foreach (var memberRef in memberRefs) { - string declaringType = memberRef.DeclaringType?.ToString () ?? string.Empty; - if (!declaringType.Contains (".Resource/")) { - continue; - } - if (declaringType.Contains ("_Microsoft.Android.Resource.Designer")) { - continue; - } - var resolved = false; - try { - var def = memberRef.Resolve (); - if (resolved = def != null) { - logMessage ($"Resolved member `{memberRef?.Name}`"); - } - } catch (Exception ex) { - logMessage ($"Exception resolving member `{memberRef?.Name}`: {ex}"); - resolved = false; - } - if (!resolved) { - logMessage ($"Adding _Linker.Generated.Resource to {assembly.Name.Name}. Could not resolve {memberRef?.Name} : {declaringType}"); - designer = new TypeDefinition ("_Linker.Generated", "Resource", TypeAttributes.Public | TypeAttributes.AnsiClass); - designer.BaseType = new TypeDefinition ("System", "Object", TypeAttributes.Public | TypeAttributes.AnsiClass); - return true; - } + if (mainDesigner == null) { + LogMessage ($" Main Designer not found."); + return; } + LogMessage ($" Main Designer found {mainDesigner.FullName}."); + designerConstants = BuildResourceDesignerFieldLookup (mainDesigner); } - if (string.IsNullOrEmpty(designerFullName)) - return false; - - foreach (ModuleDefinition module in assembly.Modules) + protected override void FixBody (MethodBody body, TypeDefinition designer) { - foreach (TypeDefinition type in module.Types) + Dictionary instructions = new Dictionary(); + var processor = body.GetILProcessor (); + string designerFullName = $"{designer.FullName}/"; + bool isDesignerMethod = designerFullName.Contains (body.Method.DeclaringType.FullName); + string declaringTypeName = body.Method.DeclaringType.Name; + foreach (var i in body.Instructions) { - if (type.FullName == designerFullName) + string line = i.ToString (); + if ((line.Contains (designerFullName) || (isDesignerMethod && i.OpCode == OpCodes.Stsfld)) && !instructions.ContainsKey (i)) { - designer = type; - return true; - } - } - } - return false; - } - - void FixBody (MethodBody body, TypeDefinition designer) - { - Dictionary instructions = new Dictionary(); - var processor = body.GetILProcessor (); - string designerFullName = $"{designer.FullName}/"; - bool isDesignerMethod = designerFullName.Contains (body.Method.DeclaringType.FullName); - string declaringTypeName = body.Method.DeclaringType.Name; - foreach (var i in body.Instructions) - { - string line = i.ToString (); - if ((line.Contains (designerFullName) || (isDesignerMethod && i.OpCode == OpCodes.Stsfld)) && !instructions.ContainsKey (i)) - { - var match = opCodeRegex.Match (line); - if (match.Success && match.Groups.Count == 5) { - string key = match.Groups[4].Value.Replace (designerFullName, string.Empty); - if (isDesignerMethod) { - key = declaringTypeName +"::" + key; + var match = opCodeRegex.Match (line); + if (match.Success && match.Groups.Count == 5) { + string key = match.Groups[4].Value.Replace (designerFullName, string.Empty); + if (isDesignerMethod) { + key = declaringTypeName +"::" + key; + } + if (designerConstants.ContainsKey (key) && !instructions.ContainsKey (i)) + instructions.Add(i, designerConstants [key]); } - if (designerConstants.ContainsKey (key) && !instructions.ContainsKey (i)) - instructions.Add(i, designerConstants [key]); - } - } - } - if (instructions.Count > 0) - logMessage ($" Fixing up {body.Method.FullName}"); - foreach (var i in instructions) - { - var newCode = Extensions.CreateLoadArraySizeOrOffsetInstruction (i.Value); - logMessage ($" Replacing {i.Key}"); - logMessage ($" With {newCode}"); - processor.Replace(i.Key, newCode); - } - } - - Dictionary BuildResourceDesignerFieldLookup (TypeDefinition type) - { - var output = new Dictionary (); - foreach (TypeDefinition definition in type.NestedTypes) - { - foreach (FieldDefinition field in definition.Fields) - { - string key = $"{definition.Name}::{field.Name}"; - if (!output.ContainsKey (key)) - output.Add(key, int.Parse (field.Constant?.ToString () ?? "0", CultureInfo.InvariantCulture)); - } - } - return output; - } - - void ClearDesignerClass (TypeDefinition designer, bool completely = false) - { - logMessage ($" TryRemoving {designer.FullName}"); - // for each of the nested types clear all but the - // int[] fields. - if (!completely) { - for (int i = designer.NestedTypes.Count -1; i >= 0; i--) { - var nestedType = designer.NestedTypes [i]; - RemoveFieldsFromType (nestedType, designer.Module); - if (nestedType.Fields.Count == 0) { - // no fields we do not need this class at all. - designer.NestedTypes.RemoveAt (i); } } - RemoveUpdateIdValues (designer); - } else { - designer.NestedTypes.Clear (); - } - designer.Fields.Clear (); - designer.Properties.Clear (); - designer.CustomAttributes.Clear (); - designer.Interfaces.Clear (); - designer.Events.Clear (); - } - - void FixType (TypeDefinition type, TypeDefinition localDesigner) - { - foreach (MethodDefinition method in type.Methods) - { - if (!method.HasBody) - continue; - FixBody (method.Body, localDesigner); - } - foreach (PropertyDefinition property in type.Properties) - { - if (property.GetMethod != null && property.GetMethod.HasBody) - { - FixBody (property.GetMethod.Body, localDesigner); - } - if (property.SetMethod != null && property.SetMethod.HasBody) + if (instructions.Count > 0) + LogMessage ($" Fixing up {body.Method.FullName}"); + foreach (var i in instructions) { - FixBody (property.SetMethod.Body, localDesigner); + var newCode = Extensions.CreateLoadArraySizeOrOffsetInstruction (i.Value); + LogMessage ($" Replacing {i.Key}"); + LogMessage ($" With {newCode}"); + processor.Replace(i.Key, newCode); } } - foreach (TypeDefinition nestedType in type.NestedTypes) - { - FixType (nestedType, localDesigner); - } - } - void FixupAssemblyTypes (AssemblyDefinition assembly, TypeDefinition designer) - { - foreach (ModuleDefinition module in assembly.Modules) + internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly) { - foreach (TypeDefinition type in module.Types) - { - if (type.FullName == designer.FullName) - continue; - FixType (type, designer); - } - } - } - - void RemoveFieldsFromType (TypeDefinition type, ModuleDefinition module) - { - for (int i = type.Fields.Count - 1; i >= 0; i--) { - var field = type.Fields [i]; - if (field.FieldType.IsArray) { - continue; - } - logMessage ($"Removing {type.Name}::{field.Name}"); - type.Fields.RemoveAt (i); - } - } + if (mainDesigner == null) + return false; + if (MonoAndroidHelper.IsFrameworkAssembly (assembly)) + return false; - void RemoveUpdateIdValues (TypeDefinition type) - { - foreach (var method in type.Methods) { - if (method.Name.Contains ("UpdateIdValues")) { - FixUpdateIdValuesBody (method); + LogMessage ($" Fixing up {assembly.Name.Name}"); + TypeDefinition localDesigner = null; + CustomAttribute designerAttribute; + if (assembly != mainAssembly) { + LogMessage ($" {assembly.Name.Name} is not the main assembly. "); + if (!FindResourceDesigner (assembly, mainApplication: false, designer: out localDesigner, designerAttribute: out designerAttribute)) { + LogMessage ($" {assembly.Name.Name} does not have a designer file."); + return false; + } } else { - FixBody (method.Body, type); + LogMessage ($" {assembly.Name.Name} is the main assembly. "); + localDesigner = mainDesigner; + designerAttribute = mainDesignerAttribute; } - } - foreach (var nestedType in type.NestedTypes) { - RemoveUpdateIdValues (nestedType); - } - } + LogMessage ($" {assembly.Name.Name} has designer {localDesigner.FullName}."); - void FixUpdateIdValuesBody (MethodDefinition method) - { - List finalInstructions = new List (); - Collection instructions = method.Body.Instructions; - for (int i = 0; i < method.Body.Instructions.Count-1; i++) { - Instruction instruction = instructions[i]; - string line = instruction.ToString (); - bool found = line.Contains ("Int32[]") || instruction.OpCode == OpCodes.Ret; - if (!found) { - method.Body.Instructions.Remove (instruction); - i--; + FixupAssemblyTypes (assembly, localDesigner); + + ClearDesignerClass (localDesigner); + if (designerAttribute != null) { + assembly.CustomAttributes.Remove (designerAttribute); } + return true; } } } diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs index 820a050a47b..fa3c3385737 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/PostTrimmingPipeline.cs @@ -48,9 +48,23 @@ public override bool RunTask () } } + // Pre-load all assemblies once so that every step (and the processing loop) + // operates on the same AssemblyDefinition instances. + var loadedAssemblies = new List<(ITaskItem item, AssemblyDefinition assembly)> (Assemblies.Length); + foreach (var item in Assemblies) { + loadedAssemblies.Add ((item, resolver.GetAssembly (item.ItemSpec))); + } + var steps = new List (); steps.Add (new CheckForObsoletePreserveAttributeStep (Log)); steps.Add (new StripEmbeddedLibrariesStep (Log)); + if (AndroidLinkResources) { + var allAssemblies = new List (loadedAssemblies.Count); + foreach (var (_, assembly) in loadedAssemblies) { + allAssemblies.Add (assembly); + } + steps.Add (new RemoveResourceDesignerStep (allAssemblies, (msg) => Log.LogDebugMessage (msg))); + } // FixAbstractMethods — resolve Mono.Android once up front. If resolution fails, log // the error and skip running the fix step entirely to avoid later unhandled exceptions. @@ -86,16 +100,8 @@ public override bool RunTask () }, (msg) => Log.LogDebugMessage (msg))); } - if (AndroidLinkResources) { - var allAssemblies = new List (Assemblies.Length); - foreach (var item in Assemblies) { - allAssemblies.Add (resolver.GetAssembly (item.ItemSpec)); - } - steps.Add (new RemoveResourceDesignerStep (allAssemblies, (msg) => Log.LogDebugMessage (msg))); - } - foreach (var item in Assemblies) { - var assembly = resolver.GetAssembly (item.ItemSpec); + foreach (var (item, assembly) in loadedAssemblies) { var context = new StepContext (item, item); foreach (var step in steps) { step.ProcessAssembly (assembly, context);