diff --git a/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs b/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs new file mode 100644 index 0000000000000..4a7e1121d57fb --- /dev/null +++ b/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Mono.Linker.Steps +{ + + /// + /// This API supports the product infrastructure and is not intended to be used directly from your code. + /// Extensibility point for custom logic that run during MarkStep, for marked members. + /// + public interface IMarkHandler + { + /// + /// Initialize is called at the beginning of MarkStep. This should be + /// used to perform global setup, and register callbacks through the + /// MarkContext.Register* methods) to be called when pieces of IL are marked. + /// + void Initialize (LinkContext context, MarkContext markContext); + } +} diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs b/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs new file mode 100644 index 0000000000000..158379f93d344 --- /dev/null +++ b/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + /// + /// Context which can be used to register actions to call during MarkStep + /// when various members are marked. + /// + public abstract class MarkContext + { + /// + /// Register a callback that will be invoked once for each marked assembly. + /// + public abstract void RegisterMarkAssemblyAction (Action action); + + /// + /// Register a callback that will be invoked once for each marked type. + /// + public abstract void RegisterMarkTypeAction (Action action); + + /// + /// Register a callback that will be invoked once for each marked method. + /// + public abstract void RegisterMarkMethodAction (Action action); + } +} \ No newline at end of file diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 95bdef9320772..fce48529eb001 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -56,6 +56,7 @@ public partial class MarkStep : IStep readonly List<(TypeDefinition Type, MethodBody Body, Instruction Instr)> _pending_isinst_instr; UnreachableBlocksOptimizer _unreachableBlocksOptimizer; + MarkStepContext _markContext; #if DEBUG static readonly DependencyKind[] _entireTypeReasons = new DependencyKind[] { @@ -190,6 +191,7 @@ public virtual void Process (LinkContext context) { _context = context; _unreachableBlocksOptimizer = new UnreachableBlocksOptimizer (_context); + _markContext = new MarkStepContext (); Initialize (); Process (); @@ -199,6 +201,7 @@ public virtual void Process (LinkContext context) void Initialize () { InitializeCorelibAttributeXml (); + _context.Pipeline.InitializeMarkHandlers (_context, _markContext); ProcessMarkedPending (); } @@ -1296,6 +1299,9 @@ protected void MarkAssembly (AssemblyDefinition assembly, DependencyInfo reason) EmbeddedXmlInfo.ProcessDescriptors (assembly, _context); + foreach (Action handleMarkAssembly in _markContext.MarkAssemblyActions) + handleMarkAssembly (assembly); + // Security attributes do not respect the attributes XML if (_context.StripSecurity) RemoveSecurity.ProcessAssembly (assembly, _context); @@ -1616,6 +1622,10 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep return null; MarkModule (type.Scope as ModuleDefinition, new DependencyInfo (DependencyKind.ScopeOfType, type)); + + foreach (Action handleMarkType in _markContext.MarkTypeActions) + handleMarkType (type); + MarkType (type.BaseType, new DependencyInfo (DependencyKind.BaseType, type), type); if (type.DeclaringType != null) MarkType (type.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, type), type); @@ -2655,6 +2665,9 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo _unreachableBlocksOptimizer.ProcessMethod (method); + foreach (Action handleMarkMethod in _markContext.MarkMethodActions) + handleMarkMethod (method); + if (!markedForCall) MarkType (method.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, method), method); MarkCustomAttributes (method, new DependencyInfo (DependencyKind.CustomAttribute, method), method); diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs new file mode 100644 index 0000000000000..f47fe101ff314 --- /dev/null +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + public class MarkStepContext : MarkContext + { + + public List> MarkAssemblyActions { get; } + public List> MarkTypeActions { get; } + public List> MarkMethodActions { get; } + + public MarkStepContext () + { + MarkAssemblyActions = new List> (); + MarkTypeActions = new List> (); + MarkMethodActions = new List> (); + } + + public override void RegisterMarkAssemblyAction (Action action) + { + MarkAssemblyActions.Add (action); + } + + public override void RegisterMarkTypeAction (Action action) + { + MarkTypeActions.Add (action); + } + + public override void RegisterMarkMethodAction (Action action) + { + MarkMethodActions.Add (action); + } + } +} \ No newline at end of file diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs b/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs new file mode 100644 index 0000000000000..9d5c57fcd597a --- /dev/null +++ b/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs @@ -0,0 +1,179 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Collections.Generic; + +namespace Mono.Linker.Steps +{ + // + // Dispatcher for SubSteps which only need to run on marked assemblies. + // This simplifies the implementation of linker custom steps, in the same + // way that SubStepsDispatcher does, but it implements IMarkHandler + // and registers a callback that gets invoked during MarkStep when an + // assembly gets marked. + // + public abstract class MarkSubStepsDispatcher : IMarkHandler + { + readonly List substeps; + + List on_assemblies; + List on_types; + List on_fields; + List on_methods; + List on_properties; + List on_events; + + protected MarkSubStepsDispatcher (IEnumerable subSteps) + { + substeps = new List (subSteps); + } + + public virtual void Initialize (LinkContext context, MarkContext markContext) + { + InitializeSubSteps (context); + markContext.RegisterMarkAssemblyAction (assembly => BrowseAssembly (assembly)); + } + + static bool HasSubSteps (List substeps) => substeps?.Count > 0; + + void BrowseAssembly (AssemblyDefinition assembly) + { + CategorizeSubSteps (assembly); + + if (HasSubSteps (on_assemblies)) + DispatchAssembly (assembly); + + if (!ShouldDispatchTypes ()) + return; + + BrowseTypes (assembly.MainModule.Types); + } + + bool ShouldDispatchTypes () + { + return HasSubSteps (on_types) + || HasSubSteps (on_fields) + || HasSubSteps (on_methods) + || HasSubSteps (on_properties) + || HasSubSteps (on_events); + } + + void BrowseTypes (Collection types) + { + foreach (TypeDefinition type in types) { + DispatchType (type); + + if (type.HasFields && HasSubSteps (on_fields)) { + foreach (FieldDefinition field in type.Fields) + DispatchField (field); + } + + if (type.HasMethods && HasSubSteps (on_methods)) { + foreach (MethodDefinition method in type.Methods) + DispatchMethod (method); + } + + if (type.HasProperties && HasSubSteps (on_properties)) { + foreach (PropertyDefinition property in type.Properties) + DispatchProperty (property); + } + + if (type.HasEvents && HasSubSteps (on_events)) { + foreach (EventDefinition @event in type.Events) + DispatchEvent (@event); + } + + if (type.HasNestedTypes) + BrowseTypes (type.NestedTypes); + } + } + + void DispatchAssembly (AssemblyDefinition assembly) + { + foreach (var substep in on_assemblies) { + substep.ProcessAssembly (assembly); + } + } + + void DispatchType (TypeDefinition type) + { + foreach (var substep in on_types) { + substep.ProcessType (type); + } + } + + void DispatchField (FieldDefinition field) + { + foreach (var substep in on_fields) { + substep.ProcessField (field); + } + } + + void DispatchMethod (MethodDefinition method) + { + foreach (var substep in on_methods) { + substep.ProcessMethod (method); + } + } + + void DispatchProperty (PropertyDefinition property) + { + foreach (var substep in on_properties) { + substep.ProcessProperty (property); + } + } + + void DispatchEvent (EventDefinition @event) + { + foreach (var substep in on_events) { + substep.ProcessEvent (@event); + } + } + + void InitializeSubSteps (LinkContext context) + { + foreach (var substep in substeps) + substep.Initialize (context); + } + + void CategorizeSubSteps (AssemblyDefinition assembly) + { + on_assemblies = new List (); + on_types = new List (); + on_fields = new List (); + on_methods = new List (); + on_properties = new List (); + on_events = new List (); + + foreach (var substep in substeps) + CategorizeSubStep (substep, assembly); + } + + void CategorizeSubStep (ISubStep substep, AssemblyDefinition assembly) + { + if (!substep.IsActiveFor (assembly)) + return; + + CategorizeTarget (substep, SubStepTargets.Assembly, on_assemblies); + CategorizeTarget (substep, SubStepTargets.Type, on_types); + CategorizeTarget (substep, SubStepTargets.Field, on_fields); + CategorizeTarget (substep, SubStepTargets.Method, on_methods); + CategorizeTarget (substep, SubStepTargets.Property, on_properties); + CategorizeTarget (substep, SubStepTargets.Event, on_events); + } + + static void CategorizeTarget (ISubStep substep, SubStepTargets target, List list) + { + if (!Targets (substep, target)) + return; + + list.Add (substep); + } + + static bool Targets (ISubStep substep, SubStepTargets target) => (substep.Targets & target) == target; + } +} \ No newline at end of file diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs index 59c73b8a63215..02f1050ec8448 100644 --- a/src/tools/illink/src/linker/Linker/Driver.cs +++ b/src/tools/illink/src/linker/Linker/Driver.cs @@ -776,57 +776,111 @@ protected virtual void AddXmlDependencyRecorder (LinkContext context, string fil context.Tracer.AddRecorder (new XmlDependencyRecorder (context, fileName)); } - protected bool AddCustomStep (Pipeline pipeline, string arg) + protected bool AddMarkHandler (Pipeline pipeline, string arg) + { + if (!TryGetCustomAssembly (ref arg, out Assembly custom_assembly)) + return false; + + var step = ResolveStep (arg, custom_assembly); + if (step == null) + return false; + + pipeline.AppendMarkHandler (step); + return true; + } + + bool TryGetCustomAssembly (ref string arg, out Assembly assembly) { - Assembly custom_assembly = null; + assembly = null; int pos = arg.IndexOf (","); - if (pos != -1) { - custom_assembly = GetCustomAssembly (arg.Substring (pos + 1)); - if (custom_assembly == null) + if (pos == -1) + return true; + + assembly = GetCustomAssembly (arg.Substring (pos + 1)); + if (assembly == null) + return false; + + arg = arg.Substring (0, pos); + return true; + } + + protected bool AddCustomStep (Pipeline pipeline, string arg) + { + if (!TryGetCustomAssembly (ref arg, out Assembly custom_assembly)) + return false; + + string customStepName; + string targetName = null; + bool before = false; + if (!arg.Contains (":")) { + customStepName = arg; + } else { + string[] parts = arg.Split (':'); + if (parts.Length != 2) { + context.LogError ($"Invalid value '{arg}' specified for '--custom-step' option", 1024); return false; - arg = arg.Substring (0, pos); - } + } + customStepName = parts[1]; - pos = arg.IndexOf (":"); - if (pos == -1) { - var step = ResolveStep (arg, custom_assembly); - if (step == null) + if (!parts[0].StartsWith ("-") && !parts[0].StartsWith ("+")) { + context.LogError ($"Expected '+' or '-' to control new step insertion", 1025); return false; + } - pipeline.AppendStep (step); - return true; + before = parts[0][0] == '-'; + targetName = parts[0].Substring (1); } - string[] parts = arg.Split (':'); - if (parts.Length != 2) { - context.LogError ($"Invalid value '{arg}' specified for '--custom-step' option", 1024); + var stepType = ResolveStepType (customStepName, custom_assembly); + if (stepType == null) return false; - } - if (!parts[0].StartsWith ("-") && !parts[0].StartsWith ("+")) { - context.LogError ($"Expected '+' or '-' to control new step insertion", 1025); - return false; - } + if (typeof (IStep).IsAssignableFrom (stepType)) { + + var customStep = (IStep) Activator.CreateInstance (stepType); + if (targetName == null) { + pipeline.AppendStep (customStep); + return true; + } - bool before = parts[0][0] == '-'; - string name = parts[0].Substring (1); + IStep target = FindStep (pipeline, targetName); + if (target == null) { + context.LogError ($"Pipeline step '{targetName}' could not be found", 1026); + return false; + } - IStep target = FindStep (pipeline, name); - if (target == null) { - context.LogError ($"Pipeline step '{name}' could not be found", 1026); - return false; + if (before) + pipeline.AddStepBefore (target, customStep); + else + pipeline.AddStepAfter (target, customStep); + + return true; } - IStep newStep = ResolveStep (parts[1], custom_assembly); - if (newStep == null) - return false; + if (typeof (IMarkHandler).IsAssignableFrom (stepType)) { - if (before) - pipeline.AddStepBefore (target, newStep); - else - pipeline.AddStepAfter (target, newStep); + var customStep = (IMarkHandler) Activator.CreateInstance (stepType); + if (targetName == null) { + pipeline.AppendMarkHandler (customStep); + return true; + } - return true; + IMarkHandler target = FindMarkHandler (pipeline, targetName); + if (target == null) { + context.LogError ($"Pipeline step '{targetName}' could not be found", 1026); + return false; + } + + if (before) + pipeline.AddMarkHandlerBefore (target, customStep); + else + pipeline.AddMarkHandlerAfter (target, customStep); + + return true; + } + + context.LogError ($"Custom step '{stepType}' is incompatible with this linker version", 1028); + return false; } static IStep FindStep (Pipeline pipeline, string name) @@ -840,7 +894,30 @@ static IStep FindStep (Pipeline pipeline, string name) return null; } - IStep ResolveStep (string type, Assembly assembly) + static IMarkHandler FindMarkHandler (Pipeline pipeline, string name) + { + foreach (IMarkHandler step in pipeline.MarkHandlers) { + Type t = step.GetType (); + if (t.Name == name) + return step; + } + + return null; + } + + Type ResolveStepType (string type, Assembly assembly) + { + Type step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false); + + if (step == null) { + context.LogError ($"Custom step '{type}' could not be found", 1027); + return null; + } + + return step; + } + + TStep ResolveStep (string type, Assembly assembly) where TStep : class { Type step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false); @@ -849,12 +926,12 @@ IStep ResolveStep (string type, Assembly assembly) return null; } - if (!typeof (IStep).IsAssignableFrom (step)) { + if (!typeof (TStep).IsAssignableFrom (step)) { context.LogError ($"Custom step '{type}' is incompatible with this linker version", 1028); return null; } - return (IStep) Activator.CreateInstance (step); + return (TStep) Activator.CreateInstance (step); } static string[] GetFiles (string param) diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs index 01fbbabd537bf..12ea9ce9f5769 100644 --- a/src/tools/illink/src/linker/Linker/LinkContext.cs +++ b/src/tools/illink/src/linker/Linker/LinkContext.cs @@ -32,7 +32,7 @@ using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; - +using Mono.Linker.Steps; namespace Mono.Linker { @@ -190,6 +190,8 @@ public ISymbolWriterProvider SymbolWriterProvider { public string AssemblyListFile { get; set; } + public List MarkHandlers { get; } + public LinkContext (Pipeline pipeline, ILogger logger) { _pipeline = pipeline; @@ -221,6 +223,7 @@ public LinkContext (Pipeline pipeline, ILogger logger) GeneralWarnAsError = false; WarnAsError = new Dictionary (); WarnVersion = WarnVersion.Latest; + MarkHandlers = new List (); const CodeOptimizations defaultOptimizations = CodeOptimizations.BeforeFieldInit | diff --git a/src/tools/illink/src/linker/Linker/Pipeline.cs b/src/tools/illink/src/linker/Linker/Pipeline.cs index 0aa5a3d1ae8b6..8d5ebc30a994b 100644 --- a/src/tools/illink/src/linker/Linker/Pipeline.cs +++ b/src/tools/illink/src/linker/Linker/Pipeline.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; - using Mono.Linker.Steps; namespace Mono.Linker @@ -38,10 +37,12 @@ public class Pipeline { readonly List _steps; + public List MarkHandlers { get; } public Pipeline () { _steps = new List (); + MarkHandlers = new List (); } public void PrependStep (IStep step) @@ -54,6 +55,11 @@ public void AppendStep (IStep step) _steps.Add (step); } + public void AppendMarkHandler (IMarkHandler step) + { + MarkHandlers.Add (step); + } + public void AddStepBefore (Type target, IStep step) { for (int i = 0; i < _steps.Count; i++) { @@ -62,8 +68,7 @@ public void AddStepBefore (Type target, IStep step) return; } } - string msg = String.Format ("Step {0} could not be inserted before (not found) {1}", step, target); - throw new InvalidOperationException (msg); + throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}"); } public void AddStepBefore (IStep target, IStep step) @@ -74,6 +79,18 @@ public void AddStepBefore (IStep target, IStep step) return; } } + throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}"); + } + + public void AddMarkHandlerBefore (IMarkHandler target, IMarkHandler step) + { + for (int i = 0; i < MarkHandlers.Count; i++) { + if (MarkHandlers[i] == target) { + MarkHandlers.Insert (i, step); + return; + } + } + throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}"); } public void ReplaceStep (Type target, IStep step) @@ -93,8 +110,7 @@ public void AddStepAfter (Type target, IStep step) return; } } - string msg = String.Format ("Step {0} could not be inserted after (not found) {1}", step, target); - throw new InvalidOperationException (msg); + throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}"); } public void AddStepAfter (IStep target, IStep step) @@ -108,6 +124,21 @@ public void AddStepAfter (IStep target, IStep step) return; } } + throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}"); + } + + public void AddMarkHandlerAfter (IMarkHandler target, IMarkHandler step) + { + for (int i = 0; i < MarkHandlers.Count; i++) { + if (MarkHandlers[i] == target) { + if (i == MarkHandlers.Count - 1) + MarkHandlers.Add (step); + else + MarkHandlers.Insert (i + 1, step); + return; + } + } + throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}"); } public void RemoveStep (Type target) @@ -140,6 +171,15 @@ public IStep[] GetSteps () return _steps.ToArray (); } + public void InitializeMarkHandlers (LinkContext context, MarkContext markContext) + { + while (MarkHandlers.Count > 0) { + IMarkHandler markHandler = MarkHandlers[0]; + markHandler.Initialize (context, markContext); + MarkHandlers.Remove (markHandler); + } + } + public bool ContainsStep (Type type) { foreach (IStep step in _steps) diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs b/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs new file mode 100644 index 0000000000000..3f9aea88feb49 --- /dev/null +++ b/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Mono.Linker.Steps +{ + /// + /// This API supports the product infrastructure and is not intended to be used directly from your code. + /// + public interface IMarkHandler + { + void Initialize (LinkContext context, MarkContext markContext); + } +} diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs b/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs new file mode 100644 index 0000000000000..b4c231432219e --- /dev/null +++ b/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Mono.Cecil; + +namespace Mono.Linker.Steps +{ + public abstract class MarkContext + { + public abstract void RegisterMarkAssemblyAction (Action action); + + public abstract void RegisterMarkTypeAction (Action action); + + public abstract void RegisterMarkMethodAction (Action action); + } +} \ No newline at end of file diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs b/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs new file mode 100644 index 0000000000000..a90d923c665ea --- /dev/null +++ b/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Mono.Linker.Steps +{ + public abstract class MarkSubStepsDispatcher : IMarkHandler + { + protected MarkSubStepsDispatcher (IEnumerable subSteps) => throw null; + + public virtual void Initialize (LinkContext context, MarkContext markContext) => throw null; + } +} \ No newline at end of file diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs new file mode 100644 index 0000000000000..7f257dc49c99d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using Mono.Cecil; +using Mono.Linker; +using Mono.Linker.Steps; + +public class CustomMarkHandler : IMarkHandler +{ + LinkContext _context; + + public void Initialize (LinkContext context, MarkContext markContext) + { + _context = context; + markContext.RegisterMarkAssemblyAction (assembly => DiscoverTypesInAssembly (assembly)); + markContext.RegisterMarkTypeAction (type => DiscoverMethodsInType (type)); + markContext.RegisterMarkMethodAction (method => DiscoverMethodsOnDeclaringType (method)); + } + + void MarkTypeFoo (TypeDefinition type) + { + if (type.Name == "DiscoveredTypeForAssembly") + _context.Annotations.Mark (type); + + if (!type.HasNestedTypes) + return; + + foreach (var nested in type.NestedTypes) + MarkTypeFoo (nested); + } + + void DiscoverTypesInAssembly (AssemblyDefinition assembly) + { + foreach (var type in assembly.MainModule.Types) + MarkTypeFoo (type); + } + + void DiscoverMethodsInType (TypeDefinition type) + { + foreach (var method in type.Methods) { + if (method.Name == $"DiscoveredMethodForType_{type.Name}") + _context.Annotations.Mark (method); + } + } + + void DiscoverMethodsOnDeclaringType (MethodDefinition method) + { + foreach (var otherMethod in method.DeclaringType.Methods) + if (otherMethod.Name == $"DiscoveredMethodForMethod_{method.Name}") + _context.Annotations.Mark (otherMethod); + } +} \ No newline at end of file diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs new file mode 100644 index 0000000000000..9dab0d3feeec6 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using Mono.Linker; +using Mono.Linker.Steps; + +public class MyMarkSubStepsDispatcher : MarkSubStepsDispatcher +{ + public MyMarkSubStepsDispatcher () + : base (GetSubSteps ()) + { + } + + public override void Initialize (LinkContext context, MarkContext markContext) + { + base.Initialize (context, markContext); + } + + static IEnumerable GetSubSteps () + { + yield return new CustomSubStep (); + } +} \ No newline at end of file diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs new file mode 100644 index 0000000000000..8a8208c1f7692 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs @@ -0,0 +1,89 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Extensibility +{ + [SetupCompileBefore ("MarkHandler.dll", new[] { "Dependencies/CustomMarkHandler.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })] + [SetupLinkerArgument ("--custom-step", "CustomMarkHandler,MarkHandler.dll")] + public class MarkHandlerUsage + { + public static void Main () + { + UsedType.UsedMethod (); + } + + [Kept] + public class DiscoveredTypeForAssembly + { + [Kept] + public static void DiscoveredMethodForType_DiscoveredTypeForAssembly () + { + } + + [Kept] + public static void DiscoveredMethodForMethod_DiscoveredMethodForType_DiscoveredTypeForAssembly () + { + } + + public static void UnusedMethod () + { + } + + public static void DiscoveredMethodForMethod_UnusedMethod () + { + } + } + + [Kept] + public class UsedType + { + [Kept] + public static void DiscoveredMethodForType_UsedType () + { + } + + [Kept] + public static void DiscoveredMethodForMethod_DiscoveredMethodForType_UsedType () + { + } + + [Kept] + public static void UsedMethod () + { + } + + [Kept] + public static void DiscoveredMethodForMethod_UsedMethod () + { + } + + [Kept] + public static void DiscoveredMethodForMethod_DiscoveredMethodForMethod_UsedMethod () + { + } + + public static void UnusedMethod () + { + } + + public static void DiscoveredMethodForMethod_UnusedMethod () + { + } + } + + public class UnusedType + { + public static void DiscoveredMethodForType_UnusedType () + { + } + + public static void DiscoveredMethodForMethod_DiscoveredMethodForType_UnusedType () + { + } + + public static void UnusedMethod () + { + } + } + } +} \ No newline at end of file diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs new file mode 100644 index 0000000000000..794b89e51ded5 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs @@ -0,0 +1,24 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Extensibility +{ + [SetupCompileBefore ("MyMarkSubStepsDispatcher.dll", new[] { "Dependencies/MyMarkSubStepsDispatcher.cs", "Dependencies/CustomSubStep.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })] + [SetupLinkerArgument ("--custom-step", "MyMarkSubStepsDispatcher,MyMarkSubStepsDispatcher.dll")] + public class MarkSubStepsDispatcherUsage + { + public static void Main () + { + } + + [Kept] + public class NestedType + { + public int field; + + public static void SomeMethod () + { + } + } + } +} \ No newline at end of file