diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutionProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutionProvider.cs index 71c6008ba4f50..3530f2258d605 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutionProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutionProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Resources; @@ -33,11 +34,84 @@ public BodySubstitution GetSubstitution(MethodDesc method) AssemblyFeatureInfo info = _hashtable.GetOrCreateValue(ecmaMethod.Module); if (info.BodySubstitutions != null && info.BodySubstitutions.TryGetValue(ecmaMethod, out BodySubstitution result)) return result; + + if (TryGetFeatureCheckValue(ecmaMethod, out bool value)) + return BodySubstitution.Create(value ? 1 : 0); } return null; } + private bool TryGetFeatureCheckValue(EcmaMethod method, out bool value) + { + value = false; + + if (!method.Signature.IsStatic) + return false; + + if (!method.Signature.ReturnType.IsWellKnownType(WellKnownType.Boolean)) + return false; + + if (FindProperty(method) is not PropertyPseudoDesc property) + return false; + + if (property.SetMethod != null) + return false; + + foreach (var featureSwitchDefinitionAttribute in property.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "FeatureSwitchDefinitionAttribute")) + { + if (featureSwitchDefinitionAttribute.FixedArguments is not [CustomAttributeTypedArgument { Value: string switchName }]) + continue; + + // If there's a FeatureSwitchDefinition, don't continue looking for FeatureGuard. + // We don't want to infer feature switch settings from FeatureGuard. + return _hashtable._switchValues.TryGetValue(switchName, out value); + } + + foreach (var featureGuardAttribute in property.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "FeatureGuardAttribute")) + { + if (featureGuardAttribute.FixedArguments is not [CustomAttributeTypedArgument { Value: EcmaType featureType }]) + continue; + + if (featureType.Namespace == "System.Diagnostics.CodeAnalysis") { + switch (featureType.Name) { + case "RequiresAssemblyFilesAttribute": + case "RequiresUnreferencedCodeAttribute": + return true; + case "RequiresDynamicCodeAttribute": + if (_hashtable._switchValues.TryGetValue( + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", + out bool isDynamicCodeSupported) + && !isDynamicCodeSupported) + return true; + break; + } + } + } + + return false; + + static PropertyPseudoDesc FindProperty(EcmaMethod method) + { + if ((method.Attributes & MethodAttributes.SpecialName) == 0) + return null; + + if (method.OwningType is not EcmaType declaringType) + return null; + + var reader = declaringType.MetadataReader; + foreach (PropertyDefinitionHandle propertyHandle in reader.GetTypeDefinition(declaringType.Handle).GetProperties()) + { + PropertyDefinition propertyDef = reader.GetPropertyDefinition(propertyHandle); + var property = new PropertyPseudoDesc(declaringType, propertyHandle); + if (property.GetMethod == method) + return property; + } + + return null; + } + } + public object GetSubstitution(FieldDesc field) { if (field.GetTypicalFieldDefinition() is EcmaField ecmaField) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestDatabase.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestDatabase.cs index 488a283b043db..f742d409d9b75 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestDatabase.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestDatabase.cs @@ -69,6 +69,11 @@ public static IEnumerable SingleFile () return TestNamesBySuiteName (); } + public static IEnumerable Substitutions () + { + return TestNamesBySuiteName (); + } + public static IEnumerable TopLevelStatements () { return TestNamesBySuiteName (); diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs index 63751a6233d52..d4156edab60cf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCases/TestSuites.cs @@ -103,6 +103,20 @@ public void SingleFile (string t) Run (t); } + [Theory] + [MemberData (nameof (TestDatabase.Substitutions), MemberType = typeof (TestDatabase))] + public void Substitutions (string t) + { + switch (t) { + case "FeatureGuardSubstitutions": + Run (t); + break; + default: + // Skip the rest for now + break; + } + } + [Theory] [MemberData (nameof (TestDatabase.TopLevelStatements), MemberType = typeof (TestDatabase))] public void TopLevelStatements (string t) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs index 55ecba89fb015..3f1903ba12cee 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs @@ -74,8 +74,8 @@ internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypes internal static ValueSet GetFeatureGuardAnnotations (this IPropertySymbol propertySymbol) { HashSet featureSet = new (); - foreach (var attributeData in propertySymbol.GetAttributes (DynamicallyAccessedMembersAnalyzer.FullyQualifiedFeatureGuardAttribute)) { - if (attributeData.ConstructorArguments is [TypedConstant { Value: INamedTypeSymbol featureType }]) + foreach (var featureGuardAttribute in propertySymbol.GetAttributes (DynamicallyAccessedMembersAnalyzer.FullyQualifiedFeatureGuardAttribute)) { + if (featureGuardAttribute.ConstructorArguments is [TypedConstant { Value: INamedTypeSymbol featureType }]) featureSet.Add (featureType.GetDisplayName ()); } return featureSet.Count == 0 ? ValueSet.Empty : new ValueSet (featureSet); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index da5747c9888e5..c31a69a24d24b 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -305,7 +305,7 @@ protected virtual bool CreateSpecialIncompatibleMembersDiagnostic ( internal static bool IsAnnotatedFeatureGuard (IPropertySymbol propertySymbol, string featureName) { // Only respect FeatureGuardAttribute on static boolean properties. - if (!propertySymbol.IsStatic || propertySymbol.Type.SpecialType != SpecialType.System_Boolean) + if (!propertySymbol.IsStatic || propertySymbol.Type.SpecialType != SpecialType.System_Boolean || propertySymbol.SetMethod != null) return false; ValueSet featureCheckAnnotations = propertySymbol.GetFeatureGuardAnnotations (); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs index 1428b8cb4dbf8..fe81dbb79ec39 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs @@ -36,8 +36,8 @@ public IEnumerable CollectDiagnostics (DataFlowAnalyzerContext conte if (!context.EnableTrimAnalyzer) return diagnosticContext.Diagnostics; - if (!OwningSymbol.IsStatic || OwningSymbol.Type.SpecialType != SpecialType.System_Boolean) { - // Warn about invalid feature checks (non-static or non-bool properties) + if (!OwningSymbol.IsStatic || OwningSymbol.Type.SpecialType != SpecialType.System_Boolean || OwningSymbol.SetMethod != null) { + // Warn about invalid feature checks (non-static or non-bool properties or properties with setter) diagnosticContext.AddDiagnostic ( DiagnosticId.InvalidFeatureGuard); return diagnosticContext.Diagnostics; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs index 7c9b1bbfaf957..9db61498b28ce 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs @@ -43,8 +43,6 @@ public class TrimAnalysisVisitor : LocalDataFlowVisitor< FeatureChecksVisitor _featureChecksVisitor; - DataFlowAnalyzerContext _dataFlowAnalyzerContext; - public TrimAnalysisVisitor ( Compilation compilation, LocalStateAndContextLattice, FeatureContextLattice> lattice, @@ -59,7 +57,6 @@ public TrimAnalysisVisitor ( _multiValueLattice = lattice.LocalStateLattice.Lattice.ValueLattice; TrimAnalysisPatterns = trimAnalysisPatterns; _featureChecksVisitor = new FeatureChecksVisitor (dataFlowAnalyzerContext); - _dataFlowAnalyzerContext = dataFlowAnalyzerContext; } public override FeatureChecksValue GetConditionValue (IOperation branchValueOperation, StateValue state) @@ -436,7 +433,8 @@ public override void HandleReturnConditionValue (FeatureChecksValue returnCondit if (OwningSymbol is not IMethodSymbol method) return; - // FeatureGuard validation needs to happen only for properties. + // FeatureGuard validation needs to happen only for property getters. + // Include properties with setters here because they will get validated later. if (method.MethodKind != MethodKind.PropertyGet) return; diff --git a/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs b/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs index d29432e8b60ce..b167ed5f58be2 100644 --- a/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/RootAssemblyInputStep.cs @@ -78,7 +78,8 @@ protected override void Process () CodeOptimizations.RemoveLinkAttributes | CodeOptimizations.RemoveSubstitutions | CodeOptimizations.RemoveDynamicDependencyAttribute | - CodeOptimizations.OptimizeTypeHierarchyAnnotations, assembly.Name.Name); + CodeOptimizations.OptimizeTypeHierarchyAnnotations | + CodeOptimizations.SubstituteFeatureGuards, assembly.Name.Name); // Enable EventSource special handling Context.DisableEventSourceSpecialHandling = false; diff --git a/src/tools/illink/src/linker/Linker/CustomAttributeSource.cs b/src/tools/illink/src/linker/Linker/CustomAttributeSource.cs index f11419de4b4b2..f32e93a078579 100644 --- a/src/tools/illink/src/linker/Linker/CustomAttributeSource.cs +++ b/src/tools/illink/src/linker/Linker/CustomAttributeSource.cs @@ -52,6 +52,14 @@ public bool TryGetEmbeddedXmlInfo (ICustomAttributeProvider provider, [NotNullWh return xmlInfo != null; } + public IEnumerable GetCustomAttributes (ICustomAttributeProvider provider, string attributeNamespace, string attributeName) + { + foreach (var attr in GetCustomAttributes (provider)) { + if (attr.AttributeType.Namespace == attributeNamespace && attr.AttributeType.Name == attributeName) + yield return attr; + } + } + public IEnumerable GetCustomAttributes (ICustomAttributeProvider provider) { if (provider.HasCustomAttributes) { diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs index 742ee2140b9ce..de99303f12b7f 100644 --- a/src/tools/illink/src/linker/Linker/Driver.cs +++ b/src/tools/illink/src/linker/Linker/Driver.cs @@ -1179,6 +1179,9 @@ protected bool GetOptimizationName (string text, out CodeOptimizations optimizat case "sealer": optimization = CodeOptimizations.Sealer; return true; + case "substitutefeatureguards": + optimization = CodeOptimizations.SubstituteFeatureGuards; + return true; } Context.LogError (null, DiagnosticId.InvalidOptimizationValue, text); @@ -1361,6 +1364,7 @@ static void Usage () Console.WriteLine (" unreachablebodies: Instance methods that are marked but not executed are converted to throws"); Console.WriteLine (" unusedinterfaces: Removes interface types from declaration when not used"); Console.WriteLine (" unusedtypechecks: Inlines never successful type checks"); + Console.WriteLine (" substitutefeatureguards: Substitutes properties annotated as FeatureGuard(typeof(RequiresUnreferencedCodeAttribute)) to false"); Console.WriteLine (" --enable-opt NAME [ASM] Enable one of the additional optimizations globaly or for a specific assembly name"); Console.WriteLine (" sealer: Any method or type which does not have override is marked as sealed"); Console.WriteLine (" --explicit-reflection Adds to members never used through reflection DisablePrivateReflection attribute. Defaults to false"); diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs index 41fcba1a6f05b..43d6f994fd075 100644 --- a/src/tools/illink/src/linker/Linker/LinkContext.cs +++ b/src/tools/illink/src/linker/Linker/LinkContext.cs @@ -246,7 +246,8 @@ protected LinkContext (Pipeline pipeline, ILogger logger, string outputDirectory CodeOptimizations.RemoveLinkAttributes | CodeOptimizations.RemoveSubstitutions | CodeOptimizations.RemoveDynamicDependencyAttribute | - CodeOptimizations.OptimizeTypeHierarchyAnnotations; + CodeOptimizations.OptimizeTypeHierarchyAnnotations | + CodeOptimizations.SubstituteFeatureGuards; DisableEventSourceSpecialHandling = true; @@ -1144,5 +1145,10 @@ public enum CodeOptimizations /// Otherwise, type annotation will only be applied with calls to object.GetType() /// OptimizeTypeHierarchyAnnotations = 1 << 24, + + /// + /// Option to substitute properties annotated as FeatureGuard(typeof(RequiresUnreferencedCodeAttribute)) with false + /// + SubstituteFeatureGuards = 1 << 25, } } diff --git a/src/tools/illink/src/linker/Linker/MemberActionStore.cs b/src/tools/illink/src/linker/Linker/MemberActionStore.cs index 84f2cfd7fb467..51d792fc45b40 100644 --- a/src/tools/illink/src/linker/Linker/MemberActionStore.cs +++ b/src/tools/illink/src/linker/Linker/MemberActionStore.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using ILLink.Shared; using Mono.Cecil; namespace Mono.Linker @@ -20,7 +22,7 @@ public MemberActionStore (LinkContext context) _context = context; } - public bool TryGetSubstitutionInfo (MemberReference member, [NotNullWhen (true)] out SubstitutionInfo? xmlInfo) + private bool TryGetSubstitutionInfo (MemberReference member, [NotNullWhen (true)] out SubstitutionInfo? xmlInfo) { var assembly = member.Module.Assembly; if (!_embeddedXmlInfos.TryGetValue (assembly, out xmlInfo)) { @@ -41,6 +43,9 @@ public MethodAction GetAction (MethodDefinition method) return action; } + if (TryGetFeatureCheckValue (method, out _)) + return MethodAction.ConvertToStub; + return MethodAction.Nothing; } @@ -49,10 +54,78 @@ public bool TryGetMethodStubValue (MethodDefinition method, out object? value) if (PrimarySubstitutionInfo.MethodStubValues.TryGetValue (method, out value)) return true; - if (!TryGetSubstitutionInfo (method, out var embeddedXml)) + if (TryGetSubstitutionInfo (method, out var embeddedXml) + && embeddedXml.MethodStubValues.TryGetValue (method, out value)) + return true; + + if (TryGetFeatureCheckValue (method, out bool bValue)) { + value = bValue ? 1 : 0; + return true; + } + + return false; + } + + internal bool TryGetFeatureCheckValue (MethodDefinition method, out bool value) + { + value = false; + + if (!method.IsStatic) + return false; + + if (method.ReturnType.MetadataType != MetadataType.Boolean) + return false; + + if (FindProperty (method) is not PropertyDefinition property) return false; - return embeddedXml.MethodStubValues.TryGetValue (method, out value); + if (property.SetMethod != null) + return false; + + foreach (var featureSwitchDefinitionAttribute in _context.CustomAttributes.GetCustomAttributes (property, "System.Diagnostics.CodeAnalysis", "FeatureSwitchDefinitionAttribute")) { + if (featureSwitchDefinitionAttribute.ConstructorArguments is not [CustomAttributeArgument { Value: string switchName }]) + continue; + + // If there's a FeatureSwitchDefinition, don't continue looking for FeatureGuard. + // We don't want to infer feature switch settings from FeatureGuard. + return _context.FeatureSettings.TryGetValue (switchName, out value); + } + + if (!_context.IsOptimizationEnabled (CodeOptimizations.SubstituteFeatureGuards, method)) + return false; + + foreach (var featureGuardAttribute in _context.CustomAttributes.GetCustomAttributes (property, "System.Diagnostics.CodeAnalysis", "FeatureGuardAttribute")) { + if (featureGuardAttribute.ConstructorArguments is not [CustomAttributeArgument { Value: TypeReference featureType }]) + continue; + + if (featureType.Namespace == "System.Diagnostics.CodeAnalysis") { + switch (featureType.Name) { + case "RequiresUnreferencedCodeAttribute": + return true; + case "RequiresDynamicCodeAttribute": + if (_context.FeatureSettings.TryGetValue ( + "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", + out bool isDynamicCodeSupported) + && !isDynamicCodeSupported) + return true; + break; + } + } + } + + return false; + + static PropertyDefinition? FindProperty (MethodDefinition method) { + if (!method.IsGetter) + return null; + + foreach (var property in method.DeclaringType.Properties) { + if (property.GetMethod == method) + return property; + } + + return null; + } } public bool TryGetFieldUserValue (FieldDefinition field, out object? value) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/SubstitutionsTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/SubstitutionsTests.cs new file mode 100644 index 0000000000000..551284ef38a7b --- /dev/null +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/SubstitutionsTests.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace ILLink.RoslynAnalyzer.Tests +{ + public sealed partial class SubstitutionsTests : LinkerTestBase + { + protected override string TestSuiteName => "Substitutions"; + + [Fact] + public Task FeatureGuardSubstitutions () + { + return RunTest (); + } + } +} diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/SubstitutionsTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/SubstitutionsTests.g.cs index 1dd1a52a1a993..2e3c9ca388416 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/SubstitutionsTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/SubstitutionsTests.g.cs @@ -7,8 +7,6 @@ namespace ILLink.RoslynAnalyzer.Tests public sealed partial class SubstitutionsTests : LinkerTestBase { - protected override string TestSuiteName => "Substitutions"; - [Fact] public Task EmbeddedFieldSubstitutionsInReferencedAssembly () { @@ -45,6 +43,12 @@ public Task EmbeddedSubstitutionsNotProcessedWithIgnoreSubstitutionsAndRemoved ( return RunTest (allowMissingWarnings: true); } + [Fact] + public Task FeatureGuardSubstitutionsDisabled () + { + return RunTest (allowMissingWarnings: true); + } + [Fact] public Task InitField () { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/FeatureSwitchDefinitionAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/FeatureSwitchDefinitionAttribute.cs new file mode 100644 index 0000000000000..71b030ab299f6 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/FeatureSwitchDefinitionAttribute.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Property, Inherited = false)] + public sealed class FeatureSwitchDefinitionAttribute : Attribute + { + public string SwitchName { get; } + + public FeatureSwitchDefinitionAttribute (string switchName) + { + SwitchName = switchName; + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlow.cs index 9e300339e4cb9..60aa4f18a0662 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlow.cs @@ -14,140 +14,16 @@ namespace Mono.Linker.Tests.Cases.DataFlow { [SkipKeptItemsValidation] [ExpectedNoWarnings] - // Note: the XML must be passed as an embedded resource named ILLink.Substitutions.xml, - // not as a separate substitution file, for it to work with NativeAot. - // Related: https://github.com/dotnet/runtime/issues/88647 - [SetupCompileBefore ("TestFeatures.dll", new[] { "Dependencies/TestFeatures.cs" }, - resources: new object[] { new [] { "FeatureCheckDataFlowTestSubstitutions.xml", "ILLink.Substitutions.xml" } })] - // FeatureGuardAttribute is currently only supported by the analyzer. - // The same guard behavior is achieved for ILLink/ILCompiler using substitutions. - [SetupCompileResource ("FeatureGuardAttributeDataFlowTestSubstitutions.xml", "ILLink.Substitutions.xml")] - [IgnoreSubstitutions (false)] + [SetupCompileBefore ("TestFeatures.dll", new[] { "Dependencies/TestFeatures.cs" })] public class FeatureGuardAttributeDataFlow { public static void Main () { - DefineFeatureGuard.Test (); ValidGuardBodies.Test (); InvalidGuardBodies.Test (); InvalidFeatureGuards.Test (); } - class DefineFeatureGuard { - [FeatureGuard (typeof(RequiresDynamicCodeAttribute))] - static bool GuardDynamicCode => RuntimeFeature.IsDynamicCodeSupported; - - static void TestGuardDynamicCode () - { - if (GuardDynamicCode) - RequiresDynamicCode (); - } - - [FeatureGuard (typeof(RequiresUnreferencedCodeAttribute))] - static bool GuardUnreferencedCode => TestFeatures.IsUnreferencedCodeSupported; - - static void TestGuardUnreferencedCode () - { - if (GuardUnreferencedCode) - RequiresUnreferencedCode (); - } - - [FeatureGuard (typeof(RequiresAssemblyFilesAttribute))] - static bool GuardAssemblyFiles => TestFeatures.IsAssemblyFilesSupported; - - static void TestGuardAssemblyFiles () - { - if (GuardAssemblyFiles) - RequiresAssemblyFiles (); - } - - [ExpectedWarning ("IL4000", nameof (RequiresDynamicCodeAttribute), ProducedBy = Tool.Analyzer)] - [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] - static bool GuardDynamicCodeAndUnreferencedCode => RuntimeFeature.IsDynamicCodeSupported && TestFeatures.IsUnreferencedCodeSupported; - - static void TestMultipleGuards () - { - if (GuardDynamicCodeAndUnreferencedCode) { - RequiresDynamicCode (); - RequiresUnreferencedCode (); - } - } - - static class DynamicCode1 { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => RuntimeFeature.IsDynamicCodeSupported; - } - - static class DynamicCode2 { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => DynamicCode1.IsSupported; - } - - // Currently there is no way to annotate a feature type as depending on another feature, - // so indirect guards are expressed the same way as direct guards, by using - // FeatureGuardAttribute that references the underlying feature type. - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - static bool GuardDynamicCodeIndirect => DynamicCode2.IsSupported; - - static void TestIndirectGuard () - { - if (GuardDynamicCodeIndirect) - RequiresDynamicCode (); - } - - static class DynamicCodeCycle { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => DynamicCodeCycle.IsSupported; - } - - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - static bool GuardDynamicCodeCycle => DynamicCodeCycle.IsSupported; - - [FeatureGuard (typeof (DynamicCodeCycle))] - static void TestFeatureDependencyCycle1 () - { - if (GuardDynamicCodeCycle) - RequiresDynamicCode (); - } - - static class DynamicCodeCycle2_A { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => DynamicCodeCycle2_B.IsSupported; - } - - static class DynamicCodeCycle2_B { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => DynamicCodeCycle2_A.IsSupported; - } - - static class DynamicCodeCycle2 { - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - public static bool IsSupported => DynamicCodeCycle2_A.IsSupported; - } - - [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] - static bool GuardDynamicCodeCycle2 => DynamicCodeCycle2.IsSupported; - - static void TestFeatureDependencyCycle2 () - { - if (GuardDynamicCodeCycle2) - RequiresDynamicCode (); - } - - public static void Test () - { - TestGuardDynamicCode (); - TestGuardUnreferencedCode (); - TestGuardAssemblyFiles (); - TestMultipleGuards (); - TestIndirectGuard (); - TestFeatureDependencyCycle1 (); - TestFeatureDependencyCycle2 (); - } - } - class ValidGuardBodies { [FeatureGuard (typeof(RequiresUnreferencedCodeAttribute))] @@ -590,6 +466,27 @@ static void TestNonStaticProperty () RequiresUnreferencedCode (); } + [FeatureGuard (typeof(RequiresUnreferencedCodeAttribute))] + static bool SetOnlyProperty { set => throw null; } + + [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCodeAttribute))] + static void TestSetOnlyProperty () + { + if (SetOnlyProperty = true) + RequiresUnreferencedCode (); + } + + [ExpectedWarning ("IL4001", ProducedBy = Tool.Analyzer)] + [FeatureGuard (typeof(RequiresUnreferencedCodeAttribute))] + static bool GetAndSetProperty { get => true; set => throw null; } + + [ExpectedWarning ("IL2026", nameof (RequiresUnreferencedCodeAttribute))] + static void TestGetAndSetProperty () + { + if (GetAndSetProperty) + RequiresUnreferencedCode (); + } + // No warning for this case because we don't validate that the attribute usage matches // the expected AttributeUsage.Property for assemblies that define their own version // of FeatureGuardAttribute. @@ -607,19 +504,15 @@ public static void Test () { TestNonBooleanProperty (); TestNonStaticProperty (); + TestSetOnlyProperty (); + TestGetAndSetProperty (); TestMethod (); } } - [RequiresDynamicCode (nameof (RequiresDynamicCode))] - static void RequiresDynamicCode () { } - [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] static void RequiresUnreferencedCode () { } - [RequiresAssemblyFiles (nameof (RequiresAssemblyFiles))] - static void RequiresAssemblyFiles () { } - static bool OtherCondition () => true; } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlowTestSubstitutions.xml b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlowTestSubstitutions.xml deleted file mode 100644 index eecbf80fcf886..0000000000000 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FeatureGuardAttributeDataFlowTestSubstitutions.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs index 34c38504d088b..9e8154e1a4084 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Libraries/RootLibrary.cs @@ -18,6 +18,7 @@ namespace Mono.Linker.Tests.Cases.Libraries [SetupLinkerArgument ("-a", "test.exe", "library")] [SetupLinkerArgument ("--enable-opt", "ipconstprop")] [VerifyMetadataNames] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Libraries.RootLibrary.FeatureGuardSubstitutionsTest.FeatureSwitch", "false")] public class RootLibrary { private int field; @@ -161,6 +162,48 @@ private void LocalMethod () } } + [Kept] + public class FeatureGuardSubstitutionsTest + { + [Kept] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + private static bool GuardUnreferencedCode { + [Kept] + get => throw null; + } + + [Kept] + // Body is not modified because feature guard substitutions are disabled in library mode + private static void TestGuard () { + if (GuardUnreferencedCode) + RequiresUnreferencedCode (); + } + + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Libraries.RootLibrary.FeatureGuardSubstitutionsTest.FeatureSwitch")] + private static bool FeatureSwitch => throw null; + + [Kept] + // Feature switches are still substituted in library mode if explicitly passed on the command-line + [ExpectBodyModified] + private static void TestFeatureSwitch () { + if (FeatureSwitch) + RequiresUnreferencedCode (); + } + + [Kept] + public FeatureGuardSubstitutionsTest () + { + TestGuard (); + TestFeatureSwitch (); + } + + [Kept] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] + [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] + private static void RequiresUnreferencedCode () { } + } + [Kept] [KeptInterface (typeof (I))] public class IfaceClass : I diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index b30d39a672b44..2b025d9861a11 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -8,6 +8,7 @@ + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/Dependencies/TestFeatures.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/Dependencies/TestFeatures.cs new file mode 100644 index 0000000000000..942c9f3586dd3 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/Dependencies/TestFeatures.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace ILLink.RoslynAnalyzer +{ + public class TestFeatures + { + public static bool IsUnreferencedCodeSupported => true; + + public static bool IsAssemblyFilesSupported => true; + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.cs new file mode 100644 index 0000000000000..e34f2b4bbfd3f --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.cs @@ -0,0 +1,405 @@ + // Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using ILLink.RoslynAnalyzer; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [ExpectedNoWarnings] + [SetupCompileBefore ("TestFeatures.dll", new[] { "Dependencies/TestFeatures.cs" })] + [SetupCompileResource ("FeatureGuardSubstitutions.xml", "ILLink.Substitutions.xml")] + [IgnoreSubstitutions (false)] +#if NATIVEAOT + // ILC has different constant propagation behavior than ILLink, and we don't have + // the test infrastructure to check for different IL sequences between ILLink/ILC. + // Just validate the warning behavior instead. + [SkipKeptItemsValidation] +#else + // Tell linker to treat RequiresDynamicCodeAttribute as a disabled feature: + [SetupLinkerArgument ("--feature", "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false")] +#endif + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitch", "false")] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitchAndGuard", "false")] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitch", "true")] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.SwitchWithXml", "false")] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitchWithXml", "false")] + public class FeatureGuardSubstitutions + { + public static void Main () + { + DefineFeatureGuard.Test (); + FeatureGuardPrecedence.Test (); + } + + [Kept] + class DefineFeatureGuard { + [FeatureGuard (typeof(RequiresDynamicCodeAttribute))] + static bool GuardDynamicCode => RuntimeFeature.IsDynamicCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + static void TestGuardDynamicCode () + { + if (GuardDynamicCode) + RequiresDynamicCode (); + } + + [FeatureGuard (typeof(RequiresUnreferencedCodeAttribute))] + static bool GuardUnreferencedCode => TestFeatures.IsUnreferencedCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + static void TestGuardUnreferencedCode () + { + if (GuardUnreferencedCode) + RequiresUnreferencedCode (); + } + + [Kept] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [FeatureGuard (typeof(RequiresAssemblyFilesAttribute))] + static bool GuardAssemblyFiles { + [Kept] + get => TestFeatures.IsAssemblyFilesSupported; + } + + [Kept] + // Linker doesn't treat RequiresAssemblyFilesAttribute as a disabled feature, so it's not removed. + static void TestGuardAssemblyFiles () + { + if (GuardAssemblyFiles) + RequiresAssemblyFiles (); + } + + [ExpectedWarning ("IL4000", nameof (RequiresDynamicCodeAttribute), ProducedBy = Tool.Analyzer)] + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureGuard (typeof (RequiresDynamicCodeAttribute))] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardDynamicCodeAndUnreferencedCode => RuntimeFeature.IsDynamicCodeSupported && TestFeatures.IsUnreferencedCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + static void TestMultipleGuards () + { + if (GuardDynamicCodeAndUnreferencedCode) { + RequiresDynamicCode (); + RequiresUnreferencedCode (); + } + } + + static class UnreferencedCode { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool GuardUnreferencedCode => TestFeatures.IsUnreferencedCodeSupported; + } + + static class UnreferencedCodeIndirect { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool GuardUnreferencedCode => UnreferencedCode.GuardUnreferencedCode; + } + + // Currently there is no way to annotate a feature type as depending on another feature, + // so indirect guards are expressed the same way as direct guards, by using + // FeatureGuardAttribute that references the underlying feature type. + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardUnreferencedCodeIndirect => UnreferencedCodeIndirect.GuardUnreferencedCode; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + static void TestIndirectGuard () + { + if (GuardUnreferencedCodeIndirect) + RequiresUnreferencedCode (); + } + + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitch")] + static bool FeatureSwitch => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitch", out bool isEnabled) && isEnabled; + + [ExpectedWarning ("IL2026", ProducedBy = Tool.Analyzer)] // Analyzer doesn't respect FeatureSwitchDefinition or feature settings + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + [Kept] + static void TestFeatureSwitch () + { + if (FeatureSwitch) + RequiresUnreferencedCode (); + } + + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitchAndGuard")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool FeatureSwitchAndGuard => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.DefineFeatureGuard.FeatureSwitchAndGuard", out bool isEnabled) && isEnabled; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + static void TestFeatureSwitchAndGuard () + { + if (FeatureSwitchAndGuard) + RequiresUnreferencedCode (); + } + + static class UnreferencedCodeCycle { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool IsSupported => UnreferencedCodeCycle.IsSupported; + } + + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardUnreferencedCodeCycle => TestFeatures.IsUnreferencedCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + + static void TestFeatureDependencyCycle1 () + { + if (GuardUnreferencedCodeCycle) + RequiresUnreferencedCode (); + } + + static class UnreferencedCodeCycle2_A { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool IsSupported => UnreferencedCodeCycle2_A.IsSupported; + } + + static class UnreferencedCodeCycle2_B { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool IsSupported => UnreferencedCodeCycle2_B.IsSupported; + } + + static class UnreferencedCodeCycle2 { + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + public static bool IsSupported => UnreferencedCodeCycle2_A.IsSupported; + } + + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardUnreferencedCodeCycle2 => TestFeatures.IsUnreferencedCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.0", + "stloc.0", + "ldloc.0", + "brfalse.s il_6", + "ret" + })] + static void TestFeatureDependencyCycle2 () + { + if (GuardUnreferencedCodeCycle2) + RequiresUnreferencedCode (); + } + + [Kept] + public static void Test () + { + TestGuardDynamicCode (); + TestGuardUnreferencedCode (); + TestGuardAssemblyFiles (); + TestMultipleGuards (); + TestIndirectGuard (); + TestFeatureDependencyCycle1 (); + TestFeatureDependencyCycle2 (); + TestFeatureSwitch (); + TestFeatureSwitchAndGuard (); + } + } + + [Kept] + class FeatureGuardPrecedence { + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitch")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardAndSwitch => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitch", out bool isEnabled) && isEnabled; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.1", + "stloc.0", + "ldloc.0", + "pop", + "call System.Void Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions::RequiresUnreferencedCode()", + "nop", + "ret" + })] + // ILLink/ILCompiler ignore FeatureGuard on properties that also have FeatureSwitchDefinition + [ExpectedWarning ("IL2026", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + static void TestSwitchWinsOverGuard () + { + if (GuardAndSwitch) + RequiresUnreferencedCode (); + } + + [Kept] + [KeptAttributeAttribute (typeof (FeatureSwitchDefinitionAttribute))] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitchNotSet")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardAndSwitchNotSet { + [Kept] + get => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitchNotSet", out bool isEnabled) && isEnabled; + } + + [Kept] + // No IL modifications because feature is not set, and FeatureGuard is ignored due to FeatureSwitchDefinition. + [ExpectedWarning ("IL2026", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + static void TestSwitchNotSetWinsOverGuard () + { + if (GuardAndSwitchNotSet) + RequiresUnreferencedCode (); + } + + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardWithXml => TestFeatures.IsUnreferencedCodeSupported; + + [Kept] + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.1", + "stloc.0", + "ldloc.0", + "pop", + "call System.Void Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions::RequiresUnreferencedCode()", + "nop", + "ret" + })] + [ExpectedWarning ("IL2026", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + static void TestXmlWinsOverGuard () + { + if (GuardWithXml) + RequiresUnreferencedCode (); + } + + [KeptAttributeAttribute (typeof (FeatureSwitchDefinitionAttribute))] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.SwitchWithXml")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool SwitchWithXml => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.SwitchWithXml", out bool isEnabled) && isEnabled; + + [Kept] + // XML substitutions win despite FeatureSwitchDefinition and feature settings. + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.1", + "stloc.0", + "ldloc.0", + "pop", + "call System.Void Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions::RequiresUnreferencedCode()", + "nop", + "ret" + })] + [ExpectedWarning ("IL2026", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + static void TestXmlWinsOverSwitch () { + if (SwitchWithXml) + RequiresUnreferencedCode (); + } + + [KeptAttributeAttribute (typeof (FeatureSwitchDefinitionAttribute))] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [ExpectedWarning ("IL4000", nameof (RequiresUnreferencedCodeAttribute), ProducedBy = Tool.Analyzer)] + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardPrecedence.GuardAndSwitchWithXml")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardAndSwitchWithXml => AppContext.TryGetSwitch ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions.FeatureGuardPrecedence.GuardAndSwitchWithXml", out bool isEnabled) && isEnabled; + + [Kept] + // XML substitutions win despite FeatureSwitchDefinition and feature settings. + [ExpectedInstructionSequence (new[] { + "nop", + "ldc.i4.1", + "stloc.0", + "ldloc.0", + "pop", + "call System.Void Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutions::RequiresUnreferencedCode()", + "nop", + "ret" + })] + [ExpectedWarning ("IL2026", ProducedBy = Tool.Trimmer | Tool.NativeAot)] + static void TestXmlWinsOverGuardAndSwitch () { + if (GuardAndSwitchWithXml) + RequiresUnreferencedCode (); + } + + [Kept] + public static void Test () { + TestSwitchWinsOverGuard (); + TestSwitchNotSetWinsOverGuard (); + TestXmlWinsOverGuard (); + TestXmlWinsOverSwitch (); + TestXmlWinsOverGuardAndSwitch (); + } + } + + [RequiresDynamicCode (nameof (RequiresDynamicCode))] + static void RequiresDynamicCode () { } + + [Kept] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] + [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] + static void RequiresUnreferencedCode () { } + + [Kept] + [KeptAttributeAttribute (typeof (RequiresAssemblyFilesAttribute))] + [RequiresAssemblyFiles (nameof (RequiresAssemblyFiles))] + static void RequiresAssemblyFiles () { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.xml b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.xml new file mode 100644 index 0000000000000..ab5947a1a6b4c --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutions.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutionsDisabled.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutionsDisabled.cs new file mode 100644 index 0000000000000..198595d79394d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Substitutions/FeatureGuardSubstitutionsDisabled.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using ILLink.RoslynAnalyzer; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Substitutions +{ + [ExpectedNoWarnings] + [SetupCompileBefore ("TestFeatures.dll", new[] { "Dependencies/TestFeatures.cs" })] + [SetupLinkerArgument ("--disable-opt", "substitutefeatureguards")] + [SetupLinkerArgument ("--feature", "Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutionsDisabled.FeatureSwitch", "false")] + public class FeatureGuardSubstitutionsDisabled + { + public static void Main () + { + TestGuard (); + TestFeatureSwitch (); + } + + [Kept] + [ExpectedWarning ("IL4000", ProducedBy = Tool.Analyzer)] + [KeptAttributeAttribute (typeof (FeatureGuardAttribute))] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + static bool GuardUnreferencedCode { + [Kept] + get => throw null; + } + + [Kept] + // Body is not modified because feature guard substitutions are disabled in this test + [ExpectedWarning ("IL2026")] + static void TestGuard () + { + if (GuardUnreferencedCode) + RequiresUnreferencedCode (); + } + + [FeatureSwitchDefinition ("Mono.Linker.Tests.Cases.Substitutions.FeatureGuardSubstitutionsDisabled.FeatureSwitch")] + static bool FeatureSwitch => throw null; + + [Kept] + [ExpectedWarning ("IL2026", ProducedBy = Tool.Analyzer)] + // Feature switches are still substituted when feature guard substitutions are disabled + [ExpectBodyModified] + static void TestFeatureSwitch () + { + if (FeatureSwitch) + RequiresUnreferencedCode (); + } + + [Kept] + [KeptAttributeAttribute (typeof (RequiresUnreferencedCodeAttribute))] + [RequiresUnreferencedCode (nameof (RequiresUnreferencedCode))] + static void RequiresUnreferencedCode () { } + } +}