diff --git a/dotnet/targets/Xamarin.Shared.Sdk.targets b/dotnet/targets/Xamarin.Shared.Sdk.targets index 9bf01ee471d6..3d51bcbfefc2 100644 --- a/dotnet/targets/Xamarin.Shared.Sdk.targets +++ b/dotnet/targets/Xamarin.Shared.Sdk.targets @@ -551,6 +551,8 @@ <_UseDynamicDependenciesForSmartEnumPreservation Condition="'$(_UseDynamicDependenciesForSmartEnumPreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) <_UseDynamicDependenciesForBlockCodePreservation Condition="'$(_UseDynamicDependenciesForBlockCodePreservation)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) <_UseDynamicDependenciesForGeneratedCodeOptimizations Condition="'$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) + <_UseLinkDescriptionForApplyPreserveAttribute Condition="'$(_UseLinkDescriptionForApplyPreserveAttribute)' == '' And '$(_XamarinRuntime)' == 'NativeAOT'">true + <_UseLinkDescriptionForApplyPreserveAttribute Condition="'$(_UseLinkDescriptionForApplyPreserveAttribute)' == ''">$(_UseDynamicDependenciesInsteadOfMarking) @@ -756,6 +758,7 @@ <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveSmartEnumConversionsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForSmartEnumPreservation)' == 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.PreserveBlockCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForBlockCodePreservation)' == 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.OptimizeGeneratedCodeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForGeneratedCodeOptimizations)' == 'true'" /> + <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" /> <_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" /> @@ -1084,6 +1087,7 @@ <_LinkerItemFiles Include="$(_LinkerItemsDirectory)/_AssembliesToAOT.items" /> <_LinkerItemFiles Include="$(_LinkerItemsDirectory)/_FrameworkToPublish.items" /> <_LinkerItemFiles Include="$(_LinkerItemsDirectory)/_DynamicLibraryToPublish.items" /> + <_LinkerItemFiles Include="$(_LinkerItemsDirectory)/TrimmerRootDescriptor.items" /> @@ -1108,7 +1112,17 @@ <_AssembliesToAOT Include="@(_AllLinkerItems)" Condition="'%(_AllLinkerItems.SourceFile)' == '_AssembliesToAOT.items'" /> <_FrameworkToPublish Include="@(_AllLinkerItems)" Condition="'%(_AllLinkerItems.SourceFile)' == '_FrameworkToPublish.items'" /> <_DynamicLibraryToPublish Include="@(_AllLinkerItems)" Condition="'%(_AllLinkerItems.SourceFile)' == '_DynamicLibraryToPublish.items'" /> + <_TrimmerRootDescriptorFromCustomLinkerSteps Include="@(_AllLinkerItems)" Condition="'%(_AllLinkerItems.SourceFile)' == 'TrimmerRootDescriptor.items'" /> + + + + + $(NoWarn);IL2008 + diff --git a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt index 6f58a1daf7cb..093edf407164 100644 --- a/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/MacCatalyst-NativeAOT-size.txt @@ -1,7 +1,7 @@ -AppBundleSize: 2,450,516 bytes (2,393.1 KB = 2.3 MB) +AppBundleSize: 2,599,110 bytes (2,538.2 KB = 2.5 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 2,358 bytes (2.3 KB = 0.0 MB) -Contents/Info.plist: 1,094 bytes (1.1 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 2,445,248 bytes (2,387.9 KB = 2.3 MB) +Contents/Info.plist: 1,128 bytes (1.1 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 2,593,808 bytes (2,533.0 KB = 2.5 MB) Contents/MonoBundle/runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) Contents/PkgInfo: 8 bytes (0.0 KB = 0.0 MB) diff --git a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt index 0c2f4851f900..ce539ee72a41 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 8,094,981 bytes (7,905.3 KB = 7.7 MB) +AppBundleSize: 8,440,007 bytes (8,242.2 KB = 8.0 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 3,489 bytes (3.4 KB = 0.0 MB) -Contents/Info.plist: 725 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 5,101,408 bytes (4,981.8 KB = 4.9 MB) +Contents/Info.plist: 759 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 5,446,400 bytes (5,318.8 KB = 5.2 MB) Contents/MonoBundle/libSystem.Globalization.Native.dylib: 252,176 bytes (246.3 KB = 0.2 MB) Contents/MonoBundle/libSystem.IO.Compression.Native.dylib: 2,005,440 bytes (1,958.4 KB = 1.9 MB) Contents/MonoBundle/libSystem.Native.dylib: 292,176 bytes (285.3 KB = 0.3 MB) diff --git a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt index d931959dfc15..a88b9788bf10 100644 --- a/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/TVOS-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 2,450,669 bytes (2,393.2 KB = 2.3 MB) +AppBundleSize: 2,616,495 bytes (2,555.2 KB = 2.5 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,112 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,146 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,444,768 bytes (2,387.5 KB = 2.3 MB) +SizeTestApp: 2,610,560 bytes (2,549.4 KB = 2.5 MB) diff --git a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt index 2b692c1f301e..238ecfc35830 100644 --- a/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-NativeAOT-size.txt @@ -1,8 +1,8 @@ -AppBundleSize: 2,437,189 bytes (2,380.1 KB = 2.3 MB) +AppBundleSize: 2,602,055 bytes (2,541.1 KB = 2.5 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 2,589 bytes (2.5 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) -Info.plist: 1,136 bytes (1.1 KB = 0.0 MB) +Info.plist: 1,170 bytes (1.1 KB = 0.0 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,808 bytes (1.8 KB = 0.0 MB) -SizeTestApp: 2,431,264 bytes (2,374.3 KB = 2.3 MB) +SizeTestApp: 2,596,096 bytes (2,535.2 KB = 2.5 MB) diff --git a/tools/common/DerivedLinkContext.cs b/tools/common/DerivedLinkContext.cs index 35a39f09f125..1cc8f9e0e187 100644 --- a/tools/common/DerivedLinkContext.cs +++ b/tools/common/DerivedLinkContext.cs @@ -48,6 +48,8 @@ public class DerivedLinkContext : LinkContext { // so we need a second dictionary Dictionary LinkedAwayTypeMap = new Dictionary (); + public bool DidRunApplyPreserveAttributeStep { get; set; } + public DerivedLinkContext (LinkerConfiguration configuration, Application app) #if !LEGACY_TOOLS : base (configuration) diff --git a/tools/dotnet-linker/AppBundleRewriter.cs b/tools/dotnet-linker/AppBundleRewriter.cs index e96fc999cfea..a3053029e351 100644 --- a/tools/dotnet-linker/AppBundleRewriter.cs +++ b/tools/dotnet-linker/AppBundleRewriter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; @@ -1384,7 +1385,7 @@ public bool AddAttributeToStaticConstructor (TypeDefinition onType, CustomAttrib return modified; } - MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool modified) + public MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool modified) { modified = false; @@ -1408,7 +1409,7 @@ MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type, out bool mod /// The provider to which the attribute should be added. /// The attribute to add. /// Whether the attribute was added or not. - bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute attribute) + public bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute attribute) { if (provider.HasCustomAttributes) { foreach (var ca in provider.CustomAttributes) { @@ -1460,7 +1461,18 @@ bool AddAttributeOnlyOnce (ICustomAttributeProvider provider, CustomAttribute at } } provider.CustomAttributes.Add (attribute); + if (DebugAttributes) + Console.WriteLine ($"Added {attribute.RenderAttribute ()} to {provider}"); return true; } + + static bool? debug_attributes; + static bool DebugAttributes { + get { + if (!debug_attributes.HasValue) + debug_attributes = !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("PRINT_ATTRIBUTES")); + return debug_attributes.Value; + } + } } } diff --git a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs index 4b67b0195d1a..1d4bc9520977 100644 --- a/tools/dotnet-linker/ApplyPreserveAttributeBase.cs +++ b/tools/dotnet-linker/ApplyPreserveAttributeBase.cs @@ -1,167 +1,229 @@ // This is copied from https://github.com/mono/linker/blob/fa9ccbdaf6907c69ef1bb117906f8f012218d57f/src/tuner/Mono.Tuner/ApplyPreserveAttributeBase.cs // and modified to work without a Profile class. -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; + using System.Linq; -using System.Text; using Mono.Linker; using Mono.Linker.Steps; using Mono.Cecil; -using Mono.Cecil.Cil; - -using Xamarin.Bundler; -using Xamarin.Linker; -using Xamarin.Utils; +using Mono.Tuner; #nullable enable -namespace Mono.Tuner { +namespace Xamarin.Linker.Steps { - public abstract class ApplyPreserveAttributeBase : ConfigurationAwareSubStep { - - AppBundleRewriter? abr; - Queue deferredActions = new (); + public partial class ApplyPreserveAttribute : ConfigurationAwareSubStep, IApplyPreserveAttribute { + ApplyPreserveAttributeImpl impl; protected override string Name { get => "Apply Preserve Attribute"; } protected override int ErrorCode { get => 2450; } - // set 'removeAttribute' to true if you want the preserved attribute to be removed from the final assembly - protected abstract bool IsPreservedAttribute (ICustomAttributeProvider provider, CustomAttribute attribute, out bool removeAttribute); - public override SubStepTargets Targets => SubStepTargets.Assembly; - public override void Initialize (LinkContext context) + public ApplyPreserveAttribute () + { + impl = new ApplyPreserveAttributeImpl (this); + } + + public override bool IsActiveFor (AssemblyDefinition assembly) { - base.Initialize (context); + // It's either this step, or ApplyPreserveAttributeStep. If ApplyPreserveAttributeStep already ran, then we shouldn't run this step. + if (Configuration.DerivedLinkContext.DidRunApplyPreserveAttributeStep) + return false; - if (Configuration.Application.XamarinRuntime == XamarinRuntime.NativeAOT) - abr = Configuration.AppBundleRewriter; + return Annotations.GetAction (assembly) == AssemblyAction.Link; } protected override void Process (AssemblyDefinition assembly) { - BrowseTypes (assembly.MainModule.Types); - ProcessDeferredActions (); + impl.Process (assembly); + } + + bool IApplyPreserveAttribute.PreserveUnconditional (IMetadataTokenProvider provider) + { + if (provider is MethodDefinition method) + Annotations.SetAction (method, MethodAction.Parse); + Annotations.Mark (provider); + return true; + } + + bool IApplyPreserveAttribute.PreserveType (TypeDefinition type, bool allMembers) + { + Annotations.Mark (type); + if (allMembers) + Annotations.SetPreserve (type, TypePreserve.All); + return true; + } + + bool IApplyPreserveAttribute.PreserveConditional (TypeDefinition onType, MethodDefinition forMethod) + { + Annotations.SetAction (forMethod, MethodAction.Parse); + Annotations.AddPreservedMethod (onType, forMethod); + return true; + } + } + + public interface IApplyPreserveAttribute { + bool PreserveType (TypeDefinition type, bool allMembers); + bool PreserveUnconditional (IMetadataTokenProvider provider); + bool PreserveConditional (TypeDefinition onType, MethodDefinition forMethod); + } + + public class ApplyPreserveAttributeImpl { + IApplyPreserveAttribute applyPreserveAttribute; + + public ApplyPreserveAttributeImpl (IApplyPreserveAttribute applyPreserveAttribute) + { + this.applyPreserveAttribute = applyPreserveAttribute; + } + + bool IsPreservedAttribute (ICustomAttributeProvider provider, CustomAttribute attribute) + { + TypeReference type = attribute.Constructor.DeclaringType; + return type.Name == "PreserveAttribute"; + } + + public bool Process (AssemblyDefinition assembly) + { + var modified = false; + modified |= BrowseTypes (assembly.MainModule.Types); + modified |= ProcessAssemblyAttributes (assembly); + return modified; } - void BrowseTypes (IEnumerable types) + bool ProcessAssemblyAttributes (AssemblyDefinition assembly) { - foreach (TypeDefinition type in types) { - ProcessType (type); + if (!assembly.HasCustomAttributes) + return false; + + var modified = false; + foreach (var attribute in assembly.CustomAttributes) { + if (!attribute.Constructor.DeclaringType.Is (Namespaces.Foundation, "PreserveAttribute")) + continue; + + if (!attribute.HasConstructorArguments) + continue; + var tr = (attribute.ConstructorArguments [0].Value as TypeReference); + if (tr is null) + continue; + + // we do not call `this.ProcessType` since + // (a) we're potentially processing a different assembly and `is_active` represent the current one + // (b) it will try to fetch the [Preserve] attribute on the type (and it's not there) as `base` would + var type = tr.Resolve (); + + modified |= PreserveType (type, attribute); + } + return modified; + } + + bool BrowseTypes (IEnumerable types) + { + var modified = false; + foreach (var type in (new List (types))) { + modified |= ProcessType (type); if (type.HasFields) { - foreach (FieldDefinition field in type.Fields) - ProcessField (field); + foreach (var field in type.Fields.ToArray ()) + modified |= ProcessField (field); } if (type.HasMethods) { - foreach (MethodDefinition method in type.Methods) - ProcessMethod (method); + foreach (var method in type.Methods.ToArray ()) + modified |= ProcessMethod (method); } if (type.HasProperties) { - foreach (PropertyDefinition property in type.Properties) - ProcessProperty (property); + foreach (var property in type.Properties.ToArray ()) + modified |= ProcessProperty (property); } if (type.HasEvents) { - foreach (EventDefinition @event in type.Events) - ProcessEvent (@event); + foreach (var @event in type.Events.ToArray ()) + modified |= ProcessEvent (@event); } if (type.HasNestedTypes) { - BrowseTypes (type.NestedTypes); + modified |= BrowseTypes (type.NestedTypes); } } + return modified; } - void ProcessDeferredActions () + bool ProcessType (TypeDefinition type) { - while (deferredActions.Count > 0) { - var action = deferredActions.Dequeue (); - action.Invoke (); - } - } - - public override bool IsActiveFor (AssemblyDefinition assembly) - { - return Annotations.GetAction (assembly) == AssemblyAction.Link; + return TryApplyPreserveAttribute (type); } - protected override void Process (TypeDefinition type) - { - TryApplyPreserveAttribute (type); - } - - protected override void Process (FieldDefinition field) + bool ProcessField (FieldDefinition field) { + var modified = false; foreach (var attribute in GetPreserveAttributes (field)) - Mark (field, attribute); + modified |= Mark (field, attribute); + return modified; } - protected override void Process (MethodDefinition method) + bool ProcessMethod (MethodDefinition method) { - MarkMethodIfPreserved (method); + return MarkMethodIfPreserved (method); } - protected override void Process (PropertyDefinition property) + bool ProcessProperty (PropertyDefinition property) { + var modified = false; foreach (var attribute in GetPreserveAttributes (property)) { - MarkMethod (property.GetMethod, attribute); - MarkMethod (property.SetMethod, attribute); + modified |= MarkMethod (property.GetMethod, attribute); + modified |= MarkMethod (property.SetMethod, attribute); } + return modified; } - protected override void Process (EventDefinition @event) + bool ProcessEvent (EventDefinition @event) { + var modified = false; foreach (var attribute in GetPreserveAttributes (@event)) { - MarkMethod (@event.AddMethod, attribute); - MarkMethod (@event.InvokeMethod, attribute); - MarkMethod (@event.RemoveMethod, attribute); + modified |= MarkMethod (@event.AddMethod, attribute); + modified |= MarkMethod (@event.InvokeMethod, attribute); + modified |= MarkMethod (@event.RemoveMethod, attribute); } + return modified; } - void MarkMethodIfPreserved (MethodDefinition method) + bool MarkMethodIfPreserved (MethodDefinition method) { + var modified = false; foreach (var attribute in GetPreserveAttributes (method)) - MarkMethod (method, attribute); + modified |= MarkMethod (method, attribute); + return modified; } - void MarkMethod (MethodDefinition? method, CustomAttribute? preserve_attribute) + bool MarkMethod (MethodDefinition? method, CustomAttribute? preserve_attribute) { if (method is null) - return; + return false; - Mark (method, preserve_attribute); - Annotations.SetAction (method, MethodAction.Parse); + return Mark (method, preserve_attribute); } - void Mark (IMetadataTokenProvider provider, CustomAttribute? preserve_attribute) + bool Mark (IMetadataTokenProvider provider, CustomAttribute? preserve_attribute) { - if (IsConditionalAttribute (preserve_attribute)) { - PreserveConditional (provider); - return; - } + if (IsConditionalAttribute (preserve_attribute)) + return PreserveConditional (provider); - PreserveUnconditional (provider); + return PreserveUnconditional (provider); } - void PreserveConditional (IMetadataTokenProvider provider) + bool PreserveConditional (IMetadataTokenProvider provider) { var method = provider as MethodDefinition; if (method is null) { // workaround to support (uncommon but valid) conditional fields form [Preserve] - PreserveUnconditional (provider); - return; + return PreserveUnconditional (provider); } - Annotations.AddPreservedMethod (method.DeclaringType, method); - AddConditionalDynamicDependencyAttribute (method.DeclaringType, method); + return applyPreserveAttribute.PreserveConditional (method.DeclaringType, method); } static bool IsConditionalAttribute (CustomAttribute? attribute) @@ -176,55 +238,39 @@ static bool IsConditionalAttribute (CustomAttribute? attribute) return false; } - void PreserveUnconditional (IMetadataTokenProvider provider) + bool PreserveUnconditional (IMetadataTokenProvider provider) { - Annotations.Mark (provider); + var modified = false; - // We want to add a dynamic dependency attribute to preserve methods and fields - // but not to preserve types while we're marking the chain of declaring types. - if (provider is not TypeDefinition) { - AddDynamicDependencyAttribute (provider); - } + modified |= applyPreserveAttribute.PreserveUnconditional (provider); var member = provider as IMemberDefinition; if (member is null || member.DeclaringType is null) - return; + return modified; - Mark (member.DeclaringType, null); + modified |= Mark (member.DeclaringType, null); + + return modified; } - void TryApplyPreserveAttribute (TypeDefinition type) + bool TryApplyPreserveAttribute (TypeDefinition type) { + var modified = false; foreach (var attribute in GetPreserveAttributes (type)) { - PreserveType (type, attribute); + modified |= PreserveType (type, attribute); } + return modified; } List GetPreserveAttributes (ICustomAttributeProvider provider) { - List attrs = new List (); - if (!provider.HasCustomAttributes) - return attrs; - - var attributes = provider.CustomAttributes; - - for (int i = attributes.Count - 1; i >= 0; i--) { - var attribute = attributes [i]; + return new List (); - bool remote_attribute; - if (!IsPreservedAttribute (provider, attribute, out remote_attribute)) - continue; - - attrs.Add (attribute); - if (remote_attribute) - attributes.RemoveAt (i); - } - - return attrs; + return provider.CustomAttributes.Where (a => IsPreservedAttribute (provider, a)).ToList (); } - protected void PreserveType (TypeDefinition type, CustomAttribute preserveAttribute) + protected bool PreserveType (TypeDefinition type, CustomAttribute preserveAttribute) { var allMembers = false; if (preserveAttribute.HasFields) { @@ -233,114 +279,12 @@ protected void PreserveType (TypeDefinition type, CustomAttribute preserveAttrib allMembers = true; } - PreserveType (type, allMembers); - } - - protected void PreserveType (TypeDefinition type, bool allMembers) - { - Annotations.Mark (type); - if (allMembers) - Annotations.SetPreserve (type, TypePreserve.All); - AddDynamicDependencyAttribute (type, allMembers); + return PreserveType (type, allMembers); } - MethodDefinition GetOrCreateModuleConstructor (ModuleDefinition @module) + bool PreserveType (TypeDefinition type, bool allMembers) { - var moduleType = @module.GetModuleType (); - return GetOrCreateStaticConstructor (moduleType); - } - - // We want to avoid `DynamicallyAccessedMemberTypes.All` because the semantics are different - // from `[Preserve (AllMembers = true)]`. Specifically, we don't want to preserve nested types. - // `All` would also keep unused private members of base types which `Preserve` also doesn't cover. - const DynamicallyAccessedMemberTypes allMemberTypes = - DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields - | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties - | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods - | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors - | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents - | DynamicallyAccessedMemberTypes.Interfaces; - - void AddDynamicDependencyAttribute (TypeDefinition type, bool allMembers) - { - if (abr is null) - return; - - abr.ClearCurrentAssembly (); - abr.SetCurrentAssembly (type.Module.Assembly); - - var moduleConstructor = GetOrCreateModuleConstructor (type.GetModule ()); - var members = allMembers - ? allMemberTypes - : DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; - - // only preserve fields for enums - if (type.IsEnum) { - members = DynamicallyAccessedMemberTypes.PublicFields; - } - - var attrib = abr.CreateDynamicDependencyAttribute (members, type); - moduleConstructor.CustomAttributes.Add (attrib); - - abr.ClearCurrentAssembly (); - } - - void AddConditionalDynamicDependencyAttribute (TypeDefinition onType, MethodDefinition forMethod) - { - if (abr is null) - return; - - deferredActions.Enqueue (() => AddDynamicDependencyAttributeToStaticConstructor (onType, forMethod)); - } - - void AddDynamicDependencyAttribute (IMetadataTokenProvider provider) - { - if (abr is null) - return; - - var member = provider as IMemberDefinition; - if (member is null) - throw ErrorHelper.CreateError (99, $"Unable to add dynamic dependency attribute to {provider.GetType ().FullName}"); - - var module = member.GetModule (); - abr.ClearCurrentAssembly (); - abr.SetCurrentAssembly (module.Assembly); - - var moduleConstructor = GetOrCreateModuleConstructor (module); - var signature = DocumentationComments.GetSignature (member); - var attrib = abr.CreateDynamicDependencyAttribute (signature, member.DeclaringType); - moduleConstructor.CustomAttributes.Add (attrib); - - abr.ClearCurrentAssembly (); - } - - void AddDynamicDependencyAttributeToStaticConstructor (TypeDefinition onType, MethodDefinition forMethod) - { - if (abr is null) - return; - - abr.ClearCurrentAssembly (); - abr.SetCurrentAssembly (onType.Module.Assembly); - - var cctor = GetOrCreateStaticConstructor (onType); - var signature = DocumentationComments.GetSignature (forMethod); - var attrib = abr.CreateDynamicDependencyAttribute (signature, onType); - cctor.CustomAttributes.Add (attrib); - Annotations.AddPreservedMethod (onType, cctor); - - abr.ClearCurrentAssembly (); - } - - MethodDefinition GetOrCreateStaticConstructor (TypeDefinition type) - { - var staticCtor = type.GetTypeConstructor (); - if (staticCtor is null) { - staticCtor = type.AddMethod (".cctor", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName | MethodAttributes.Static, abr!.System_Void); - staticCtor.CreateBody (out var il); - il.Emit (OpCodes.Ret); - } - - return staticCtor; + return applyPreserveAttribute.PreserveType (type, allMembers); } } } diff --git a/tools/dotnet-linker/ApplyPreserveAttributeStep.cs b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs new file mode 100644 index 000000000000..d25a335b4d0c --- /dev/null +++ b/tools/dotnet-linker/ApplyPreserveAttributeStep.cs @@ -0,0 +1,307 @@ +using System.IO; +using System.Linq; +using System.Xml.Linq; + +using Mono.Cecil; +using Mono.Linker; +using Mono.Linker.Steps; +using Xamarin.Bundler; +using Xamarin.Tuner; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.Linker.Steps { + + public class ApplyPreserveAttributeStep : AssemblyModifierStep, IApplyPreserveAttribute { + sealed class XmlTypeDescription { + public XmlTypeDescription (TypeDefinition type) + { + Type = type; + } + + public TypeDefinition Type { get; } + public bool PreserveAllMembers { get; set; } + public bool PreserveFields { get; set; } + public bool PreserveType { get; set; } + public Dictionary Fields { get; } = new (StringComparer.Ordinal); + public Dictionary Methods { get; } = new (StringComparer.Ordinal); + } + + ApplyPreserveAttributeImpl impl; + readonly Dictionary> xmlDescriptions = new (StringComparer.Ordinal); + protected override string Name { get => "Apply Preserve Attribute"; } + protected override int ErrorCode { get => 2450; } + + bool? create_xml_description_file; + public bool CreateXmlDescriptionFile { + get { + if (create_xml_description_file.HasValue) + return create_xml_description_file.Value; + return Configuration.Application.XamarinRuntime == XamarinRuntime.NativeAOT; + } + set { + create_xml_description_file = value; + } + } + + public bool UseXmlDescriptionFile { get; set; } = true; + public string XmlDescriptionPath { get; set; } = string.Empty; + + public ApplyPreserveAttributeStep () + { + impl = new ApplyPreserveAttributeImpl (this); + } + + protected override void TryProcess () + { + DerivedLinkContext.DidRunApplyPreserveAttributeStep = true; + base.TryProcess (); + } + + protected override bool IsActiveFor (AssemblyDefinition assembly) + { + // We only care about assemblies that are being linked. + if (Annotations.GetAction (assembly) != AssemblyAction.Link) + return false; + + return true; + } + + protected override bool ModifyAssembly (AssemblyDefinition assembly) + { + return impl.Process (assembly); + } + + protected override void TryEndProcess () + { + if (!UseXmlDescriptionFile) + return; + + WriteXmlDescription (); + } + + bool IApplyPreserveAttribute.PreserveUnconditional (IMetadataTokenProvider provider) + { + if (UseXmlDescriptionFile) { + AddUnconditionalXmlDescription (provider); + return false; + } + + // We want to add a dynamic dependency attribute to preserve methods and fields + // but not to preserve types while we're marking the chain of declaring types. + if (provider is not TypeDefinition) { + return AddDynamicDependencyAttribute (provider); + } + return false; + } + + bool IApplyPreserveAttribute.PreserveType (TypeDefinition type, bool allMembers) + { + if (UseXmlDescriptionFile) { + AddXmlDescription (type, allMembers); + return false; + } + + return AddDynamicDependencyAttribute (type, allMembers); + } + + MethodDefinition GetOrCreateModuleConstructor (ModuleDefinition @module, out bool modified) + { + var moduleType = @module.GetModuleType (); + return abr.GetOrCreateStaticConstructor (moduleType, out modified); + } + + bool IApplyPreserveAttribute.PreserveConditional (TypeDefinition onType, MethodDefinition forMethod) + { + if (UseXmlDescriptionFile) { + AddXmlDescription (onType, forMethod, conditional: true); + return false; + } + + return AddConditionalDynamicDependencyAttribute (onType, forMethod); + } + + // We want to avoid `DynamicallyAccessedMemberTypes.All` because the semantics are different + // from `[Preserve (AllMembers = true)]`. Specifically, we don't want to preserve nested types. + // `All` would also keep unused private members of base types which `Preserve` also doesn't cover. + const DynamicallyAccessedMemberTypes allMemberTypes = + DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields + | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties + | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods + | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors + | DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents + | DynamicallyAccessedMemberTypes.Interfaces; + + bool AddDynamicDependencyAttribute (TypeDefinition type, bool allMembers) + { + var moduleConstructor = GetOrCreateModuleConstructor (abr.CurrentAssembly.MainModule, out var modified); + var members = allMembers + ? allMemberTypes + : DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors; + + // only preserve fields for enums + if (type.IsEnum) { + members = DynamicallyAccessedMemberTypes.PublicFields; + } + + var attrib = abr.CreateDynamicDependencyAttribute (members, type); + modified |= abr.AddAttributeOnlyOnce (moduleConstructor, attrib); + return modified; + } + + bool AddConditionalDynamicDependencyAttribute (TypeDefinition onType, MethodDefinition forMethod) + { + return abr.AddDynamicDependencyAttributeToStaticConstructor (onType, forMethod); + } + + bool AddDynamicDependencyAttribute (IMetadataTokenProvider provider) + { + var member = provider as IMemberDefinition; + if (member is null) + throw ErrorHelper.CreateError (99, $"Unable to add dynamic dependency attribute to {provider.GetType ().FullName}"); + + var moduleConstructor = GetOrCreateModuleConstructor (member.GetModule (), out var modified); + var signature = DocumentationComments.GetSignature (member); + var attrib = abr.CreateDynamicDependencyAttribute (signature, member.DeclaringType); + modified |= abr.AddAttributeOnlyOnce (moduleConstructor, attrib); + return modified; + } + + string GetXmlDescriptionFilePath () + { + if (!string.IsNullOrEmpty (XmlDescriptionPath)) + return XmlDescriptionPath; + + return Path.Combine (Configuration.CacheDirectory, "apply-preserve-attribute.xml"); + } + + static string GetXmlSignature (MethodDefinition method) + { + var marker = method.DeclaringType.FullName + "::"; + var index = method.FullName.IndexOf (marker, System.StringComparison.Ordinal); + if (index < 0) + return method.FullName; + + return method.FullName.Substring (0, index) + method.FullName.Substring (index + marker.Length); + } + + XmlTypeDescription GetOrCreateXmlDescription (TypeDefinition type) + { + var assemblyName = type.Module.Assembly.Name.Name; + if (!xmlDescriptions.TryGetValue (assemblyName, out var types)) { + types = new Dictionary (System.StringComparer.Ordinal); + xmlDescriptions.Add (assemblyName, types); + } + + if (!types.TryGetValue (type.FullName, out var description)) { + description = new XmlTypeDescription (type); + types.Add (type.FullName, description); + } + + return description; + } + + void AddXmlDescription (TypeDefinition type, bool allMembers) + { + var description = GetOrCreateXmlDescription (type); + description.PreserveType = true; + if (allMembers) { + description.PreserveAllMembers = true; + return; + } + + if (type.IsEnum) { + description.PreserveFields = true; + return; + } + } + + void AddXmlDescription (TypeDefinition onType, MethodDefinition forMethod, bool conditional) + { + var description = GetOrCreateXmlDescription (onType); + if (!conditional) + description.PreserveType = true; + description.Methods [GetXmlSignature (forMethod)] = conditional; + } + + void AddUnconditionalXmlDescription (IMetadataTokenProvider provider) + { + switch (provider) { + case MethodDefinition method: + AddXmlDescription (method.DeclaringType, method, false); + break; + case FieldDefinition field: + var description = GetOrCreateXmlDescription (field.DeclaringType); + description.Fields [field.Name] = false; + description.PreserveType = true; + break; + } + } + + XElement CreateXmlTypeElement (XmlTypeDescription description) + { + var type = new XElement ("type", new XAttribute ("fullname", description.Type.FullName)); + + if (description.PreserveAllMembers) { + type.SetAttributeValue ("preserve", "all"); + return type; + } + + if (description.PreserveFields && description.Fields.Count == 0 && description.Methods.Count == 0) { + type.SetAttributeValue ("preserve", "fields"); + return type; + } + + if (!description.PreserveType) + type.SetAttributeValue ("required", "false"); + + type.SetAttributeValue ("preserve", "nothing"); + + foreach (var field in description.Fields.OrderBy (v => v.Key, System.StringComparer.Ordinal)) + type.Add (new XElement ("field", new XAttribute ("name", field.Key), new XAttribute ("required", field.Value ? "false" : "true"))); + + foreach (var method in description.Methods.OrderBy (v => v.Key, System.StringComparer.Ordinal)) + type.Add (new XElement ("method", new XAttribute ("signature", method.Key), new XAttribute ("required", method.Value ? "false" : "true"))); + + return type; + } + + void WriteXmlDescription () + { + var xmlPath = GetXmlDescriptionFilePath (); + var directory = Path.GetDirectoryName (xmlPath); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + var document = new XDocument ( + new XElement ("linker", + xmlDescriptions + .OrderBy (v => v.Key, System.StringComparer.Ordinal) + .Select (assembly => new XElement ("assembly", + new XAttribute ("fullname", assembly.Key), + assembly.Value + .OrderBy (v => v.Key, System.StringComparer.Ordinal) + .Select (v => CreateXmlTypeElement (v.Value)))))); + document.Save (xmlPath); + + if (CreateXmlDescriptionFile) { + var items = new List (); + var item = new MSBuildItem (xmlPath); + items.Add (item); + Configuration.WriteOutputForMSBuild ("TrimmerRootDescriptor", items); + } + + // The current linker run still needs these roots immediately. Writing the TrimmerRootDescriptor item only + // makes the descriptor available to MSBuild after this step has already finished running. + var applyXmlStepType = Context.GetType ().Assembly.GetType ("Mono.Linker.Steps.ResolveFromXmlStep"); + if (applyXmlStepType is not null) { + var documentStream = File.OpenRead (xmlPath); // ResolveFromXmlStep will dispose the stream. + var applyXmlStep = (BaseStep) Activator.CreateInstance (applyXmlStepType, new object [] { documentStream, xmlPath })!; + applyXmlStep.Process (Context); + } else { + throw ErrorHelper.CreateError (99, $"Unable to find Mono.Linker.Steps.ResolveFromXmlStep to apply the generated XML description file {xmlPath}"); + } + } + } +} diff --git a/tools/dotnet-linker/CecilExtensions.cs b/tools/dotnet-linker/CecilExtensions.cs index 810cc0db91a5..705b5bcda241 100644 --- a/tools/dotnet-linker/CecilExtensions.cs +++ b/tools/dotnet-linker/CecilExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text; using Mono.Cecil; using Mono.Cecil.Cil; @@ -161,5 +162,49 @@ public static TypeDefinition GetModuleType (this ModuleDefinition @module) return moduleType; } + public static string RenderAttribute (this CustomAttribute ca) + { + var render = new Func (v => { + if (v is string s) + return $"\"{s}\""; + else if (v is TypeReference tr) + return $"typeof ({tr.FullName})"; + else + return v?.ToString () ?? "null"; + }); + + var sb = new StringBuilder (); + sb.Append ("["); + sb.Append (ca.AttributeType.Name.EndsWith ("Attribute") ? ca.AttributeType.Name.Substring (0, ca.AttributeType.Name.Length - "Attribute".Length) : ca.AttributeType.Name); + if (ca.HasFields || ca.HasConstructorArguments || ca.HasProperties) { + sb.Append ("("); + var first = true; + foreach (var arg in ca.ConstructorArguments) { + if (!first) + sb.Append (", "); + first = false; + sb.Append (render (arg.Value)); + } + foreach (var prop in ca.Properties) { + if (!first) + sb.Append (", "); + first = false; + sb.Append (prop.Name); + sb.Append (" = "); + sb.Append (render (prop.Argument.Value)); + } + foreach (var field in ca.Fields) { + if (!first) + sb.Append (", "); + first = false; + sb.Append (field.Name); + sb.Append (" = "); + sb.Append (render (field.Argument.Value)); + } + sb.Append (")"); + } + sb.Append ("]"); + return sb.ToString (); + } } } diff --git a/tools/dotnet-linker/Steps/AssemblyModifierStep.cs b/tools/dotnet-linker/Steps/AssemblyModifierStep.cs index da3fcb5e5608..2e3e971d4d87 100644 --- a/tools/dotnet-linker/Steps/AssemblyModifierStep.cs +++ b/tools/dotnet-linker/Steps/AssemblyModifierStep.cs @@ -17,19 +17,25 @@ namespace Xamarin.Linker.Steps; public abstract class AssemblyModifierStep : ConfigurationAwareStep { private protected AppBundleRewriter abr => Configuration.AppBundleRewriter; - protected override void TryProcessAssembly (AssemblyDefinition assembly) + protected sealed override void TryProcessAssembly (AssemblyDefinition assembly) { var modified = false; abr.SetCurrentAssembly (assembly); - foreach (var type in assembly.MainModule.Types) - modified |= ProcessTypeImpl (type); - + modified |= ModifyAssembly (assembly); if (modified) abr.SaveCurrentAssembly (); abr.ClearCurrentAssembly (); } + protected virtual bool ModifyAssembly (AssemblyDefinition assembly) + { + var modified = false; + foreach (var type in assembly.MainModule.Types) + modified |= ProcessTypeImpl (type); + return modified; + } + protected virtual bool ProcessType (TypeDefinition type) { return false; diff --git a/tools/dotnet-linker/Steps/RemoveAttributesStep.cs b/tools/dotnet-linker/Steps/RemoveAttributesStep.cs index af9acee94086..7fa016caab70 100644 --- a/tools/dotnet-linker/Steps/RemoveAttributesStep.cs +++ b/tools/dotnet-linker/Steps/RemoveAttributesStep.cs @@ -35,6 +35,9 @@ bool IsRemovedAttribute (CustomAttribute attribute) { // this avoids calling FullName (which allocates a string) var attr_type = attribute.Constructor.DeclaringType; + if (attr_type.Name == "PreserveAttribute") + return true; + switch (attr_type.Namespace) { case Namespaces.ObjCRuntime: switch (attr_type.Name) { diff --git a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs index 1ea1dcb0943b..22f70f051ef0 100644 --- a/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs +++ b/tools/dotnet-linker/Steps/SetBeforeFieldInitStep.cs @@ -45,12 +45,6 @@ protected override void Process (TypeDefinition type) if (Configuration.DerivedLinkContext.App.Optimizations.RegisterProtocols != true) return; - if (Configuration.DerivedLinkContext.App.XamarinRuntime == Bundler.XamarinRuntime.NativeAOT) { - // We can't remove the static constructor in the trimmer if we're using NativeAOT, - // because NativeAOT needs it for its own trimming logic. - return; - } - if (!type.IsBeforeFieldInit && type.IsInterface && type.HasMethods) { var cctor = type.GetTypeConstructor (); if (cctor is not null && cctor.IsBindingImplOptimizableCode (LinkContext)) diff --git a/tools/dotnet-linker/dotnet-linker.csproj b/tools/dotnet-linker/dotnet-linker.csproj index 1cd515ca90b8..5b8b4c66d39e 100644 --- a/tools/dotnet-linker/dotnet-linker.csproj +++ b/tools/dotnet-linker/dotnet-linker.csproj @@ -163,9 +163,6 @@ external/src/ObjCRuntime/NativeNameAttribute.cs - - external/tools/linker/ApplyPreserveAttribute.cs - external/tools/linker/ExceptionalSubStep.cs diff --git a/tools/linker/ApplyPreserveAttribute.cs b/tools/linker/ApplyPreserveAttribute.cs deleted file mode 100644 index 9eea0bb1c5ca..000000000000 --- a/tools/linker/ApplyPreserveAttribute.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2011-2013 Xamarin Inc. All rights reserved. - -using System; -using System.Collections.Generic; - -using Mono.Cecil; -using Mono.Linker; -using Mono.Tuner; - -using Xamarin.Tuner; - -namespace Xamarin.Linker.Steps { - - public class ApplyPreserveAttribute : ApplyPreserveAttributeBase { - // We need to run the ApplyPreserveAttribute step even if we're only linking sdk assemblies, because even - // though we know that sdk assemblies will never have Preserve attributes, user assemblies may have - // [assembly: LinkSafe] attributes, which means we treat them as sdk assemblies and those may have - // Preserve attributes. - public override bool IsActiveFor (AssemblyDefinition assembly) - { - return Annotations.GetAction (assembly) == AssemblyAction.Link; - } - - protected override void Process (AssemblyDefinition assembly) - { - base.Process (assembly); - ProcessAssemblyAttributes (assembly); - } - - void ProcessAssemblyAttributes (AssemblyDefinition assembly) - { - if (!assembly.HasCustomAttributes) - return; - - foreach (var attribute in assembly.CustomAttributes) { - if (!attribute.Constructor.DeclaringType.Is (Namespaces.Foundation, "PreserveAttribute")) - continue; - - if (!attribute.HasConstructorArguments) - continue; - var tr = (attribute.ConstructorArguments [0].Value as TypeReference); - if (tr is null) - continue; - - // we do not call `this.ProcessType` since - // (a) we're potentially processing a different assembly and `is_active` represent the current one - // (b) it will try to fetch the [Preserve] attribute on the type (and it's not there) as `base` would - var type = tr.Resolve (); - - PreserveType (type, attribute); - } - } - - protected override bool IsPreservedAttribute (ICustomAttributeProvider provider, CustomAttribute attribute, out bool removeAttribute) - { - removeAttribute = false; - TypeReference type = attribute.Constructor.DeclaringType; - - if (type.Name == "PreserveAttribute") { - // there's no need to keep the [Preserve] attribute in the assembly once it was processed - removeAttribute = true; - return true; - } - return false; - } - } -}