diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index 17ffb5aee072..117091db0ae3 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -192,26 +192,15 @@ void Initialize () { foreach (AssemblyDefinition assembly in _context.GetAssemblies ()) InitializeAssembly (assembly); + + ProcessMarkedPending (); } protected virtual void InitializeAssembly (AssemblyDefinition assembly) { var action = _context.Annotations.GetAction (assembly); - switch (action) { - case AssemblyAction.Copy: - case AssemblyAction.Save: - Tracer.AddDirectDependency (assembly, new DependencyInfo (DependencyKind.AssemblyAction, action), marked: false); - MarkEntireAssembly (assembly); - break; - case AssemblyAction.Link: - case AssemblyAction.AddBypassNGen: - case AssemblyAction.AddBypassNGenUsed: - MarkAssembly (assembly); - - foreach (TypeDefinition type in assembly.MainModule.Types) - InitializeType (type); - break; - } + if (action == AssemblyAction.Copy || action == AssemblyAction.Save) + MarkAssembly (assembly, new DependencyInfo (DependencyKind.AssemblyAction, action)); } void Complete () @@ -234,27 +223,6 @@ static bool TypeIsDynamicInterfaceCastableImplementation (TypeDefinition type) return false; } - void InitializeType (TypeDefinition type) - { - if (type.HasNestedTypes) { - foreach (var nested in type.NestedTypes) - InitializeType (nested); - } - - if (!Annotations.IsMarked (type)) - return; - - // We may get here for a type marked by an earlier step, or by a type - // marked indirectly as the result of some other InitializeType call. - // Just track this as already marked, and don't include a new source. - MarkType (type, DependencyInfo.AlreadyMarked, type); - - if (type.HasFields) - InitializeFields (type); - if (type.HasMethods) - InitializeMethods (type.Methods); - } - protected bool IsFullyPreserved (TypeDefinition type) { if (Annotations.TryGetPreserve (type, out TypePreserve preserve) && preserve == TypePreserve.All) @@ -272,20 +240,6 @@ protected bool IsFullyPreserved (TypeDefinition type) return false; } - void InitializeFields (TypeDefinition type) - { - foreach (FieldDefinition field in type.Fields) - if (Annotations.IsMarked (field)) - MarkField (field, DependencyInfo.AlreadyMarked); - } - - void InitializeMethods (Collection methods) - { - foreach (MethodDefinition method in methods) - if (Annotations.IsMarked (method)) - EnqueueMethod (method, DependencyInfo.AlreadyMarked); - } - internal void MarkEntireType (TypeDefinition type, bool includeBaseTypes, in DependencyInfo reason, IMemberDefinition sourceLocationMember) { MarkEntireTypeInternal (type, includeBaseTypes, reason, sourceLocationMember, new HashSet ()); @@ -374,7 +328,8 @@ void Process () continue; if (!Annotations.IsMarked (type)) continue; - _context.MarkingHelpers.MarkExportedType (exported, assembly.MainModule, new DependencyInfo (DependencyKind.ExportedType, type)); + var di = new DependencyInfo (DependencyKind.ExportedType, type); + _context.MarkingHelpers.MarkExportedType (exported, assembly.MainModule, di); } } } @@ -399,6 +354,48 @@ bool ProcessPrimaryQueue () return true; } + bool ProcessMarkedPending () + { + bool marked = false; + foreach (var pending in Annotations.GetMarkedPending ()) { + marked = true; + + // Some pending items might be processed by the time we get to them. + if (Annotations.IsProcessed (pending)) + continue; + + switch (pending) { + case TypeDefinition type: + MarkType (type, DependencyInfo.AlreadyMarked, null); + break; + case MethodDefinition method: + MarkMethod (method, DependencyInfo.AlreadyMarked, null); + // Methods will not actually be processed until we drain the method queue. + break; + case FieldDefinition field: + MarkField (field, DependencyInfo.AlreadyMarked); + break; + case ModuleDefinition module: + MarkModule (module, DependencyInfo.AlreadyMarked); + break; + case ExportedType exportedType: + Annotations.SetProcessed (exportedType); + // No additional processing is done for exported types. + break; + default: + throw new NotImplementedException (pending.GetType ().ToString ()); + } + } + + foreach (var type in Annotations.GetPendingPreserve ()) { + marked = true; + Debug.Assert (Annotations.IsProcessed (type)); + ApplyPreserveInfo (type); + } + + return marked; + } + void ProcessPendingTypeChecks () { for (int i = 0; i < _pending_isinst_instr.Count; ++i) { @@ -1228,18 +1225,22 @@ void MarkCustomAttributeArgument (CustomAttributeArgument argument, ICustomAttri protected bool CheckProcessed (IMetadataTokenProvider provider) { - if (Annotations.IsProcessed (provider)) - return true; - - Annotations.Processed (provider); - return false; + return !Annotations.SetProcessed (provider); } - protected void MarkAssembly (AssemblyDefinition assembly) + + protected void MarkAssembly (AssemblyDefinition assembly, DependencyInfo reason) { + Annotations.Mark (assembly, reason); if (CheckProcessed (assembly)) return; + var action = _context.Annotations.GetAction (assembly); + if (action == AssemblyAction.Copy || action == AssemblyAction.Save) { + MarkEntireAssembly (assembly); + return; + } + ProcessModuleType (assembly); LazyMarkCustomAttributes (assembly); @@ -1252,6 +1253,8 @@ protected void MarkAssembly (AssemblyDefinition assembly) void MarkEntireAssembly (AssemblyDefinition assembly) { + Debug.Assert (Annotations.IsProcessed (assembly)); + MarkCustomAttributes (assembly, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly), null); MarkCustomAttributes (assembly.MainModule, new DependencyInfo (DependencyKind.AssemblyOrModuleAttribute, assembly.MainModule), null); @@ -1390,6 +1393,12 @@ void MarkField (FieldDefinition field, in DependencyInfo reason) throw new ArgumentOutOfRangeException ($"Internal error: unsupported field dependency {reason.Kind}"); #endif + if (reason.Kind == DependencyKind.AlreadyMarked) { + Debug.Assert (Annotations.IsMarked (field)); + } else { + Annotations.Mark (field, reason); + } + if (CheckProcessed (field)) return; @@ -1432,13 +1441,6 @@ void MarkField (FieldDefinition field, in DependencyInfo reason) Annotations.SetPreservedStaticCtor (parent); Annotations.SetSubstitutedInit (parent); } - - if (reason.Kind == DependencyKind.AlreadyMarked) { - Debug.Assert (Annotations.IsMarked (field)); - return; - } - - Annotations.Mark (field, reason); } protected virtual bool IgnoreScope (IMetadataScope scope) @@ -1447,9 +1449,16 @@ protected virtual bool IgnoreScope (IMetadataScope scope) return Annotations.GetAction (assembly) != AssemblyAction.Link; } - void MarkScope (IMetadataScope scope, TypeDefinition type) + void MarkModule (ModuleDefinition module, DependencyInfo reason) { - Annotations.Mark (scope, new DependencyInfo (DependencyKind.ScopeOfType, type)); + if (reason.Kind == DependencyKind.AlreadyMarked) { + Debug.Assert (Annotations.IsMarked (module)); + } else { + Annotations.Mark (module, reason); + } + if (CheckProcessed (module)) + return; + MarkAssembly (module.Assembly, new DependencyInfo (DependencyKind.AssemblyOfModule, module)); } protected virtual void MarkSerializable (TypeDefinition type) @@ -1532,7 +1541,7 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep // will call MarkType on the attribute type itself). // If for some reason we do keep the attribute type (could be because of previous reference which would cause IL2045 // or because of a copy assembly with a reference and so on) then we should not spam the warnings due to the type itself. - if (sourceLocationMember.DeclaringType != type) + if (sourceLocationMember != null && sourceLocationMember.DeclaringType != type) _context.LogWarning ( $"Attribute '{type.GetDisplayName ()}' is being referenced in code but the linker was " + $"instructed to remove all instances of this attribute. If the attribute instances are necessary make sure to " + @@ -1545,7 +1554,7 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep if (CheckProcessed (type)) return null; - MarkScope (type.Scope, type); + MarkModule (type.Scope as ModuleDefinition, new DependencyInfo (DependencyKind.ScopeOfType, type)); MarkType (type.BaseType, new DependencyInfo (DependencyKind.BaseType, type), type); MarkType (type.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, type), type); MarkCustomAttributes (type, new DependencyInfo (DependencyKind.CustomAttribute, type), type); @@ -1612,6 +1621,7 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep DoAdditionalTypeProcessing (type); ApplyPreserveInfo (type); + ApplyPreserveMethods (type); return type; } @@ -2277,9 +2287,10 @@ static IGenericParameterProvider GetGenericProviderFromInstance (IGenericInstanc void ApplyPreserveInfo (TypeDefinition type) { - ApplyPreserveMethods (type); - if (Annotations.TryGetPreserve (type, out TypePreserve preserve)) { + if (!Annotations.SetAppliedPreserve (type, preserve)) + throw new InternalErrorException ($"Type {type} already has applied {preserve}."); + var di = new DependencyInfo (DependencyKind.TypePreserve, type); switch (preserve) { @@ -2349,6 +2360,7 @@ void ApplyPreserveMethods (TypeDefinition type) if (list == null) return; + Annotations.ClearPreservedMethods (type); MarkMethodCollection (list, new DependencyInfo (DependencyKind.PreservedMethod, type), type); } @@ -2358,6 +2370,7 @@ void ApplyPreserveMethods (MethodDefinition method) if (list == null) return; + Annotations.ClearPreservedMethods (method); MarkMethodCollection (list, new DependencyInfo (DependencyKind.PreservedMethod, method), method); } @@ -2478,7 +2491,6 @@ protected virtual MethodDefinition MarkMethod (MethodReference reference, Depend AssemblyDefinition ResolveAssembly (IMetadataScope scope) { AssemblyDefinition assembly = _context.Resolve (scope); - MarkAssembly (assembly); return assembly; } @@ -2757,8 +2769,7 @@ void ProcessInteropMethod (MethodDefinition method) { if (method.IsPInvokeImpl) { var pii = method.PInvokeInfo; - Annotations.Mark (pii.Module, new DependencyInfo (DependencyKind.InteropMethodDependency, method)); - + Annotations.MarkProcessed (pii.Module, new DependencyInfo (DependencyKind.InteropMethodDependency, method)); if (!string.IsNullOrEmpty (_context.PInvokesListFile)) { _context.PInvokes.Add (new PInvokeInfo { AssemblyName = method.DeclaringType.Module.Name, @@ -3124,7 +3135,7 @@ protected virtual void MarkInterfaceImplementation (InterfaceImplementation ifac MarkCustomAttributes (iface, new DependencyInfo (DependencyKind.CustomAttribute, iface), type); // Blame the interface type on the interfaceimpl itself. MarkType (iface.InterfaceType, new DependencyInfo (DependencyKind.InterfaceImplementationInterfaceType, iface), type); - Annotations.Mark (iface, new DependencyInfo (DependencyKind.InterfaceImplementationOnType, type)); + Annotations.MarkProcessed (iface, new DependencyInfo (DependencyKind.InterfaceImplementationOnType, type)); } // diff --git a/src/linker/Linker.Steps/ResolveFromXmlStep.cs b/src/linker/Linker.Steps/ResolveFromXmlStep.cs index 4d66a35341db..176feb183194 100644 --- a/src/linker/Linker.Steps/ResolveFromXmlStep.cs +++ b/src/linker/Linker.Steps/ResolveFromXmlStep.cs @@ -167,12 +167,14 @@ protected override void ProcessMethod (TypeDefinition type, MethodDefinition met if (Annotations.IsMarked (method)) Context.LogWarning ($"Duplicate preserve of '{method.GetDisplayName ()}'", 2025, _xmlDocumentLocation); - Annotations.Mark (method, new DependencyInfo (DependencyKind.XmlDescriptor, _xmlDocumentLocation)); Annotations.MarkIndirectlyCalledMethod (method); Annotations.SetAction (method, MethodAction.Parse); - if (!(bool) customData) + if (!(bool) customData) { Annotations.AddPreservedMethod (type, method); + } else { + Annotations.Mark (method, new DependencyInfo (DependencyKind.XmlDescriptor, _xmlDocumentLocation)); + } } void ProcessMethodIfNotNull (TypeDefinition type, MethodDefinition method, object customData) @@ -222,8 +224,6 @@ protected override void ProcessEvent (TypeDefinition type, EventDefinition @even if (Annotations.IsMarked (@event)) Context.LogWarning ($"Duplicate preserve of '{@event.FullName}'", 2025, _xmlDocumentLocation); - Annotations.Mark (@event, new DependencyInfo (DependencyKind.XmlDescriptor, _xmlDocumentLocation)); - ProcessMethod (type, @event.AddMethod, null, customData); ProcessMethod (type, @event.RemoveMethod, null, customData); ProcessMethodIfNotNull (type, @event.InvokeMethod, customData); @@ -236,8 +236,6 @@ protected override void ProcessProperty (TypeDefinition type, PropertyDefinition if (Annotations.IsMarked (property)) Context.LogWarning ($"Duplicate preserve of '{property.FullName}'", 2025, _xmlDocumentLocation); - Annotations.Mark (property, new DependencyInfo (DependencyKind.XmlDescriptor, _xmlDocumentLocation)); - ProcessPropertyAccessors (type, property, accessors, customData); } diff --git a/src/linker/Linker.Steps/RootAssemblyInputStep.cs b/src/linker/Linker.Steps/RootAssemblyInputStep.cs index 30577931fed5..7279cf01a760 100644 --- a/src/linker/Linker.Steps/RootAssemblyInputStep.cs +++ b/src/linker/Linker.Steps/RootAssemblyInputStep.cs @@ -27,9 +27,8 @@ protected override void Process () AssemblyAction action = Context.Annotations.GetAction (assembly); switch (action) { case AssemblyAction.CopyUsed: - Annotations.Mark (assembly.MainModule, di); - goto case AssemblyAction.Copy; case AssemblyAction.Copy: + Annotations.Mark (assembly.MainModule, di); // Mark Step will take care of marking whole assembly return; case AssemblyAction.Link: diff --git a/src/linker/Linker/Annotations.cs b/src/linker/Linker/Annotations.cs index ebb241f1f1d9..fa87ebcdf1d7 100644 --- a/src/linker/Linker/Annotations.cs +++ b/src/linker/Linker/Annotations.cs @@ -49,9 +49,14 @@ public partial class AnnotationStore protected readonly Dictionary field_values = new Dictionary (); protected readonly HashSet field_init = new HashSet (); protected readonly HashSet fieldType_init = new HashSet (); - protected readonly HashSet marked = new HashSet (); + + // Annotations.Mark will add unmarked items to marked_pending, to be fully marked later ("processed") by MarkStep. + // Items go through state changes from "unmarked" -> "pending" -> "processed". "pending" items are only tracked + // once, and once "processed", an item never becomes "pending" again. + protected readonly HashSet marked_pending = new HashSet (); protected readonly HashSet processed = new HashSet (); - protected readonly Dictionary preserved_types = new Dictionary (); + protected readonly Dictionary preserved_types = new Dictionary (); + protected readonly HashSet pending_preserve = new HashSet (); protected readonly Dictionary preserved_type_members = new (); protected readonly Dictionary preserved_exportedtype_members = new (); protected readonly Dictionary> preserved_methods = new Dictionary> (); @@ -169,13 +174,15 @@ public bool HasSubstitutedInit (TypeDefinition type) [Obsolete ("Mark token providers with a reason instead.")] public void Mark (IMetadataTokenProvider provider) { - marked.Add (provider); + if (!processed.Contains (provider)) + marked_pending.Add (provider); } public void Mark (IMetadataTokenProvider provider, in DependencyInfo reason) { Debug.Assert (!(reason.Kind == DependencyKind.AlreadyMarked)); - marked.Add (provider); + if (!processed.Contains (provider)) + marked_pending.Add (provider); Tracer.AddDirectDependency (provider, reason, marked: true); } @@ -192,9 +199,19 @@ public void Mark (CustomAttribute attribute, in DependencyInfo reason) Tracer.AddDirectDependency (attribute, reason, marked: true); } + public IMetadataTokenProvider[] GetMarkedPending () + { + return marked_pending.ToArray (); + } + + public bool IsMarkedPending (IMetadataTokenProvider provider) + { + return marked_pending.Contains (provider); + } + public bool IsMarked (IMetadataTokenProvider provider) { - return marked.Contains (provider); + return processed.Contains (provider) || marked_pending.Contains (provider); } public bool IsMarked (CustomAttribute attribute) @@ -241,9 +258,15 @@ public bool IsRelevantToVariantCasting (TypeDefinition type) return types_relevant_to_variant_casting.Contains (type); } - public void Processed (IMetadataTokenProvider provider) + public bool SetProcessed (IMetadataTokenProvider provider) { - processed.Add (provider); + if (processed.Add (provider)) { + if (!marked_pending.Remove (provider)) + throw new InternalErrorException ($"{provider} must be marked before it can be processed."); + return true; + } + + return false; } public bool IsProcessed (IMetadataTokenProvider provider) @@ -251,12 +274,65 @@ public bool IsProcessed (IMetadataTokenProvider provider) return processed.Contains (provider); } + public bool MarkProcessed (IMetadataTokenProvider provider, in DependencyInfo reason) + { + Debug.Assert (!(reason.Kind == DependencyKind.AlreadyMarked)); + Tracer.AddDirectDependency (provider, reason, marked: true); + // The item may or may not be pending. + marked_pending.Remove (provider); + return processed.Add (provider); + } + + public TypeDefinition[] GetPendingPreserve () + { + return pending_preserve.ToArray (); + } + + public bool SetAppliedPreserve (TypeDefinition type, TypePreserve preserve) + { + if (!preserved_types.TryGetValue (type, out (TypePreserve preserve, bool applied) existing)) + throw new InternalErrorException ($"Type {type} must have a TypePreserve before it can be applied."); + + if (preserve != existing.preserve) + throw new InternalErrorException ($"Type {type} does not have {preserve}. The TypePreserve may have changed before the call to {nameof (SetAppliedPreserve)}."); + + if (existing.applied) { + Debug.Assert (!pending_preserve.Contains (type)); + return false; + } + + preserved_types[type] = (existing.preserve, true); + pending_preserve.Remove (type); + return true; + } + + public bool HasAppliedPreserve (TypeDefinition type, TypePreserve preserve) + { + if (!preserved_types.TryGetValue (type, out (TypePreserve preserve, bool applied) existing)) + throw new InternalErrorException ($"Type {type} must have a TypePreserve before it can be applied."); + + if (preserve != existing.preserve) + throw new InternalErrorException ($"Type {type} does not have {preserve}. The TypePreserve may have changed before the call to {nameof (HasAppliedPreserve)}."); + + return existing.applied; + } + public void SetPreserve (TypeDefinition type, TypePreserve preserve) { - if (preserved_types.TryGetValue (type, out TypePreserve existing)) - preserved_types[type] = ChoosePreserveActionWhichPreservesTheMost (existing, preserve); - else - preserved_types.Add (type, preserve); + Debug.Assert (preserve != TypePreserve.Nothing); + if (!preserved_types.TryGetValue (type, out (TypePreserve preserve, bool applied) existing)) { + preserved_types.Add (type, (preserve, false)); + return; + } + Debug.Assert (existing.preserve != TypePreserve.Nothing); + var newPreserve = ChoosePreserveActionWhichPreservesTheMost (existing.preserve, preserve); + if (newPreserve != existing.preserve) { + if (existing.applied) { + var addedPending = pending_preserve.Add (type); + Debug.Assert (addedPending); + } + preserved_types[type] = (newPreserve, false); + } } public static TypePreserve ChoosePreserveActionWhichPreservesTheMost (TypePreserve leftPreserveAction, TypePreserve rightPreserveAction) @@ -282,7 +358,13 @@ public static TypePreserve ChoosePreserveActionWhichPreservesTheMost (TypePreser public bool TryGetPreserve (TypeDefinition type, out TypePreserve preserve) { - return preserved_types.TryGetValue (type, out preserve); + if (preserved_types.TryGetValue (type, out (TypePreserve preserve, bool _applied) existing)) { + preserve = existing.preserve; + return true; + } + + preserve = default (TypePreserve); + return false; } public void SetMembersPreserve (TypeDefinition type, TypePreserveMembers preserve) @@ -307,11 +389,6 @@ static TypePreserveMembers CombineMembers (TypePreserveMembers left, TypePreserv return TypePreserveMembers.AllVisibleOrInternal; } - public bool TryGetPreservedMembers (TypeDefinition type, out TypePreserveMembers preserve) - { - return preserved_type_members.TryGetValue (type, out preserve); - } - public void SetMembersPreserve (ExportedType type, TypePreserveMembers preserve) { if (preserved_exportedtype_members.TryGetValue (type, out TypePreserveMembers existing)) @@ -320,6 +397,11 @@ public void SetMembersPreserve (ExportedType type, TypePreserveMembers preserve) preserved_exportedtype_members.Add (type, preserve); } + public bool TryGetPreservedMembers (TypeDefinition type, out TypePreserveMembers preserve) + { + return preserved_type_members.TryGetValue (type, out preserve); + } + public bool TryGetPreservedMembers (ExportedType type, out TypePreserveMembers preserve) { return preserved_exportedtype_members.TryGetValue (type, out preserve); @@ -381,6 +463,11 @@ public List GetPreservedMethods (TypeDefinition type) return GetPreservedMethods (type as IMemberDefinition); } + public bool ClearPreservedMethods (TypeDefinition type) + { + return preserved_methods.Remove (type); + } + public void AddPreservedMethod (TypeDefinition type, MethodDefinition method) { AddPreservedMethod (type as IMemberDefinition, method); @@ -391,6 +478,11 @@ public List GetPreservedMethods (MethodDefinition method) return GetPreservedMethods (method as IMemberDefinition); } + public bool ClearPreservedMethods (MethodDefinition key) + { + return preserved_methods.Remove (key); + } + public void AddPreservedMethod (MethodDefinition key, MethodDefinition method) { AddPreservedMethod (key as IMemberDefinition, method); @@ -406,6 +498,12 @@ List GetPreservedMethods (IMemberDefinition definition) void AddPreservedMethod (IMemberDefinition definition, MethodDefinition method) { + if (IsMarked (definition)) { + Mark (method, new DependencyInfo (DependencyKind.PreservedMethod, definition)); + Debug.Assert (GetPreservedMethods (definition) == null); + return; + } + var methods = GetPreservedMethods (definition); if (methods == null) { methods = new List (); diff --git a/src/linker/Linker/DependencyInfo.cs b/src/linker/Linker/DependencyInfo.cs index 758a424e1022..cfb0e51b755d 100644 --- a/src/linker/Linker/DependencyInfo.cs +++ b/src/linker/Linker/DependencyInfo.cs @@ -51,6 +51,7 @@ public enum DependencyKind // Modules and assemblies ScopeOfType = 28, // type -> module/assembly + AssemblyOfModule = 81, // module -> assembly TypeInAssembly = 29, // assembly -> type ModuleOfExportedType = 30, // exported type -> module ExportedType = 31, // type -> exported type diff --git a/src/linker/Linker/MarkingHelpers.cs b/src/linker/Linker/MarkingHelpers.cs index 0fd829722ed8..aa5d95b258e8 100644 --- a/src/linker/Linker/MarkingHelpers.cs +++ b/src/linker/Linker/MarkingHelpers.cs @@ -1,4 +1,4 @@ -using System; +using System; using Mono.Cecil; namespace Mono.Linker @@ -14,7 +14,8 @@ public MarkingHelpers (LinkContext context) public void MarkExportedType (ExportedType type, ModuleDefinition module, in DependencyInfo reason) { - _context.Annotations.Mark (type, reason); + if (!_context.Annotations.MarkProcessed (type, reason)) + return; if (_context.KeepTypeForwarderOnlyAssemblies) _context.Annotations.Mark (module, new DependencyInfo (DependencyKind.ModuleOfExportedType, type)); } diff --git a/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsAfterMark.cs b/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsAfterMark.cs new file mode 100644 index 000000000000..a9da1fbd0278 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsAfterMark.cs @@ -0,0 +1,39 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Extensibility +{ +#if !NETCOREAPP + [IgnoreTestCase ("Specific to the illink build")] +#endif + [SetupCompileBefore ("customstep.dll", new[] { "Dependencies/PreserveMethodsSubStep.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })] + [SetupLinkerArgument ("--custom-step", "+MarkStep:PreserveMethodsSubStep,customstep.dll")] + public class CustomStepCanPreserveMethodsAfterMark + { + public static void Main () + { + UsedType.UsedMethod (); + } + + [Kept] + static class UsedType + { + [Kept] + public static void UsedMethod () { } + + [Kept] + public static void PreservedForType () { } + + [Kept] + public static void PreservedForMethod_UsedMethod () { } + } + + // Annotations.Mark in a CustomStep before MarkStep will not necessarily keep a method, + // if it belongs to an unmarked type. + static class UnusedType + { + public static void MarkedMethod () { } + } + + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsBeforeMark.cs b/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsBeforeMark.cs new file mode 100644 index 000000000000..17ca281af1e9 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Extensibility/CustomStepCanPreserveMethodsBeforeMark.cs @@ -0,0 +1,41 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Extensibility +{ +#if !NETCOREAPP + [IgnoreTestCase ("Specific to the illink build")] +#endif + [SetupCompileBefore ("customstep.dll", new[] { "Dependencies/PreserveMethodsSubStep.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })] + [SetupLinkerArgument ("--custom-step", "-MarkStep:PreserveMethodsSubStep,customstep.dll")] + public class CustomStepCanPreserveMethodsBeforeMark + { + public static void Main () + { + UsedType.UsedMethod (); + } + + [Kept] + static class UsedType + { + [Kept] + public static void UsedMethod () { } + + [Kept] + public static void PreservedForType () { } + + [Kept] + public static void PreservedForMethod_UsedMethod () { } + } + + // Annotations.Mark in a CustomStep before MarkStep will keep a method, + // even if it belongs to an otherwise unmarked type. + [Kept] + static class UnusedType + { + [Kept] + public static void MarkedMethod () { } + } + + } +} \ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/PreserveMethodsSubStep.cs b/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/PreserveMethodsSubStep.cs new file mode 100644 index 000000000000..f67df3119724 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/PreserveMethodsSubStep.cs @@ -0,0 +1,42 @@ +using System; +using Mono.Cecil; +using Mono.Linker.Steps; + +class PreserveMethodsSubStep : BaseStep +{ + + protected override void Process () + { + foreach (var assembly in Context.GetAssemblies ()) { + foreach (var type in assembly.MainModule.Types) + ProcessType (type); + } + } + + void ProcessType (TypeDefinition type) + { + if (type.HasNestedTypes) { + foreach (var nested in type.NestedTypes) + ProcessType (nested); + } + + + foreach (var method in type.Methods) { + if (method.Name == "PreservedForType") + Annotations.AddPreservedMethod (type, method); + + ProcessMethod (method); + } + } + + public void ProcessMethod (MethodDefinition method) + { + if (method.Name == "MarkedMethod") + Annotations.Mark (method); + + foreach (var m in method.DeclaringType.Methods) { + if (m.Name == $"PreservedForMethod_{method.Name}") + Annotations.AddPreservedMethod (method, m); + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs b/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs index 6cafb467ad08..45fb3bdfc4a1 100644 --- a/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs +++ b/test/Mono.Linker.Tests.Cases/LinkAttributes/LinkerAttributeRemoval.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Cases.LinkAttributes.Dependencies;