diff options
author | Sven Boemer <sbomer@gmail.com> | 2020-04-24 01:15:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-24 01:15:06 +0300 |
commit | d60f4eab2982dfef19d71dfcf2d5ab2286950af2 (patch) | |
tree | cdeabea66ef73ddee0693d93675d3ebe7d863e0d | |
parent | f5a987566eef13ea8f06e1fcfd3680ce307ae51f (diff) |
Add feature removal based on XML substitutions (#1132)
* Add feature removal based on XML substitutions
* Don't use Dictionary.TryAdd on mono
* Add doc comments to the task
* Change feature argument and description
* Use High importance for invalid features
And for invalid feature settings
* Look for embedded ILLink.Substitutions.xml
* Undo indentation change
* Tweak description slighty
* Respond to feedback
- Don't check for multiple feature settings of same feature
- Put substitution step after BlacklistStep
- Require a location in ctors that take an XPathDocument
* Remove --exclude-feature from ILLink build
* Add steps in a consistent order
* Only allow passing boolean feature values
* Use new logging APIs
36 files changed, 601 insertions, 82 deletions
diff --git a/src/ILLink.Tasks/LinkTask.cs b/src/ILLink.Tasks/LinkTask.cs index d5932913e..4f0193af5 100644 --- a/src/ILLink.Tasks/LinkTask.cs +++ b/src/ILLink.Tasks/LinkTask.cs @@ -107,6 +107,14 @@ namespace ILLink.Tasks bool? _iPConstProp; /// <summary> + /// A list of feature names used by the body substitution logic. + /// Each Item requires "Value" boolean metadata with the value of + /// the feature setting. + /// Maps to '--feature'. + /// </summary> + public ITaskItem [] FeatureSettings { get; set; } + + /// <summary> /// Boolean specifying whether to enable sealer optimization globally. /// Maps to '--enable-opt sealer' or '--disable-opt sealer'. /// </summary> @@ -329,6 +337,16 @@ namespace ILLink.Tasks } } + if (FeatureSettings != null) { + foreach (var featureSetting in FeatureSettings) { + var feature = featureSetting.ItemSpec; + var featureValue = featureSetting.GetMetadata ("Value"); + if (String.IsNullOrEmpty(featureValue)) + throw new ArgumentException ("feature settings require \"Value\" metadata"); + args.Append ("--feature ").Append (feature).Append(" ").AppendLine (featureValue); + } + } + if (LinkSymbols) args.AppendLine ("-b"); diff --git a/src/linker/Linker.Steps/BlacklistStep.cs b/src/linker/Linker.Steps/BlacklistStep.cs index c1187ce72..daa3d81c2 100644 --- a/src/linker/Linker.Steps/BlacklistStep.cs +++ b/src/linker/Linker.Steps/BlacklistStep.cs @@ -27,6 +27,7 @@ // using System; +using System.Collections.Generic; using System.Linq; using System.IO; using System.Reflection; @@ -41,13 +42,15 @@ namespace Mono.Linker.Steps { protected override void Process () { + var steps_to_add = new Stack<IStep> (); + foreach (string name in Assembly.GetExecutingAssembly ().GetManifestResourceNames ()) { - if (!name.EndsWith (".xml", StringComparison.OrdinalIgnoreCase) || !ShouldProcessAssemblyResource (GetAssemblyName (name))) + if (!name.EndsWith (".xml", StringComparison.OrdinalIgnoreCase) || !ShouldProcessRootDescriptorResource (GetAssemblyName (name))) continue; try { Context.LogMessage ($"Processing resource linker descriptor: {name}"); - AddToPipeline (GetResolveStep (name)); + steps_to_add.Push (GetResolveStep (name)); } catch (XmlException ex) { /* This could happen if some broken XML file is included. */ Context.LogMessage ($"Error processing {name}: {ex}"); @@ -55,22 +58,36 @@ namespace Mono.Linker.Steps { } foreach (var asm in Context.GetAssemblies ()) { - foreach (var rsc in asm.Modules - .SelectMany (mod => mod.Resources) - .Where (res => res.ResourceType == ResourceType.Embedded) - .Where (res => res.Name.EndsWith (".xml", StringComparison.OrdinalIgnoreCase)) - .Where (res => ShouldProcessAssemblyResource (GetAssemblyName (res.Name))) + var embeddedXml = asm.Modules + .SelectMany (mod => mod.Resources) + .Where (res => res.ResourceType == ResourceType.Embedded) + .Where (res => res.Name.EndsWith (".xml", StringComparison.OrdinalIgnoreCase)); + foreach (var rsc in embeddedXml + .Where (res => ShouldProcessRootDescriptorResource (GetAssemblyName (res.Name))) .Cast<EmbeddedResource> ()) { try { Context.LogMessage ($"Processing embedded resource linker descriptor: {rsc.Name}"); - - AddToPipeline (GetExternalResolveStep (rsc, asm)); + steps_to_add.Push (GetExternalResolveStep (rsc, asm)); } catch (XmlException ex) { /* This could happen if some broken XML file is embedded. */ Context.LogMessage ($"Error processing {rsc.Name}: {ex}"); } } + + foreach (var rsc in embeddedXml + .Where (res => res.Name.Equals ("ILLink.Substitutions.xml", StringComparison.OrdinalIgnoreCase)) + .Cast<EmbeddedResource> ()) { + try { + Context.LogMessage ($"Processing embedded {rsc.Name} from {asm.Name}"); + steps_to_add.Push (GetExternalSubstitutionStep (rsc, asm)); + } catch (XmlException ex) { + Context.LogMessage ($"Error processing {rsc.Name}: {ex}"); + } + } } + + foreach (var step in steps_to_add) + AddToPipeline (step); } static string GetAssemblyName (string descriptor) @@ -82,7 +99,7 @@ namespace Mono.Linker.Steps { return descriptor.Substring (0, pos); } - bool ShouldProcessAssemblyResource (string name) + bool ShouldProcessRootDescriptorResource (string name) { AssemblyDefinition assembly = Context.GetLoadedAssembly (name); @@ -110,6 +127,11 @@ namespace Mono.Linker.Steps { return new ResolveFromXmlStep (GetExternalDescriptor (resource), resource.Name, assembly, "resource " + resource.Name + " in " + assembly.FullName); } + IStep GetExternalSubstitutionStep (EmbeddedResource resource, AssemblyDefinition assembly) + { + return new BodySubstituterStep (GetExternalDescriptor (resource), resource.Name, assembly, "resource " + resource.Name + " in " + assembly.FullName); + } + static ResolveFromXmlStep GetResolveStep (string descriptor) { return new ResolveFromXmlStep (GetDescriptor (descriptor), "descriptor " + descriptor + " from " + Assembly.GetExecutingAssembly ().FullName); diff --git a/src/linker/Linker.Steps/BodySubstituterStep.cs b/src/linker/Linker.Steps/BodySubstituterStep.cs index 9ea44b55b..4f00fc177 100644 --- a/src/linker/Linker.Steps/BodySubstituterStep.cs +++ b/src/linker/Linker.Steps/BodySubstituterStep.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using System.Globalization; using System.Xml.XPath; @@ -9,37 +8,63 @@ namespace Mono.Linker.Steps { public class BodySubstituterStep : BaseStep { - protected override void Process () + readonly XPathDocument _document; + readonly string _xmlDocumentLocation; + readonly string _resourceName; + readonly AssemblyDefinition _resourceAssembly; + + public BodySubstituterStep (XPathDocument document, string xmlDocumentLocation) { - var files = Context.Substitutions; - if (files == null) - return; + _document = document; + _xmlDocumentLocation = xmlDocumentLocation; + } - foreach (var file in files) { - try { - ReadSubstitutionFile (GetSubstitutions (file)); - } catch (Exception ex) when (!(ex is XmlResolutionException)) { - throw new XmlResolutionException ($"Failed to process XML substitution '{file}'", ex); - } + public BodySubstituterStep (XPathDocument document, string resourceName, AssemblyDefinition resourceAssembly, string xmlDocumentLocation = "") + : this (document, xmlDocumentLocation) + { + if (string.IsNullOrEmpty (resourceName)) + throw new ArgumentNullException (nameof (resourceName)); - } + _resourceName = resourceName; + _resourceAssembly = resourceAssembly ?? throw new ArgumentNullException (nameof (resourceAssembly)); } - static XPathDocument GetSubstitutions (string substitutionsFile) + protected override void Process () { - using (FileStream fs = File.OpenRead (substitutionsFile)) { - return GetSubstitutions (fs); + try { + ReadSubstitutions (_document); + + if (!string.IsNullOrEmpty (_resourceName) && Context.StripResources) + Context.Annotations.AddResourceToRemove (_resourceAssembly, _resourceName); + } catch (Exception ex) when (!(ex is XmlResolutionException)) { + throw new XmlResolutionException ($"Failed to process XML substitution: '{_xmlDocumentLocation}'", ex); } } - static XPathDocument GetSubstitutions (Stream substitutions) + bool ShouldProcessSubstitutions (XPathNavigator nav) { - using (StreamReader sr = new StreamReader (substitutions)) { - return new XPathDocument (sr); + var feature = GetAttribute (nav, "feature"); + if (string.IsNullOrEmpty (feature)) + return true; + + var value = GetAttribute (nav, "featurevalue"); + if (string.IsNullOrEmpty (value)) { + Context.LogMessage (MessageContainer.CreateErrorMessage ($"Feature {feature} does not specify a \"featurevalue\" attribute", 1001)); + return false; } + + if (!bool.TryParse (value, out bool bValue)) { + Context.LogMessage(MessageContainer.CreateErrorMessage ($"Unsupported non-boolean feature definition {feature}", 1002)); + return false; + } + + if (Context.FeatureSettings == null || !Context.FeatureSettings.TryGetValue (feature, out bool featureSetting)) + return false; + + return bValue == featureSetting; } - void ReadSubstitutionFile (XPathDocument document) + void ReadSubstitutions (XPathDocument document) { XPathNavigator nav = document.CreateNavigator (); @@ -47,7 +72,8 @@ namespace Mono.Linker.Steps if (!nav.MoveToChild ("linker", "")) return; - // TODO: Add handling for feature + if (!ShouldProcessSubstitutions (nav)) + return; ProcessAssemblies (nav.SelectChildren ("assembly", "")); } @@ -57,6 +83,9 @@ namespace Mono.Linker.Steps while (iterator.MoveNext ()) { var name = GetAssemblyName (iterator.Current); + if (!ShouldProcessSubstitutions (iterator.Current)) + continue; + AssemblyDefinition assembly = Context.GetLoadedAssembly (name.Name); if (assembly == null) { @@ -78,6 +107,9 @@ namespace Mono.Linker.Steps while (iterator.MoveNext ()) { XPathNavigator nav = iterator.Current; + if (!ShouldProcessSubstitutions (nav)) + continue; + string fullname = GetAttribute (nav, "fullname"); TypeDefinition type = assembly.MainModule.GetType (fullname); @@ -96,21 +128,32 @@ namespace Mono.Linker.Steps if (!nav.HasChildren) return; + if (!ShouldProcessSubstitutions (nav)) + return; + XPathNodeIterator methods = nav.SelectChildren ("method", ""); if (methods.Count > 0) ProcessMethods (type, methods); var fields = nav.SelectChildren ("field", ""); if (fields.Count > 0) { - while (fields.MoveNext ()) + while (fields.MoveNext ()) { + if (!ShouldProcessSubstitutions (fields.Current)) + continue; + ProcessField (type, fields); + } } } void ProcessMethods (TypeDefinition type, XPathNodeIterator iterator) { - while (iterator.MoveNext ()) + while (iterator.MoveNext ()) { + if (!ShouldProcessSubstitutions (iterator.Current)) + continue; + ProcessMethod (type, iterator); + } } void ProcessMethod (TypeDefinition type, XPathNodeIterator iterator) diff --git a/src/linker/Linker.Steps/MarkStep.cs b/src/linker/Linker.Steps/MarkStep.cs index ecd44c47c..3caf171ce 100644 --- a/src/linker/Linker.Steps/MarkStep.cs +++ b/src/linker/Linker.Steps/MarkStep.cs @@ -1178,8 +1178,11 @@ namespace Mono.Linker.Steps { { // Keep default ctor for XmlSerializer support. See https://github.com/mono/linker/issues/957 MarkDefaultConstructor (type, new DependencyInfo (DependencyKind.SerializationMethodForType, type)); - if (!_context.IsFeatureExcluded ("deserialization")) - MarkMethodsIf (type.Methods, IsSpecialSerializationConstructor, new DependencyInfo (DependencyKind.SerializationMethodForType, type)); +#if !FEATURE_ILLINK + if (_context.IsFeatureExcluded ("deserialization")) + return; +#endif + MarkMethodsIf (type.Methods, IsSpecialSerializationConstructor, new DependencyInfo (DependencyKind.SerializationMethodForType, type)); } internal protected virtual TypeDefinition MarkType (TypeReference reference, DependencyInfo reason) @@ -1241,7 +1244,12 @@ namespace Mono.Linker.Steps { // TODO: This needs work to ensure we handle EventSource appropriately. // This marks static fields of KeyWords/OpCodes/Tasks subclasses of an EventSource type. - if (!_context.IsFeatureExcluded ("etw") && BCL.EventTracingForWindows.IsEventSourceImplementation (type, _context)) { + if ( +#if !FEATURE_ILLINK + !_context.IsFeatureExcluded ("etw") && +#endif + BCL.EventTracingForWindows.IsEventSourceImplementation (type, _context) + ) { MarkEventSourceProviders (type); } @@ -1288,10 +1296,14 @@ namespace Mono.Linker.Steps { if (ShouldMarkTypeStaticConstructor (type) && reason.Kind != DependencyKind.TriggersCctorForCalledMethod) MarkStaticConstructor (type, new DependencyInfo (DependencyKind.CctorForType, type)); - if (_context.IsFeatureExcluded ("deserialization")) +#if !FEATURE_ILLINK + if (_context.IsFeatureExcluded ("deserialization")) { MarkMethodsIf (type.Methods, HasOnSerializeAttribute, new DependencyInfo (DependencyKind.SerializationMethodForType, type)); - else + } else +#endif + { MarkMethodsIf (type.Methods, HasOnSerializeOrDeserializeAttribute, new DependencyInfo (DependencyKind.SerializationMethodForType, type)); + } } DoAdditionalTypeProcessing (type); diff --git a/src/linker/Linker.Steps/ResolveFromXmlStep.cs b/src/linker/Linker.Steps/ResolveFromXmlStep.cs index b070b07db..529d48f72 100644 --- a/src/linker/Linker.Steps/ResolveFromXmlStep.cs +++ b/src/linker/Linker.Steps/ResolveFromXmlStep.cs @@ -62,7 +62,7 @@ namespace Mono.Linker.Steps { readonly string _resourceName; readonly AssemblyDefinition _resourceAssembly; - public ResolveFromXmlStep (XPathDocument document, string xmlDocumentLocation = "<unspecified>") + public ResolveFromXmlStep (XPathDocument document, string xmlDocumentLocation) { _document = document; _xmlDocumentLocation = xmlDocumentLocation; @@ -108,8 +108,10 @@ namespace Mono.Linker.Steps { protected virtual void ProcessAssembly (AssemblyDefinition assembly, XPathNodeIterator iterator) { +#if !FEATURE_ILLINK if (IsExcluded (iterator.Current)) return; +#endif if (GetTypePreserve (iterator.Current) == TypePreserve.All) { foreach (var type in assembly.MainModule.Types) @@ -232,8 +234,10 @@ namespace Mono.Linker.Steps { protected virtual void ProcessType (TypeDefinition type, XPathNavigator nav) { +#if !FEATURE_ILLINK if (IsExcluded (nav)) return; +#endif TypePreserve preserve = GetTypePreserve (nav); if (preserve != TypePreserve.Nothing) @@ -328,8 +332,10 @@ namespace Mono.Linker.Steps { protected virtual void ProcessField (TypeDefinition type, XPathNodeIterator iterator) { +#if !FEATURE_ILLINK if (IsExcluded (iterator.Current)) return; +#endif string value = GetSignature (iterator.Current); if (!String.IsNullOrEmpty (value)) @@ -394,8 +400,10 @@ namespace Mono.Linker.Steps { protected virtual void ProcessMethod (TypeDefinition type, XPathNodeIterator iterator, bool required) { +#if !FEATURE_ILLINK if (IsExcluded (iterator.Current)) return; +#endif string value = GetSignature (iterator.Current); if (!String.IsNullOrEmpty (value)) @@ -490,9 +498,11 @@ namespace Mono.Linker.Steps { protected virtual void ProcessEvent (TypeDefinition type, XPathNodeIterator iterator, bool required) { +#if !FEATURE_ILLINK if (IsExcluded (iterator.Current)) return; - +#endif + string value = GetSignature (iterator.Current); if (!String.IsNullOrEmpty (value)) ProcessEventSignature (type, value, required); @@ -560,9 +570,11 @@ namespace Mono.Linker.Steps { protected virtual void ProcessProperty (TypeDefinition type, XPathNodeIterator iterator, bool required) { +#if !FEATURE_ILLINK if (IsExcluded (iterator.Current)) return; - +#endif + string value = GetSignature (iterator.Current); if (!String.IsNullOrEmpty (value)) ProcessPropertySignature (type, value, GetAccessors (iterator.Current), required); @@ -698,6 +710,7 @@ namespace Mono.Linker.Steps { return nav.GetAttribute (attribute, _ns); } +#if !FEATURE_ILLINK protected virtual bool IsExcluded (XPathNavigator nav) { var value = GetAttribute (nav, "feature"); @@ -706,7 +719,7 @@ namespace Mono.Linker.Steps { return Context.IsFeatureExcluded (value); } - +#endif public override string ToString () { diff --git a/src/linker/Linker/Driver.cs b/src/linker/Linker/Driver.cs index 767e9c73f..880275471 100644 --- a/src/linker/Linker/Driver.cs +++ b/src/linker/Linker/Driver.cs @@ -178,9 +178,13 @@ namespace Mono.Linker { #if !FEATURE_ILLINK I18nAssemblies assemblies = I18nAssemblies.All; -#endif - var custom_steps = new List<string> (); var excluded_features = new HashSet<string> (StringComparer.Ordinal); + var resolve_from_xapi_steps = new Stack<string> (); +#endif + var resolve_from_assembly_steps = new Stack<(string, ResolveFromAssemblyStep.RootVisibility)> (); + var resolve_from_xml_steps = new Stack<string> (); + var body_substituter_steps = new Stack<string> (); + var custom_steps = new Stack<string> (); var set_optimizations = new List<(CodeOptimizations, string, bool)> (); bool dumpDependencies = false; string dependenciesFileName = null; @@ -252,11 +256,11 @@ namespace Mono.Linker { return -1; } - if (!GetStringParam (token, l => context.AddSubstitutionFile (l))) + if (!GetStringParam (token, l => body_substituter_steps.Push (l))) return -1; continue; - +#if !FEATURE_ILLINK case "--exclude-feature": if (arguments.Count < 1) { ErrorMissingArgument (token); @@ -272,7 +276,7 @@ namespace Mono.Linker { return -1; continue; - +#endif case "--explicit-reflection": if (!GetBoolParam (token, l => context.AddReflectionAnnotations = l)) return -1; @@ -280,7 +284,7 @@ namespace Mono.Linker { continue; case "--custom-step": - if (!GetStringParam (token, l => custom_steps.Add (l))) + if (!GetStringParam (token, l => custom_steps.Push (l))) return -1; continue; @@ -320,29 +324,46 @@ namespace Mono.Linker { continue; case "--disable-opt": - if (!GetStringParam (token, l => { - if (!GetOptimizationName (l, out var opt)) - return; + { + string optName = null; + if (!GetStringParam (token, l => optName = l)) + return -1; - string assemblyName = GetNextStringValue (); - set_optimizations.Add ((opt, assemblyName, false)); - })) + if (!GetOptimizationName (optName, out var opt)) return -1; - continue; + string assemblyName = GetNextStringValue (); + set_optimizations.Add ((opt, assemblyName, false)); + continue; + } case "--enable-opt": - if (!GetStringParam (token, l => { - if (!GetOptimizationName (l, out var opt)) - return; - - string assemblyName = GetNextStringValue (); - set_optimizations.Add ((opt, assemblyName, true)); - })) + { + string optName = null; + if (!GetStringParam (token, l => optName = l)) + return -1; + + if (!GetOptimizationName (optName, out var opt)) return -1; + string assemblyName = GetNextStringValue (); + set_optimizations.Add ((opt, assemblyName, true)); + continue; + } + case "--feature": + { + string featureName = null; + if (!GetStringParam (token, l => featureName = l)) + return -1; + + if (!GetBoolParam (token, value => { + context.SetFeatureValue (featureName, value); + })) + return -1; + continue; + } case "--new-mvid": // // This is not same as --deterministic which calculates MVID @@ -440,7 +461,7 @@ namespace Mono.Linker { case "x": if (!GetStringParam (token, l => { foreach (string file in GetFiles (l)) - AddResolveFromXmlStep (p, file); + resolve_from_xml_steps.Push (file); })) return -1; @@ -454,7 +475,7 @@ namespace Mono.Linker { ? ResolveFromAssemblyStep.RootVisibility.PublicAndFamily : ResolveFromAssemblyStep.RootVisibility.Any; foreach (string file in GetFiles (l)) - p.PrependStep (new ResolveFromAssemblyStep (file, rootVisibility)); + resolve_from_assembly_steps.Push ((file, rootVisibility)); })) return -1; @@ -464,7 +485,7 @@ namespace Mono.Linker { case "i": if (!GetStringParam (token, l => { foreach (string file in GetFiles (l)) - p.PrependStep (new ResolveFromXApiStep (new XPathDocument (file))); + resolve_from_xapi_steps.Push (file); })) return -1; @@ -538,6 +559,21 @@ namespace Mono.Linker { // // Modify the default pipeline // + +#if !FEATURE_ILLINK + foreach (var file in resolve_from_xapi_steps) + p.PrependStep (new ResolveFromXApiStep (new XPathDocument (file))); +#endif + + foreach (var file in resolve_from_xml_steps) + AddResolveFromXmlStep (p, file); + + foreach (var (file, rootVisibility) in resolve_from_assembly_steps) + p.PrependStep (new ResolveFromAssemblyStep (file, rootVisibility)); + + foreach (var file in body_substituter_steps) + AddBodySubstituterStep (p, file); + if (ignoreDescriptors) p.RemoveStep (typeof (BlacklistStep)); @@ -550,20 +586,17 @@ namespace Mono.Linker { #if !FEATURE_ILLINK p.AddStepAfter (typeof (LoadReferencesStep), new LoadI18nAssemblies (assemblies)); - if (assemblies != I18nAssemblies.None) { + if (assemblies != I18nAssemblies.None) p.AddStepAfter (typeof (PreserveDependencyLookupStep), new PreserveCalendarsStep (assemblies)); - } #endif - if (_needAddBypassNGenStep) { + if (_needAddBypassNGenStep) p.AddStepAfter (typeof (SweepStep), new AddBypassNGenStep ()); - } - - p.AddStepBefore (typeof (MarkStep), new BodySubstituterStep ()); if (removeCAS) p.AddStepBefore (typeof (MarkStep), new RemoveSecurityStep ()); +#if !FEATURE_ILLINK if (excluded_features.Count > 0) { p.AddStepBefore (typeof (MarkStep), new RemoveFeaturesStep () { FeatureCOM = excluded_features.Contains ("com"), @@ -576,6 +609,7 @@ namespace Mono.Linker { excluded_features.CopyTo (excluded); context.ExcludedFeatures = excluded; } +#endif p.AddStepBefore (typeof (MarkStep), new RemoveUnreachableBlocksStep ()); p.AddStepBefore (typeof (OutputStep), new ClearInitLocalsStep ()); @@ -584,13 +618,21 @@ namespace Mono.Linker { // // Pipeline setup with all steps enabled // + // ResolveFromAssemblyStep [optional, possibly many] + // ResolveFromXmlStep [optional, possibly many] + // [mono only] ResolveFromXApiStep [optional, possibly many] // LoadReferencesStep + // [mono only] LoadI18nAssemblies // BlacklistStep [optional] + // dynamically adds steps: + // ResolveFromXmlStep [optional, possibly many] + // BodySubstituterStep [optional, possibly many] // PreserveDependencyLookupStep + // [mono only] PreselveCalendarsStep [optional] // TypeMapStep // BodySubstituterStep [optional] // RemoveSecurityStep [optional] - // RemoveFeaturesStep [optional] + // [mono only] RemoveFeaturesStep [optional] // RemoveUnreachableBlocksStep [optional] // MarkStep // ReflectionBlockedStep [optional] @@ -600,6 +642,7 @@ namespace Mono.Linker { // CleanStep // RegenerateGuidStep [optional] // ClearInitLocalsStep + // SealerStep // OutputStep // @@ -651,6 +694,11 @@ namespace Mono.Linker { pipeline.PrependStep (new ResolveFromXmlStep (new XPathDocument (file), file)); } + void AddBodySubstituterStep (Pipeline pipeline, string file) + { + pipeline.AddStepBefore (typeof (MarkStep), new BodySubstituterStep (new XPathDocument (file), file)); + } + protected virtual void AddXmlDependencyRecorder (LinkContext context, string fileName) { context.Tracer.AddRecorder (new XmlDependencyRecorder (context, fileName)); @@ -952,16 +1000,17 @@ namespace Mono.Linker { Console.WriteLine (" --enable-opt NAME [ASM] Enable one of the additional optimizations globaly or for a specific assembly name"); Console.WriteLine (" clearinitlocals: Remove initlocals"); Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed"); +#if !FEATURE_ILLINK Console.WriteLine (" --exclude-feature NAME Any code which has a feature <name> in linked assemblies will be removed"); Console.WriteLine (" com: Support for COM Interop"); Console.WriteLine (" etw: Event Tracing for Windows"); -#if !FEATURE_ILLINK Console.WriteLine (" remoting: .NET Remoting dependencies"); -#endif Console.WriteLine (" sre: System.Reflection.Emit namespace"); Console.WriteLine (" globalization: Globalization data and globalization behavior"); +#endif Console.WriteLine (" --explicit-reflection Adds to members never used through reflection DisablePrivateReflection attribute. Defaults to false"); Console.WriteLine (" --keep-dep-attributes Keep attributes used for manual dependency tracking. Defaults to false"); + Console.WriteLine (" --feature FEATURE VALUE Apply any optimizations defined when this feature setting is a constant known at link time"); Console.WriteLine (" --new-mvid Generate a new guid for each linked assembly (short -g). Defaults to true"); Console.WriteLine (" --strip-resources Remove XML descriptor resources for linked assemblies. Defaults to true"); Console.WriteLine (" --strip-security Remove metadata and code related to Code Access Security. Defaults to true"); diff --git a/src/linker/Linker/LinkContext.cs b/src/linker/Linker/LinkContext.cs index d4bf02463..79c21ba32 100644 --- a/src/linker/Linker/LinkContext.cs +++ b/src/linker/Linker/LinkContext.cs @@ -118,7 +118,7 @@ namespace Mono.Linker { public bool StripResources { get; set; } - public List<string> Substitutions { get; private set; } + public Dictionary<string, bool> FeatureSettings { get; private set; } public List<string> AttributeDefinitions { get; private set; } @@ -161,8 +161,9 @@ namespace Mono.Linker { public IReflectionPatternRecorder ReflectionPatternRecorder { get; set; } +#if !FEATURE_ILLINK public string [] ExcludedFeatures { get; set; } - +#endif public CodeOptimizationsSettings Optimizations { get; set; } public bool AddReflectionAnnotations { get; set; } @@ -214,17 +215,15 @@ namespace Mono.Linker { Optimizations = new CodeOptimizationsSettings (defaultOptimizations); } - public void AddSubstitutionFile (string file) + public void SetFeatureValue (string feature, bool value) { - if (Substitutions == null) { - Substitutions = new List<string> { file }; + Debug.Assert (!String.IsNullOrEmpty (feature)); + if (FeatureSettings == null) { + FeatureSettings = new Dictionary<string, bool> { { feature, value } }; return; } - if (Substitutions.Contains (file)) - return; - - Substitutions.Add (file); + FeatureSettings [feature] = value; } public void AddAttributeDefinitionFile (string file) @@ -437,10 +436,12 @@ namespace Mono.Linker { _resolver.Dispose (); } +#if !FEATURE_ILLINK public bool IsFeatureExcluded (string featureName) { return ExcludedFeatures != null && Array.IndexOf (ExcludedFeatures, featureName) >= 0; } +#endif public bool IsOptimizationEnabled (CodeOptimizations optimization, MemberReference context) { diff --git a/src/linker/Mono.Linker.csproj b/src/linker/Mono.Linker.csproj index c7951ee91..947c270af 100644 --- a/src/linker/Mono.Linker.csproj +++ b/src/linker/Mono.Linker.csproj @@ -29,6 +29,7 @@ <Compile Remove="Linker\XApiReader.cs" /> <Compile Remove="Linker.Steps\LoadI18nAssemblies.cs" /> <Compile Remove="Linker.Steps\PreserveCalendarsStep.cs" /> + <Compile Remove="Linker.Steps\RemoveFeaturesStep.cs" /> <Compile Remove="Linker.Steps\ResolveFromXApiStep.cs" /> </ItemGroup> diff --git a/src/linker/README.md b/src/linker/README.md index 318a19946..21ad02581 100644 --- a/src/linker/README.md +++ b/src/linker/README.md @@ -116,6 +116,9 @@ An example of a substitution XML file <field name="MyNumericField" value="5" initialize="true"> </field> </type> + <type fullname="UserCode.Substitutions.Playground" feature="EnableOptionalFeature" featurevalue="false"> + <method signature="System.String UseOptionalFeature()" body="remove" /> + </type> </assembly> </linker> ``` @@ -132,6 +135,11 @@ value and override the existing behaviour. The rule can also apply to static fie if set to default value without explicit `initialize` setting could help to elide whole explicit static constructor. +The `feature` and `featurevalue` attributes are optional, but must be used together if they are used. +They can be applied to any other element to specify conditions under which the contained substitutions +are applied. For example, the above substitution for `UseOptionalFeature` will be applied only if +`--feature EnableOptionalFeature false` is passed to the linker. + ### Adding custom steps to the linker. You can write custom steps for the linker and tell the linker to use them. diff --git a/test/ILLink.Tasks.Tests/ILLink.Tasks.Tests.cs b/test/ILLink.Tasks.Tests/ILLink.Tasks.Tests.cs index 44266efb7..bbae3a027 100644 --- a/test/ILLink.Tasks.Tests/ILLink.Tasks.Tests.cs +++ b/test/ILLink.Tasks.Tests/ILLink.Tasks.Tests.cs @@ -304,6 +304,52 @@ namespace ILLink.Tasks.Tests } } + public static IEnumerable<object []> FeatureSettingsCases => new List<object []> { + new object [] { + new ITaskItem [] { + new TaskItem ("FeatureName", new Dictionary<string, string> { { "Value", "true" } }) + }, + }, + new object [] { + new ITaskItem [] { + new TaskItem ("FeatureName", new Dictionary<string, string> { { "Value", "true" } }), + new TaskItem ("FeatureName", new Dictionary<string, string> { { "Value", "false" } }) + }, + }, + new object [] { + new ITaskItem [] { + new TaskItem ("FeatureName1", new Dictionary<string, string> { { "value", "true" } }), + new TaskItem ("FeatureName2", new Dictionary<string, string> { { "value", "false" } }), + }, + }, + }; + + [Theory] + [MemberData (nameof (FeatureSettingsCases))] + public void TestFeatureSettings (ITaskItem [] featureSettings) + { + var task = new MockTask () { + FeatureSettings = featureSettings + }; + using (var driver = task.CreateDriver ()) { + var expectedSettings = featureSettings.Select (f => new { Feature = f.ItemSpec, Value = f.GetMetadata ("Value") }) + .GroupBy (f => f.Feature) + .Select (f => f.Last()) + .ToDictionary (f => f.Feature, f => bool.Parse(f.Value)); + var actualSettings = driver.Context.FeatureSettings; + Assert.Equal (expectedSettings, actualSettings); + } + } + + [Fact] + public void TestInvalidFeatureSettings () + { + var task = new MockTask () { + FeatureSettings = new ITaskItem [] { new TaskItem ("FeatureName") } + }; + Assert.Throws <ArgumentException> (() => task.CreateDriver ()); + } + [Fact] public void TestExtraArgs () { diff --git a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/ComAttributesAreRemovedWhenFeatureExcluded.cs b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/ComAttributesAreRemovedWhenFeatureExcluded.cs index ca83503d4..0fd0317f7 100644 --- a/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/ComAttributesAreRemovedWhenFeatureExcluded.cs +++ b/test/Mono.Linker.Tests.Cases/Attributes/OnlyKeepUsed/ComAttributesAreRemovedWhenFeatureExcluded.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes.OnlyKeepUsed { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--used-attrs-only", "true")] [SetupLinkerArgument ("--exclude-feature", "com")] public class ComAttributesAreRemovedWhenFeatureExcluded { diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSource.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSource.cs index 38cf2da9d..868ac6d35 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSource.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSource.cs @@ -4,6 +4,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSourceEmptyBody.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSourceEmptyBody.cs index c36fa18ab..05fc018c6 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSourceEmptyBody.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/BaseRemovedEventSourceEmptyBody.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/Excluded.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/Excluded.cs index 0568f7a3d..59be7a22d 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/Excluded.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/Excluded.cs @@ -4,6 +4,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/LocalsOfModifiedMethodAreRemoved.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/LocalsOfModifiedMethodAreRemoved.cs index 97af4deaf..06ba2b79f 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/LocalsOfModifiedMethodAreRemoved.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/LocalsOfModifiedMethodAreRemoved.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/NonEventWithLog.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/NonEventWithLog.cs index 5b0b82f18..e6f76bf95 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/NonEventWithLog.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/NonEventWithLog.cs @@ -4,6 +4,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Used to avoid different compilers generating different IL which can mess up the instruction asserts [SetupCompileArgument ("/optimize+")] diff --git a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/StubbedMethodWithExceptionHandlers.cs b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/StubbedMethodWithExceptionHandlers.cs index 7d6cb22c8..68ca8f37c 100644 --- a/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/StubbedMethodWithExceptionHandlers.cs +++ b/test/Mono.Linker.Tests.Cases/BCLFeatures/ETW/StubbedMethodWithExceptionHandlers.cs @@ -4,6 +4,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.BCLFeatures.ETW { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] diff --git a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemovedWhenComFeatureExcluded.cs b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemovedWhenComFeatureExcluded.cs index de8908678..597da0c7b 100644 --- a/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemovedWhenComFeatureExcluded.cs +++ b/test/Mono.Linker.Tests.Cases/Inheritance.Interfaces/OnReferenceType/UnusedComInterfaceIsRemovedWhenComFeatureExcluded.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Inheritance.Interfaces.OnReferenceType { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "com")] public class UnusedComInterfaceIsRemovedWhenComFeatureExcluded { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExcludedFeatureCom.cs b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExcludedFeatureCom.cs index 0016d7272..df2aeb29f 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExcludedFeatureCom.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/CanPreserveExcludedFeatureCom.cs @@ -4,6 +4,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "com")] public class CanPreserveExcludedFeatureCom { public static void Main() diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnAssembly.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnAssembly.cs index cbdfb5509..59d546a15 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnAssembly.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnAssembly.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Metadata; using Mono.Linker.Tests.Cases.LinkXml.FeatureExclude.Dependencies; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] [SetupCompileBefore ("library1.dll", new[] {typeof (OnAssembly_Lib1)})] [SetupCompileBefore ("library2.dll", new[] {typeof (OnAssembly_Lib2)})] diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnEvent.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnEvent.cs index e733ab2d1..2cf664c5e 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnEvent.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnEvent.cs @@ -3,6 +3,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] public class OnEvent { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnField.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnField.cs index 93a61f0ab..466be6199 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnField.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnField.cs @@ -2,6 +2,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] public class OnField { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnMethod.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnMethod.cs index 11feb7a6a..30741f2a2 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnMethod.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnMethod.cs @@ -2,6 +2,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] public class OnMethod { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnProperty.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnProperty.cs index eb043fb01..c550c605c 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnProperty.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnProperty.cs @@ -2,6 +2,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] public class OnProperty { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnType.cs b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnType.cs index 368812fcc..731f54080 100644 --- a/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnType.cs +++ b/test/Mono.Linker.Tests.Cases/LinkXml/FeatureExclude/OnType.cs @@ -2,6 +2,9 @@ using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.LinkXml.FeatureExclude { +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "one")] public class OnType { public static void Main () diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.cs b/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.cs new file mode 100644 index 000000000..479be2d6f --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.cs @@ -0,0 +1,25 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupCompileResource ("EmbeddedSubstitutions.xml", "ILLink.Substitutions.xml")] + [IncludeBlacklistStep (true)] + public class EmbeddedSubstitutions + { + public static void Main () + { + ConvertToThrowMethod (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldstr", + "newobj", + "throw" + })] + public static void ConvertToThrowMethod () + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.xml b/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.xml new file mode 100644 index 000000000..3bd0175a6 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/EmbeddedSubstitutions.xml @@ -0,0 +1,7 @@ +<linker> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.EmbeddedSubstitutions"> + <method signature="System.Void ConvertToThrowMethod()" body="remove" /> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.cs b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.cs new file mode 100644 index 000000000..980451da4 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.cs @@ -0,0 +1,51 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("FeatureSubstitutions.xml")] + [SetupLinkerArgument ("--feature", "OptionalFeature", "false")] + public class FeatureSubstitutions + { + [Kept] + static bool IsOptionalFeatureEnabled { + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + get; + } + + public static void Main () + { + TestOptionalFeature (); + } + + [Kept] + [ExpectBodyModified] + [ExpectedInstructionSequence (new [] { + "call", + "brfalse", + "call", + "ret", + })] + static void TestOptionalFeature () + { + if (IsOptionalFeatureEnabled) { + UseOptionalFeature (); + } else { + UseFallback (); + } + } + + static void UseOptionalFeature () + { + } + + [Kept] + static void UseFallback () + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.xml b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.xml new file mode 100644 index 000000000..44d332d75 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutions.xml @@ -0,0 +1,8 @@ +<linker feature="OptionalFeature" featurevalue="false"> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutions"> + <method signature="System.Boolean get_IsOptionalFeatureEnabled()" body="stub" value="false"> + </method> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalFalse.xml b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalFalse.xml new file mode 100644 index 000000000..a9452db38 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalFalse.xml @@ -0,0 +1,9 @@ +<!-- Check that the feature attribute can be used on the linker element. --> +<linker feature="GlobalCondition" featurevalue="false"> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureInAssembly"> + <method signature="System.Boolean GlobalConditionMethod()" body="stub" value="false"> + </method> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalTrue.xml b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalTrue.xml new file mode 100644 index 000000000..3b8d006f9 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsGlobalTrue.xml @@ -0,0 +1,9 @@ +<!-- Check that the feature attribute can be used on the linker element. --> +<linker feature="GlobalCondition" featurevalue="true"> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsNested"> + <method signature="System.Boolean GlobalConditionMethod()" body="stub" value="true"> + </method> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.cs b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.cs new file mode 100644 index 000000000..73ba6acbc --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.cs @@ -0,0 +1,33 @@ +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("FeatureSubstitutionsInvalid.xml")] + [SetupLinkerArgument ("--feature", "NoValueFeature", "true")] + [LogContains ("Feature NoValueFeature does not specify a \"featurevalue\" attribute")] + public class FeatureSubstitutionsInvalid + { + public static void Main () + { + NoValueFeatureMethod (); + NonBooleanFeatureMethod (); + BooleanFeatureMethod (); + } + + [Kept] + static void NoValueFeatureMethod () + { + } + + [Kept] + static void NonBooleanFeatureMethod () + { + } + + [Kept] + static void BooleanFeatureMethod () + { + } + } +} diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.xml b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.xml new file mode 100644 index 000000000..9a0ed8a39 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsInvalid.xml @@ -0,0 +1,9 @@ +<linker> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsInvalid"> + <method signature="System.Boolean NoValueFeatureMethod()" body="stub" value="true" feature="NoValueFeature" /> + <method signature="System.Boolean NonBooleanFeatureMethod()" body="stub" value="false" feature="NonBooleanFeature" featurevalue="nonboolean" /> + <method signature="System.Boolean BooleanFeatureMethod()" body="stub" value="false" feature="BooleanFeature" featurevalue="true" /> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.cs b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.cs new file mode 100644 index 000000000..5e124ee48 --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.cs @@ -0,0 +1,75 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [SetupLinkerSubstitutionFile ("FeatureSubstitutionsGlobalTrue.xml")] + [SetupLinkerSubstitutionFile ("FeatureSubstitutionsGlobalFalse.xml")] + [SetupLinkerSubstitutionFile ("FeatureSubstitutionsNested.xml")] + [SetupLinkerArgument ("--feature", "GlobalCondition", "true")] + [SetupLinkerArgument ("--feature", "AssemblyCondition", "false")] + [SetupLinkerArgument ("--feature", "TypeCondition", "true")] + [SetupLinkerArgument ("--feature", "MethodCondition", "false")] + [SetupLinkerArgument ("--feature", "FieldCondition", "true")] + public class FeatureSubstitutionsNested + { + public static void Main () { + GlobalConditionMethod (); + AssemblyConditionMethod (); + TypeConditionMethod (); + MethodConditionMethod (); + _ = FieldConditionField; + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.1", + "ret", + })] + static bool GlobalConditionMethod () { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + static bool AssemblyConditionMethod () { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.1", + "ret", + })] + static bool TypeConditionMethod () { + throw new NotImplementedException (); + } + + [Kept] + [ExpectedInstructionSequence (new [] { + "ldc.i4.0", + "ret", + })] + static bool MethodConditionMethod () { + throw new NotImplementedException (); + } + + [Kept] + static readonly bool FieldConditionField; + + [Kept] + [ExpectedInstructionSequence (new [] { + "nop", + "ldc.i4.1", + "stsfld", + "ret" + })] + static FeatureSubstitutionsNested () + { + } + } +}
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.xml b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.xml new file mode 100644 index 000000000..122bea08d --- /dev/null +++ b/test/Mono.Linker.Tests.Cases/Substitutions/FeatureSubstitutionsNested.xml @@ -0,0 +1,32 @@ +<linker> + <!-- Check that the feature attribute can be used on the assembly element. --> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" feature="AssemblyCondition" featurevalue="false"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsNested"> + <method signature="System.Boolean AssemblyConditionMethod()" body="stub" value="false" /> + </type> + <!-- Or on the type element. --> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsNested" feature="TypeCondition" featurevalue="true"> + <method signature="System.Boolean TypeConditionMethod()" body="stub" value="true"> + </method> + <!-- Or on the method element. --> + <method signature="System.Boolean MethodConditionMethod()" body="stub" value="false" feature="MethodCondition" featurevalue="false" /> + <!-- Else case --> + <method signature="System.Boolean MethodConditionMethod()" body="stub" value="true" feature="MethodCondition" featurevalue="true" /> + <!-- Or on the field element. --> + <field name="FieldConditionField" value="true" initialize="true" feature="FieldCondition" featurevalue="true" /> + <!-- Else case --> + <field name="FieldConditionField" value="false" initialize="true" feature="FieldCondition" featurevalue="false" /> + </type> + <!-- Else case for the type feature attribute --> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsNested" feature="TypeCondition" featurevalue="false"> + <method signature="System.Boolean TypeConditionMethod()" body="stub" value="false"> + </method> + </type> + </assembly> + <!-- Else case for the assembly feature attribute --> + <assembly fullname="test, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" feature="AssemblyCondition" featurevalue="true"> + <type fullname="Mono.Linker.Tests.Cases.Substitutions.FeatureSubstitutionsNested"> + <method signature="System.Boolean AssemblyConditionMethod()" body="stub" value="true" /> + </type> + </assembly> +</linker>
\ No newline at end of file diff --git a/test/Mono.Linker.Tests.Cases/TestFramework/VerifyExpectModifiedAttributesWork.cs b/test/Mono.Linker.Tests.Cases/TestFramework/VerifyExpectModifiedAttributesWork.cs index 3b3a7f015..787c5179e 100644 --- a/test/Mono.Linker.Tests.Cases/TestFramework/VerifyExpectModifiedAttributesWork.cs +++ b/test/Mono.Linker.Tests.Cases/TestFramework/VerifyExpectModifiedAttributesWork.cs @@ -8,6 +8,9 @@ namespace Mono.Linker.Tests.Cases.TestFramework { /// This test is here to give some coverage to the attribute to ensure it doesn't break. We need to leverage the ETW feature since it is the only /// one that modifies bodies currently /// </summary> +#if NETCOREAPP + [IgnoreTestCase ("--exclude-feature is not supported on .NET Core")] +#endif [SetupLinkerArgument ("--exclude-feature", "etw")] // Keep framework code that calls EventSource methods like OnEventCommand [SetupLinkerCoreAction ("skip")] |