diff --git a/docs/design/tools/illink/compiler-generated-code-handling.md b/docs/design/tools/illink/compiler-generated-code-handling.md index 4ed908db0eab76..df5661ea01ed96 100644 --- a/docs/design/tools/illink/compiler-generated-code-handling.md +++ b/docs/design/tools/illink/compiler-generated-code-handling.md @@ -95,6 +95,24 @@ static IEnumerable TestLocalVariable () } ``` +## Attribute propagation via CompilerLoweringPreserveAttribute + +To address the challenges of propagating user-authored attributes to compiler-generated code, .NET 10 introduced a general mechanism: `[CompilerLoweringPreserveAttribute]`. This attribute can be applied to other attribute types to instruct compilers to propagate those attributes to compiler-generated code. + +`DynamicallyAccessedMembersAttribute` is now marked with `[CompilerLoweringPreserve]`, so when the compiler generates new fields or type parameters (such as for local functions, iterator/async state machines, or primary constructor parameters), the relevant `DynamicallyAccessedMembers` annotations are automatically applied to the generated members. This allows trimming tools to directly use the annotations present in the generated code, without needing to reverse-engineer the mapping to user code. + +### .NET 10 and later + +For .NET 10 and later, trimming tools should rely on the compiler to propagate attributes such as `DynamicallyAccessedMembersAttribute` to all relevant compiler-generated code, as indicated by `[CompilerLoweringPreserve]`. No heuristics are needed for these assemblies. This isn't perfect because it's possible for such assemblies to be compiled with new Roslyn versions that could use different lowering strategies, so it's possible that the existing heuristics will break for new releases of a pre-`net10.0` assembly. + +To mitigate this there are a few options: + +1. Multitarget the library to `net10.0` (so that it is built with the new `CompilerLoweringPreserve` behavior and will avoid the heuristics) +2. Fix the heuristics to work for code produced by new Roslyn versions +3. The trimming tools could detect the presence of a polyfilled `DynamicallyAccessedMembersAttribute` type with `CompilerLoweringPreserve`. When present this would turn off the heuristics for the containing assembly. + +Another issue is that .NET 10 libraries might be built with `false`, and the tooling would not be able to detect the TargetFramework. Aside from setting `true`, mitigations 1. and 2. above would also apply to this scenario. + ### Compiler dependent behavior Since the problems are all caused by compiler generated code, the behaviors depend on the specific compiler in use. The main focus of this document is the Roslyn C# compiler right now. Mainly since it's by far the most used compiler for .NET code. That said, we would like to design the solution in such a way that other compilers using similar patterns could also benefit from it. @@ -560,9 +578,9 @@ and fields on the closure types. ### Long term solution Detecting which compiler generated items are used by any given user method is currently relatively tricky. -There's no definitive marker in the IL which would let the trimmer confidently determine this information. -Good long term solution will need the compilers to produce some kind of marker in the IL so that -static analysis tools can reliably detect all of the compiler generated items. +There's no definitive marker in the IL which would let the trimmer confidently determine this information +for all of the above cases. Good long term solution will need the compilers to produce some kind of marker +in the IL so that static analysis tools can reliably detect all of the compiler generated items. This ask can be described as: For a given user method, ability to determine all of the items (methods, fields, types, IL code) which were @@ -573,6 +591,10 @@ helpers and other infrastructure which may be needed but is not directly attribu This should be enough to implement solutions for both suppression propagation and data flow analysis. +For `DynamicallyAccessedMembersAttribute`, we have a long-term solution that relies on the +`[CompilerLoweringPreserve]` attribute, which tells Roslyn to propagate `DynamicallyAccessedMembers` +annotations to compiler-generated code. + ### Possible short term solution #### Heuristic based solution diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index c20912efd6a5fb..d55ac1c07b1474 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -67,7 +67,7 @@ public void TestDependencyGraphInvariants(EcmaMethod method) CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup(); NativeAotILProvider ilProvider = new NativeAotILProvider(); - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, Logger.Null); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, Logger.Null, disableGeneratedCodeHeuristics: true); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager(compilationGroup, context, new FullyBlockedMetadataBlockingPolicy(), new FullyBlockedManifestResourceBlockingPolicy(), diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AssemblyExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AssemblyExtensions.cs new file mode 100644 index 00000000000000..24ca1923b90c3a --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AssemblyExtensions.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Reflection.Metadata; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +#nullable enable + +namespace ILCompiler +{ + public static class AssemblyExtensions + { + public static Version? GetTargetFrameworkVersion(this EcmaAssembly assembly) + { + // Get the custom attributes from the assembly's metadata + MetadataReader reader = assembly.MetadataReader; + CustomAttributeHandle attrHandle = reader.GetCustomAttributeHandle(assembly.AssemblyDefinition.GetCustomAttributes(), + "System.Runtime.Versioning", "TargetFrameworkAttribute"); + if (!attrHandle.IsNil) + { + CustomAttribute attr = reader.GetCustomAttribute(attrHandle); + CustomAttributeValue decoded = attr.DecodeValue(new CustomAttributeTypeProvider(assembly)); + if (decoded.FixedArguments.Length == 1 && decoded.FixedArguments[0].Value is string tfm && !string.IsNullOrEmpty(tfm)) + { + var versionPrefix = "Version=v"; + var idx = tfm.IndexOf(versionPrefix); + if (idx >= 0) + { + var versionStr = tfm.Substring(idx + versionPrefix.Length); + if (Version.TryParse(versionStr, out var version)) + return version; + } + } + } + return null; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index a96ccdcf5e94ee..6e5d78b8cbdb4a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -24,17 +24,20 @@ public class CompilerGeneratedState private readonly record struct TypeArgumentInfo( /// The method which calls the ctor for the given type MethodDesc CreatingMethod, - /// Attributes for the type, pulled from the creators type arguments + /// Generic parameters of the creator used as type arguments for the type IReadOnlyList? OriginalAttributes); private readonly TypeCacheHashtable _typeCacheHashtable; private readonly Logger _logger; - public CompilerGeneratedState(ILProvider ilProvider, Logger logger) + private readonly bool _disableGeneratedCodeHeuristics; + + public CompilerGeneratedState(ILProvider ilProvider, Logger logger, bool disableGeneratedCodeHeuristics) { _typeCacheHashtable = new TypeCacheHashtable(ilProvider); _logger = logger; + _disableGeneratedCodeHeuristics = disableGeneratedCodeHeuristics; } private sealed class TypeCacheHashtable : LockFreeReaderHashtable @@ -659,6 +662,16 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN MetadataType generatedType = (MetadataType)type.GetTypeDefinition(); Debug.Assert(CompilerGeneratedNames.IsStateMachineOrDisplayClass(generatedType.Name)); + // Avoid the heuristics for .NET10+, where DynamicallyAccessedMembers flows to generated code + // because it is annotated with CompilerLoweringPreserveAttribute. + if (_disableGeneratedCodeHeuristics && + generatedType.Module.Assembly is EcmaAssembly asm && asm.GetTargetFrameworkVersion() >= new Version(10, 0)) + { + // Still run the logic for coverage to help us find bugs, but don't use the result. + GetCompilerGeneratedStateForType(generatedType); + return null; + } + var typeCache = GetCompilerGeneratedStateForType(generatedType); if (typeCache is null) return null; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 182d84900f57e8..12bd8b0e81aa12 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -37,7 +37,7 @@ public class Logger private readonly bool _treatWarningsAsErrors; private readonly Dictionary _warningsAsErrors; - public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), null, false); + public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), null, false, true); public bool IsVerbose { get; } @@ -51,7 +51,8 @@ public Logger( IEnumerable singleWarnDisabledModules, IEnumerable suppressedCategories, bool treatWarningsAsErrors, - IDictionary warningsAsErrors) + IDictionary warningsAsErrors, + bool disableGeneratedCodeHeuristics) { _logWriter = writer; IsVerbose = isVerbose; @@ -62,22 +63,22 @@ public Logger( _suppressedCategories = new HashSet(suppressedCategories, StringComparer.Ordinal); _treatWarningsAsErrors = treatWarningsAsErrors; _warningsAsErrors = new Dictionary(warningsAsErrors); - _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this); + _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this, disableGeneratedCodeHeuristics); _unconditionalSuppressMessageAttributeState = new UnconditionalSuppressMessageAttributeState(_compilerGeneratedState, this); } - public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules, IEnumerable suppressedCategories, bool treatWarningsAsErrors, IDictionary warningsAsErrors) - : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules, suppressedCategories, treatWarningsAsErrors, warningsAsErrors) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules, IEnumerable suppressedCategories, bool treatWarningsAsErrors, IDictionary warningsAsErrors, bool disableGeneratedCodeHeuristics) + : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules, suppressedCategories, treatWarningsAsErrors, warningsAsErrors, disableGeneratedCodeHeuristics) { } - public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose) - : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty(), Array.Empty(), false, new Dictionary()) + public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose, bool disableGeneratedCodeHeuristics) + : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty(), Array.Empty(), false, new Dictionary(), disableGeneratedCodeHeuristics) { } - public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose) - : this(new TextLogWriter(writer), ilProvider, isVerbose) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, bool disableGeneratedCodeHeuristics) + : this(new TextLogWriter(writer), ilProvider, isVerbose, disableGeneratedCodeHeuristics) { } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index e22f7193a2bae3..0aecb5f920268e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -343,6 +343,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/ILCompiler.Trimming.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/ILCompiler.Trimming.Tests.csproj index f2d5356f5dc9ab..e8cef2b9014c91 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/ILCompiler.Trimming.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/ILCompiler.Trimming.Tests.csproj @@ -50,6 +50,12 @@ $(TargetFramework) + + $(TargetFrameworkMoniker) + + + $(TargetFrameworkMonikerDisplayName) + $(ToolsProjectRoot)illink/test/ diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs index 6bf18640886d4a..2bbc6f55408258 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs @@ -59,6 +59,10 @@ class LinkedMethodEntity : LinkedEntity ".StartupCodeMain(Int32,IntPtr)", ".MainMethodWrapper()", ".MainMethodWrapper(String[])", + "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute.__GetFieldHelper(Int32,MethodTable*&)", + "System.Runtime.InteropServices.TypeMapping", + "System.Runtime.InteropServices.TypeMapping.GetOrCreateExternalTypeMapping()", + "System.Runtime.InteropServices.TypeMapping.GetOrCreateProxyTypeMapping()", // Ignore compiler generated code which can't be reasonably matched to the source method "", @@ -102,7 +106,7 @@ IEnumerable VerifyImpl() // TODO - this is mostly attribute verification // foreach (var originalModule in originalAssembly.Modules) - // VerifyModule(originalModule, linkedAssembly.Modules.FirstOrDefault(m => m.Name == originalModule.Name)); + // VerifyModule(originalModule, linkedAssembly.Modules.FirstOrDefault (m => m.Name == originalModule.Name)); // TODO // VerifyResources(originalAssembly, linkedAssembly); @@ -291,12 +295,9 @@ static bool ShouldIncludeType(TypeDesc type) if (metadataType.Namespace.StartsWith("Internal")) return false; - // Simple way to filter out system assemblies - the best way would be to get a list - // of input/reference assemblies and filter on that, but it's tricky and this should work for basically everything - if (metadataType.Namespace.StartsWith("System")) + if (metadataType.Module.Assembly is EcmaAssembly asm && asm.Assembly.GetName().Name == "System.Private.CoreLib") return false; - return ShouldIncludeEntityByDisplayName(type); } @@ -2059,7 +2060,10 @@ private IEnumerable VerifyKeptAllTypesAndMembersInAssembly(string assemb var missingInLinked = originalTypes.Keys.Except(linkedTypes.Keys); if (missingInLinked.Any()) + { yield return $"Expected all types to exist in the linked assembly {assemblyName}, but one or more were missing"; + yield break; + } foreach (var originalKvp in originalTypes) { diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs index e9a6db92c2bf86..b3a066627a6167 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs @@ -20,5 +20,6 @@ public class ILCompilerOptions public bool TreatWarningsAsErrors; public Dictionary WarningsAsErrors = new Dictionary(); public List SuppressedWarningCategories = new List(); + public bool DisableGeneratedCodeHeuristics; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs index 186ea1d3c69b96..673df2e9a11500 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -78,17 +78,17 @@ public virtual void Check(TrimmedTestCaseResult testResult) { _originalsResolver.Dispose(); } + } - bool HasActiveSkipKeptItemsValidationAttribute(ICustomAttributeProvider provider) + internal static bool HasActiveSkipKeptItemsValidationAttribute(ICustomAttributeProvider provider) + { + if (TryGetCustomAttribute(provider, nameof(SkipKeptItemsValidationAttribute), out var attribute)) { - if (TryGetCustomAttribute(provider, nameof(SkipKeptItemsValidationAttribute), out var attribute)) - { - object? by = attribute.GetPropertyValue(nameof(SkipKeptItemsValidationAttribute.By)); - return by is null ? true : ((Tool)by).HasFlag(Tool.NativeAot); - } - - return false; + object? by = attribute.GetPropertyValue(nameof(SkipKeptItemsValidationAttribute.By)); + return by is null ? true : ((Tool)by).HasFlag(Tool.NativeAot); } + + return false; } protected virtual AssemblyChecker CreateAssemblyChecker(AssemblyDefinition original, TrimmedTestCaseResult testResult) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs index 4dbdaf53bd47b0..f44456c12efd7c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -128,7 +128,16 @@ private static string GetReferenceDir() string runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)!; string ncaVersion = Path.GetFileName(runtimeDir); string dotnetDir = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(runtimeDir)))!; - return Path.Combine(dotnetDir, "packs", "Microsoft.NETCore.App.Ref", ncaVersion, "ref", PathUtilities.TFMDirectoryName); + return Path.Combine(dotnetDir, "packs", "Microsoft.NETCore.App.Ref", ncaVersion, "ref", PathUtilities.TargetFramework); + } + + public IEnumerable GetCommonSourceFiles() + { + var dam = _testCase.RootCasesDirectory.Parent + .Combine("Mono.Linker.Tests.Cases.Expectations") + .Combine("Support") + .Combine("DynamicallyAccessedMembersAttribute.cs"); + yield return dam; } public virtual IEnumerable GetCommonReferencedAssemblies(NPath workingDirectory) @@ -224,6 +233,11 @@ public virtual IEnumerable GetSetupCompileAssembliesAfter() .Select(CreateSetupCompileAssemblyInfo); } + public bool GetGenerateTargetFrameworkAttribute() + { + return GetOptionAttributeValue(nameof(GetGenerateTargetFrameworkAttribute), true); + } + private SetupCompileInfo CreateSetupCompileAssemblyInfo(CustomAttribute attribute) { var ctorArguments = attribute.ConstructorArguments; diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs index 8acfd278c40ffc..f043024e96a741 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -36,7 +36,7 @@ public TestCaseCompiler(TestCaseSandbox sandbox, TestCaseCompilationMetadataProv _metadataProvider = metadataProvider; } - public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable sourceFiles, string[] commonReferences, string[] mainAssemblyReferences, IEnumerable? defines, NPath[] resources, string[] additionalArguments) + public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable sourceFiles, string[] commonReferences, string[] mainAssemblyReferences, IEnumerable? defines, NPath[] resources, bool generateTargetFrameworkAttribute, string[] additionalArguments) { var originalCommonReferences = commonReferences.Select(r => r.ToNPath()).ToArray(); var originalDefines = defines?.ToArray() ?? Array.Empty(); @@ -44,7 +44,13 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable Prepare(outputDirectory); var removeFromLinkerInputAssemblies = new List(); - var compiledReferences = CompileBeforeTestCaseAssemblies(outputDirectory, originalCommonReferences, originalDefines, removeFromLinkerInputAssemblies).ToArray(); + var compiledReferences = CompileBeforeTestCaseAssemblies( + outputDirectory, + originalCommonReferences, + originalDefines, + removeFromLinkerInputAssemblies, + generateTargetFrameworkAttribute) + .ToArray(); var allTestCaseReferences = originalCommonReferences .Concat(compiledReferences) .Concat(mainAssemblyReferences.Select(r => r.ToNPath())) @@ -56,6 +62,7 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable allTestCaseReferences, originalDefines, resources, + generateTargetFrameworkAttribute, additionalArguments); var testAssembly = CompileAssembly(options); @@ -65,7 +72,12 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable // behavior of skipping the after test compile if (outputDirectory != _sandbox.ExpectationsDirectory) { - CompileAfterTestCaseAssemblies(outputDirectory, originalCommonReferences, originalDefines, removeFromLinkerInputAssemblies); + CompileAfterTestCaseAssemblies( + outputDirectory, + originalCommonReferences, + originalDefines, + removeFromLinkerInputAssemblies, + generateTargetFrameworkAttribute); foreach (var assemblyToRemove in removeFromLinkerInputAssemblies) assemblyToRemove.DeleteIfExists(); @@ -78,7 +90,7 @@ protected virtual void Prepare(NPath outputDirectory) { } - protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, string[] additionalArguments) + protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, bool generateTargetFrameworkAttribute, string[] additionalArguments) { return new CompilerOptions { @@ -88,11 +100,12 @@ protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPa Defines = defines.Concat(_metadataProvider.GetDefines()).ToArray(), Resources = resources, AdditionalArguments = additionalArguments, - CompilerToUse = _metadataProvider.GetCSharpCompilerToUse() + CompilerToUse = _metadataProvider.GetCSharpCompilerToUse(), + GenerateTargetFrameworkAttribute = generateTargetFrameworkAttribute }; } - protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources) + protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, bool generateTargetFrameworkAttribute) { var allDefines = defines.Concat(setupCompileInfo.Defines ?? Array.Empty()).ToArray(); var allReferences = references.Concat(setupCompileInfo.References?.Select(p => MakeSupportingAssemblyReferencePathAbsolute(outputDirectory, p)) ?? Array.Empty()).ToArray(); @@ -105,11 +118,12 @@ protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompil Defines = allDefines, Resources = resources, AdditionalArguments = additionalArguments, - CompilerToUse = setupCompileInfo.CompilerToUse?.ToLowerInvariant() + CompilerToUse = setupCompileInfo.CompilerToUse?.ToLowerInvariant(), + GenerateTargetFrameworkAttribute = generateTargetFrameworkAttribute }; } - private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, IList removeFromLinkerInputAssemblies) + private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, IList removeFromLinkerInputAssemblies, bool generateTargetFrameworkAttribute) { foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesBefore()) { @@ -130,7 +144,8 @@ private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory CollectSetupBeforeSourcesFiles(setupCompileInfo), references, defines, - CollectSetupBeforeResourcesFiles(setupCompileInfo)); + CollectSetupBeforeResourcesFiles(setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly(options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -141,7 +156,7 @@ private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory } } - private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, IList removeFromLinkerInputAssemblies) + private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, IList removeFromLinkerInputAssemblies, bool generateTargetFrameworkAttribute) { foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesAfter()) { @@ -151,7 +166,8 @@ private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] refer CollectSetupAfterSourcesFiles(setupCompileInfo), references, defines, - CollectSetupAfterResourcesFiles(setupCompileInfo)); + CollectSetupAfterResourcesFiles(setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly(options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -179,15 +195,15 @@ private NPath[] CollectSetupAfterResourcesFiles(SetupCompileInfo info) return _sandbox.AfterReferenceResourceDirectoryFor(info.OutputName).Files().ToArray(); } - private static NPath[] CollectSourceFilesFrom(NPath directory) + private NPath[] CollectSourceFilesFrom(NPath directory) { - var sourceFiles = directory.Files("*.cs").ToArray(); - if (sourceFiles.Length > 0) - return sourceFiles; + var sourceFiles = directory.Files("*.cs"); + if (sourceFiles.Any()) + return sourceFiles.Concat(_metadataProvider.GetCommonSourceFiles()).ToArray(); - sourceFiles = directory.Files("*.il").ToArray(); - if (sourceFiles.Length > 0) - return sourceFiles; + sourceFiles = directory.Files("*.il"); + if (sourceFiles.Any()) + return sourceFiles.ToArray(); throw new FileNotFoundException($"Didn't find any sources files in {directory}"); } @@ -301,9 +317,21 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) var syntaxTrees = options.SourceFiles.Select(p => CSharpSyntaxTree.ParseText( text: p.ReadAllText(), - options: parseOptions + options: parseOptions, + path: p.ToString(), + Encoding.UTF8 ) - ); + ).ToList(); + + if (options.GenerateTargetFrameworkAttribute) + { + syntaxTrees.Add(CSharpSyntaxTree.ParseText( + text: GenerateTargetFrameworkAttributeSource(), + options: parseOptions, + path: "AssemblyInfo.g.cs", + Encoding.UTF8 + )); + } var compilation = CSharpCompilation.Create( assemblyName: options.OutputPath.FileNameWithoutExtension, @@ -432,5 +460,14 @@ protected NPath CompileIlAssembly(CompilerOptions options) { return _ilCompiler.Compile(options); } + + private string GenerateTargetFrameworkAttributeSource() + { + var tfm = PathUtilities.TargetFrameworkMoniker; + var tfmDisplayName = PathUtilities.TargetFrameworkMonikerDisplayName; + return $""" + [assembly: System.Runtime.Versioning.TargetFramework("{tfm}", FrameworkDisplayName = "{tfmDisplayName}")] + """; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs index 4d32c0e13f2bb7..2a06adb2875633 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs @@ -199,6 +199,10 @@ public virtual void AddAdditionalArgument(string flag, string[] values) { Options.SuppressedWarningCategories.Add(MessageSubCategory.AotAnalysis); } + else if (flag == "--disable-generated-code-heuristics") + { + Options.DisableGeneratedCodeHeuristics = true; + } } public virtual void ProcessTestInputAssembly(NPath inputAssemblyPath) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs index ecf6dad1155a67..97e3256eef782a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs @@ -93,7 +93,8 @@ public ILScanResults Trim(ILCompilerOptions options, TrimmingCustomizations? cus singleWarnDisabledModules: Enumerable.Empty(), suppressedCategories: options.SuppressedWarningCategories, treatWarningsAsErrors: options.TreatWarningsAsErrors, - warningsAsErrors: options.WarningsAsErrors); + warningsAsErrors: options.WarningsAsErrors, + disableGeneratedCodeHeuristics: options.DisableGeneratedCodeHeuristics); foreach (var descriptor in options.Descriptors) { @@ -121,7 +122,7 @@ public ILScanResults Trim(ILCompilerOptions options, TrimmingCustomizations? cus SubstitutionProvider substitutionProvider = new SubstitutionProvider(logger, featureSwitches, substitutions); ilProvider = new SubstitutedILProvider(ilProvider, substitutionProvider, new DevirtualizationManager()); - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger, options.DisableGeneratedCodeHeuristics); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager( compilationGroup, diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs index 55b670ee7213da..1f57299b8103fa 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs +++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs @@ -177,6 +177,8 @@ internal sealed class ILCompilerRootCommand : RootCommand new("--make-repro-path") { Description = "Path where to place a repro package" }; public Option UnmanagedEntryPointsAssemblies { get; } = new("--generateunmanagedentrypoints") { DefaultValueFactory = _ => Array.Empty(), Description = "Generate unmanaged entrypoints for a given assembly" }; + public Option DisableGeneratedCodeHeuristics { get; } = + new("--disable-generated-code-heuristics") { Description = "Disable heuristics for detecting compiler-generated code" }; public OptimizationMode OptimizationMode { get; private set; } public ParseResult Result; @@ -266,6 +268,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler") Options.Add(SingleMethodGenericArgs); Options.Add(MakeReproPath); Options.Add(UnmanagedEntryPointsAssemblies); + Options.Add(DisableGeneratedCodeHeuristics); this.SetAction(result => { diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 46ce7a5696d804..0eb4dfa8625db0 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -94,7 +94,7 @@ public int Run() } var logger = new Logger(Console.Out, ilProvider, Get(_command.IsVerbose), ProcessWarningCodes(Get(_command.SuppressedWarnings)), Get(_command.SingleWarn), Get(_command.SingleWarnEnabledAssemblies), Get(_command.SingleWarnDisabledAssemblies), suppressedWarningCategories, - Get(_command.TreatWarningsAsErrors), warningsAsErrors); + Get(_command.TreatWarningsAsErrors), warningsAsErrors, Get(_command.DisableGeneratedCodeHeuristics)); // NativeAOT is full AOT and its pre-compiled methods can not be // thrown away at runtime if they mismatch in required ISAs or @@ -399,7 +399,7 @@ public int Run() logger, typeSystemContext, XmlReader.Create(fs), substitutionFilePath, featureSwitches)); } - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger, Get(_command.DisableGeneratedCodeHeuristics)); if (Get(_command.UseReachability) is string reachabilityInstrumentationFileName) { diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 32eb57fd031cd3..f28de73c2932f5 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -22,8 +22,8 @@ public class CompilerGeneratedState readonly record struct TypeArgumentInfo( /// The method which calls the ctor for the given type MethodDefinition CreatingMethod, - /// Attributes for the type, pulled from the creators type arguments - IReadOnlyList? OriginalAttributes); + /// Generic parameters of the creator used as type arguments for the type + IList? OriginalAttributes); readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; @@ -386,7 +386,7 @@ static void MapGeneratedTypeTypeParameters( var method = typeInfo.CreatingMethod; if (method.Body is { } body) { - var typeArgs = new ICustomAttributeProvider[generatedType.GenericParameters.Count]; + var typeArgs = new GenericParameter[generatedType.GenericParameters.Count]; var typeRef = ScanForInit(generatedType, body, context); if (typeRef is null) { @@ -397,7 +397,7 @@ static void MapGeneratedTypeTypeParameters( { var typeArg = typeRef.GenericArguments[i]; // Start with the existing parameters, in case we can't find the mapped one - ICustomAttributeProvider userAttrs = generatedType.GenericParameters[i]; + GenericParameter userAttrs = generatedType.GenericParameters[i]; // The type parameters of the state machine types are alpha renames of the // the method parameters, so the type ref should always be a GenericParameter. However, // in the case of nesting, there may be multiple renames, so if the parameter is a method @@ -518,10 +518,19 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDefinition method, /// Gets the attributes on the "original" method of a generated type, i.e. the /// attributes on the corresponding type parameters from the owning method. /// - public IReadOnlyList? GetGeneratedTypeAttributes(TypeDefinition generatedType) + public IList? GetGeneratedTypeAttributes(TypeDefinition generatedType) { Debug.Assert(CompilerGeneratedNames.IsStateMachineOrDisplayClass(generatedType.Name)); + // Avoid the heuristics for .NET10+, where DynamicallyAccessedMembers flows to generated code + // because it is annotated with CompilerLoweringPreserveAttribute. + if (_context.DisableGeneratedCodeHeuristics && generatedType.Module.Assembly.GetTargetFrameworkVersion() >= new Version(10, 0)) + { + // Still run the logic for coverage to help us find bugs, but don't use the result. + GetCompilerGeneratedStateForType(generatedType); + return null; + } + var typeToCache = GetCompilerGeneratedStateForType(generatedType); if (typeToCache is null) return null; diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs index 4c3edaaeacf991..1010982cf86d81 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs @@ -446,10 +446,10 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) DynamicallyAccessedMemberTypes[]? typeGenericParameterAnnotations = null; if (type.HasGenericParameters) { - var attrs = GetGeneratedTypeAttributes(type); + var attrs = GetGeneratedTypeAttributes(type) ?? type.GenericParameters; for (int genericParameterIndex = 0; genericParameterIndex < type.GenericParameters.Count; genericParameterIndex++) { - var provider = attrs?[genericParameterIndex] ?? type.GenericParameters[genericParameterIndex]; + var provider = attrs[genericParameterIndex]; var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(type, providerIfNotMember: provider); if (annotation != DynamicallyAccessedMemberTypes.None) { @@ -462,7 +462,7 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) return new TypeAnnotations(type, typeAnnotation, annotatedMethods.ToArray(), annotatedFields.ToArray(), typeGenericParameterAnnotations); } - private IReadOnlyList? GetGeneratedTypeAttributes(TypeDefinition typeDef) + private IList? GetGeneratedTypeAttributes(TypeDefinition typeDef) { if (!CompilerGeneratedNames.IsStateMachineOrDisplayClass(typeDef.Name)) { diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 5feafeb12b3149..ffdd348619827e 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -4073,13 +4073,19 @@ protected virtual void MarkReflectionLikeDependencies(MethodIL methodIL, bool re { case MethodDefinition nestedFunction: if (nestedFunction.Body is MethodBody nestedBody) - requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner(Context.GetMethodIL(nestedBody), origin); + { + var nestedOrigin = new MessageOrigin(nestedFunction); + requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner(Context.GetMethodIL(nestedBody), nestedOrigin); + } break; case TypeDefinition stateMachineType: foreach (var method in stateMachineType.Methods) { if (method.Body is MethodBody stateMachineBody) - requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner(Context.GetMethodIL(stateMachineBody), origin); + { + var stateMachineOrigin = new MessageOrigin(method); + requiresReflectionMethodBodyScanner |= MarkAndCheckRequiresReflectionMethodBodyScanner(Context.GetMethodIL(stateMachineBody), stateMachineOrigin); + } } break; default: diff --git a/src/tools/illink/src/linker/Linker/AssemblyDefinitionExtensions.cs b/src/tools/illink/src/linker/Linker/AssemblyDefinitionExtensions.cs index 124270ba853b43..4b316e881b9e1f 100644 --- a/src/tools/illink/src/linker/Linker/AssemblyDefinitionExtensions.cs +++ b/src/tools/illink/src/linker/Linker/AssemblyDefinitionExtensions.cs @@ -1,6 +1,7 @@ // 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 Mono.Cecil; namespace Mono.Linker @@ -16,5 +17,29 @@ public static class AssemblyDefinitionExtensions } return null; } + + public static Version? GetTargetFrameworkVersion(this AssemblyDefinition assembly) + { + foreach (var attr in assembly.CustomAttributes) + { + if (attr.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute" && attr.HasConstructorArguments) + { + var tfm = attr.ConstructorArguments[0].Value as string; + if (!string.IsNullOrEmpty(tfm)) + { + // Try to extract the version from the TFM string, e.g. ".NETCoreApp,Version=v8.0" + var versionPrefix = "Version=v"; + var idx = tfm.IndexOf(versionPrefix); + if (idx >= 0) + { + var versionStr = tfm.Substring(idx + versionPrefix.Length); + if (Version.TryParse(versionStr, out var version)) + return version; + } + } + } + } + return null; + } } } diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs index fe5f1abfb3c6ac..7d2cc9006b4b93 100644 --- a/src/tools/illink/src/linker/Linker/Driver.cs +++ b/src/tools/illink/src/linker/Linker/Driver.cs @@ -459,6 +459,12 @@ protected int SetupContext(ILogger? customLogger = null) continue; + case "--disable-generated-code-heuristics": + if (!GetBoolParam(token, l => context.DisableGeneratedCodeHeuristics = l)) + return -1; + + continue; + case "--ignore-descriptors": if (!GetBoolParam(token, l => context.IgnoreDescriptors = l)) return -1; diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs index 64b2ebdacafc67..d6423e6dd23437 100644 --- a/src/tools/illink/src/linker/Linker/LinkContext.cs +++ b/src/tools/illink/src/linker/Linker/LinkContext.cs @@ -119,6 +119,8 @@ public Pipeline Pipeline public bool DisableOperatorDiscovery { get; set; } + public bool DisableGeneratedCodeHeuristics { get; set; } + /// /// Option to not special case EventSource. /// Currently, values are hard-coded and does not have a command line option to control diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index 50258259939eb7..5c6d4dcfd0aa18 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -52,6 +52,12 @@ public Task AttributeFieldDataflow() return RunTest(nameof(AttributeFieldDataflow)); } + [Fact] + public Task AttributePrimaryConstructorDataflow() + { + return RunTest(); + } + [Fact] public Task AttributePropertyDataflow() { @@ -88,6 +94,18 @@ public Task CompilerGeneratedTypes() return RunTest(); } + [Fact] + public Task CompilerGeneratedTypesNet90() + { + return RunTest(); + } + + [Fact] + public Task CompilerGeneratedTypesReleaseNet90() + { + return RunTest(); + } + [Fact] public Task CompilerGeneratedTypesRelease() { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs index 2b45fa3159c3d7..5181d88744efb6 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.IO; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -43,11 +44,22 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel additionalReferences ??= Array.Empty(); var sources = new List() { src }; sources.AddRange(additionalSources ?? Array.Empty()); + TestCaseUtils.GetDirectoryPaths(out string rootSourceDirectory); + var commonSourcePath = Path.Combine(Path.GetDirectoryName(rootSourceDirectory)!, + "Mono.Linker.Tests.Cases.Expectations", + "Support", + "DynamicallyAccessedMembersAttribute.cs"); + sources.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(commonSourcePath), path: commonSourcePath)); var comp = CSharpCompilation.Create( assemblyName: Guid.NewGuid().ToString("N"), syntaxTrees: sources, references: SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences().Add(mdRef).AddRange(additionalReferences), - new CSharpCompilationOptions(consoleApplication ? OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary)); + new CSharpCompilationOptions(consoleApplication ? OutputKind.ConsoleApplication : OutputKind.DynamicallyLinkedLibrary, + specificDiagnosticOptions: new Dictionary + { + // Allow the polyfilled DynamicallyAccessedMembersAttribute to take precedence over the one in corelib. + { "CS0436", ReportDiagnostic.Suppress } + })); var analyzerOptions = new AnalyzerOptions( additionalFiles: additionalFiles?.ToImmutableArray() ?? ImmutableArray.Empty, new SimpleAnalyzerOptions(globalAnalyzerOptions)); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/GenerateTargetFrameworkAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/GenerateTargetFrameworkAttribute.cs new file mode 100644 index 00000000000000..ec08453d104ae6 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/GenerateTargetFrameworkAttribute.cs @@ -0,0 +1,15 @@ +// 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. + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata +{ + public sealed class GenerateTargetFrameworkAttribute : BaseMetadataAttribute + { + public readonly bool Value; + + public GenerateTargetFrameworkAttribute(bool value) + { + Value = value; + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj index b65c85abb00f5a..09d87f8f83f020 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj @@ -1,4 +1,8 @@ + + + $(NoWarn);CS0436 + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/DynamicallyAccessedMembersAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 00000000000000..fda78a81108e97 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,37 @@ +// 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 Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace System.Diagnostics.CodeAnalysis +{ + using System.Runtime.CompilerServices; + + [SkipKeptItemsValidation] + [CompilerLoweringPreserve] + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + public sealed class DynamicallyAccessedMembersAttribute : Attribute + { + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } +} + +namespace System.Runtime.CompilerServices +{ + [SkipKeptItemsValidation] + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class CompilerLoweringPreserveAttribute : Attribute + + { + public CompilerLoweringPreserveAttribute() { } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePrimaryConstructorDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePrimaryConstructorDataflow.cs new file mode 100644 index 00000000000000..87343f8835005e --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePrimaryConstructorDataflow.cs @@ -0,0 +1,33 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Reflection; +using Mono.Linker.Tests.Cases.DataFlow; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [Kept] + [ExpectedNoWarnings] + class AttributePrimaryConstructorDataflow + { + public static void Main() + { + new PrimaryConstructor(typeof(int)).UseField(); + } + + [Kept] + [KeptMember(".ctor(System.Type)")] + class PrimaryConstructor( + [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t + ) + { + [Kept] + public void UseField() => t.RequiresPublicMethods(); + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index 1af43fcd0487b7..9dd06d6fd6466a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs @@ -7,6 +7,8 @@ using System.Reflection; using System.Threading.Tasks; 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.DataFlow { @@ -14,6 +16,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow [ExpectedNoWarnings] [SkipKeptItemsValidation] + [Define("INCLUDE_UNEXPECTED_LOWERING_WARNINGS")] // https://github.com/dotnet/roslyn/issues/79333 + [Define("DEBUG")] + [SetupLinkerArgument("--disable-generated-code-heuristics")] public class CompilerGeneratedTypes { public static void Main() @@ -66,6 +71,9 @@ private static void IteratorTypeMismatch() _ = Local(); [ExpectedWarning("IL2090", nameof(DynamicallyAccessedMemberTypes.PublicProperties), CompilerGeneratedCode = true)] +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif static IEnumerable Local<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>() { foreach (var m in typeof(T).GetMethods()) @@ -83,6 +91,10 @@ private static void LocalIterator() { foreach (var m in Local()) { } +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif static IEnumerable Local< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() @@ -104,6 +116,11 @@ private static void IteratorCapture() void Local1<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { _ = Local2(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Local2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() { foreach (var m in typeof(T1).GetMethods()) @@ -121,9 +138,18 @@ private static void IteratorCapture() private static void NestedIterators() { Local1(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Local1<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { foreach (var o in Local2()) { yield return o; } + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Local2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() { foreach (var m in typeof(T1).GetMethods()) @@ -141,10 +167,21 @@ private static void NestedIterators() private static void IteratorInsideClosure() { Outer(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { int x = 0; foreach (var o in Inner()) yield return o; + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Inner<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() { x++; @@ -158,6 +195,12 @@ private static void IteratorInsideClosureMismatch() { Outer(); +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T1>() { int x = 0; @@ -178,6 +221,9 @@ private static void IteratorInsideClosureMismatch() private static void Async() { Local().Wait(); +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif async Task Local<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>() { await Task.Delay(0); @@ -191,6 +237,11 @@ private static void AsyncCapture() void Local1<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { Local2().Wait(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif async Task Local2<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() { await Task.Delay(0); @@ -201,11 +252,14 @@ private static void AsyncCapture() } } - [ExpectedWarning("IL2090", nameof(DynamicallyAccessedMemberTypes.PublicProperties), CompilerGeneratedCode = true)] private static void AsyncTypeMismatch() { _ = Local(); + [ExpectedWarning("IL2090", "T", nameof(DynamicallyAccessedMemberTypes.PublicProperties), CompilerGeneratedCode = true)] +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif static async Task Local<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>() { await Task.Delay(0); @@ -222,6 +276,10 @@ private static void AsyncInsideClosure() { int x = 0; Inner().Wait(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2090", "T2", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif async Task Inner<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T2>() { await Task.Delay(0); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesNet90.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesNet90.cs new file mode 100644 index 00000000000000..56cede0ed472aa --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesNet90.cs @@ -0,0 +1,26 @@ +// 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 Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + [SkipKeptItemsValidation] + [SetupCompileArgument("/main:Mono.Linker.Tests.Cases.DataFlow.CompilerGeneratedTypesNet90")] + [SandboxDependency("CompilerGeneratedTypes.cs")] + // Without a TargetFramework attribute, the linker uses the pre-.NET10 behavior + // which uses heuristics to understand compiler-generated types. + [GenerateTargetFrameworkAttribute(false)] + class CompilerGeneratedTypesNet90 + { + // This test just links the CompilerGeneratedTypes test without setting the + // TargetFramework attribute, to test the pre-.NET10 behavior of the linker's + // compiler-generated state machine handling. + public static void Main() + { + CompilerGeneratedTypes.Main(); + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesRelease.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesRelease.cs index c35f5c2e14b07b..9f3c73d74f9708 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesRelease.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesRelease.cs @@ -11,6 +11,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow [SetupCompileArgument("/optimize+")] [SetupCompileArgument("/main:Mono.Linker.Tests.Cases.DataFlow.CompilerGeneratedTypesRelease")] [SandboxDependency("CompilerGeneratedTypes.cs")] + [Define("INCLUDE_UNEXPECTED_LOWERING_WARNINGS")] // https://github.com/dotnet/roslyn/issues/79333 + [SetupLinkerArgument("--disable-generated-code-heuristics")] class CompilerGeneratedTypesRelease { // This test just links the CompilerGeneratedTypes test in the Release configuration, to test diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesReleaseNet90.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesReleaseNet90.cs new file mode 100644 index 00000000000000..9e9c4890d38a20 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesReleaseNet90.cs @@ -0,0 +1,27 @@ +// 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 Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + [SkipKeptItemsValidation] + [SetupCompileArgument("/optimize+")] + [SetupCompileArgument("/main:Mono.Linker.Tests.Cases.DataFlow.CompilerGeneratedTypesReleaseNet90")] + [SandboxDependency("CompilerGeneratedTypes.cs")] + // Without a TargetFramework attribute, the linker uses the pre-.NET10 behavior + // which uses heuristics to understand compiler-generated types + [GenerateTargetFrameworkAttribute(false)] + class CompilerGeneratedTypesReleaseNet90 + { + // This test just links the CompilerGeneratedTypes test in the Release configuration + // without setting the TargetFramework attribute, to test the pre-.NET10 behavior of + // the linker's compiler-generated state machine handling. + public static void Main() + { + CompilerGeneratedTypes.Main(); + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs index 6f60a6100995cd..3dd1f4f487fb9a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Generics/NewConstraintOnClass.cs @@ -130,9 +130,8 @@ public static void Test() namespace System.Runtime.CompilerServices { - // NativeAOT test infra filters out System.* members from validation for now - [Kept(By = Tool.Trimmer)] - [KeptMember(".ctor()", By = Tool.Trimmer)] + [Kept] + [KeptMember(".ctor()")] public partial class IsUnmanagedAttribute { } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/LinkAttributes/OverrideAttributeRemoval.xml b/src/tools/illink/test/Mono.Linker.Tests.Cases/LinkAttributes/OverrideAttributeRemoval.xml index 3991969c573718..a3feb692a02cc3 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/LinkAttributes/OverrideAttributeRemoval.xml +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/LinkAttributes/OverrideAttributeRemoval.xml @@ -1,5 +1,8 @@ + + + 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 94ce936df32b09..ccc1d915f1515c 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 @@ -66,4 +66,8 @@ + + + + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/TopLevelStatements/Directory.Build.props b/src/tools/illink/test/Mono.Linker.Tests.Cases/TopLevelStatements/Directory.Build.props index 1264c7538d3ed8..10b25c9f4ebc6a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/TopLevelStatements/Directory.Build.props +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/TopLevelStatements/Directory.Build.props @@ -7,4 +7,8 @@ Exe + + + + diff --git a/src/tools/illink/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj b/src/tools/illink/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj index 2d7890e6002420..ca8657356dc8b3 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj +++ b/src/tools/illink/test/Mono.Linker.Tests/Mono.Linker.Tests.csproj @@ -48,6 +48,12 @@ $(TargetFramework) + + $(TargetFrameworkMoniker) + + + $(TargetFrameworkMonikerDisplayName) + $(MicrosoftDotNetCecilVersion) diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs index a09d67863d5add..e924f613003a72 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/AssemblyChecker.cs @@ -1153,6 +1153,12 @@ protected virtual IEnumerable FilterLinkedAttributes(ICustomAttributePro if (linked is AssemblyDefinition) continue; break; + + // TargetFrameworkAttribute is generated for most testcases, so don't check this. + case "System.Runtime.Versioning.TargetFrameworkAttribute": + if (linked is AssemblyDefinition) + continue; + break; } yield return attr.AttributeType.FullName; diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index 828d3ee4924c34..a65763908a588f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -135,17 +135,17 @@ public virtual void Check(TrimmedTestCaseResult linkResult) _originalsResolver.Dispose(); _linkedResolver.Dispose(); } + } - bool HasActiveSkipKeptItemsValidationAttribute(ICustomAttributeProvider provider) + internal static bool HasActiveSkipKeptItemsValidationAttribute(IMemberDefinition provider) + { + if (TryGetCustomAttribute(provider, nameof(SkipKeptItemsValidationAttribute), out var attribute)) { - if (TryGetCustomAttribute(provider, nameof(SkipKeptItemsValidationAttribute), out var attribute)) - { - object by = attribute.GetPropertyValue(nameof(SkipKeptItemsValidationAttribute.By)); - return by is null ? true : ((Tool)by).HasFlag(Tool.Trimmer); - } - - return false; + object by = attribute.GetPropertyValue(nameof(SkipKeptItemsValidationAttribute.By)); + return by is null ? true : ((Tool)by).HasFlag(Tool.Trimmer); } + + return false; } protected virtual void VerifyILOfOtherAssemblies(TrimmedTestCaseResult linkResult) @@ -1415,7 +1415,7 @@ static bool HasAttribute(ICustomAttributeProvider caProvider, string attributeNa } #nullable enable - static bool TryGetCustomAttribute(ICustomAttributeProvider caProvider, string attributeName, [NotNullWhen(true)] out CustomAttribute? customAttribute) + internal static bool TryGetCustomAttribute(ICustomAttributeProvider caProvider, string attributeName, [NotNullWhen(true)] out CustomAttribute? customAttribute) { if (caProvider is AssemblyDefinition assembly && assembly.EntryPoint != null) { diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs index 87fc9f898955a3..99c745a2731772 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -127,7 +127,7 @@ static string GetReferenceDir() string runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location); string ncaVersion = Path.GetFileName(runtimeDir); var dotnetDir = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(runtimeDir))); - string candidatePath = Path.Combine(dotnetDir, "packs", "Microsoft.NETCore.App.Ref", ncaVersion, "ref", PathUtilities.TFMDirectoryName); + string candidatePath = Path.Combine(dotnetDir, "packs", "Microsoft.NETCore.App.Ref", ncaVersion, "ref", PathUtilities.TargetFramework); if (Directory.Exists(candidatePath)) return candidatePath; @@ -144,13 +144,21 @@ static string GetReferenceDir() if (candidatePath == null) throw new InvalidOperationException($"Could not determine ref pack path. Based on runtime directory {runtimeDir}."); - candidatePath = Path.Combine(candidatePath, "ref", PathUtilities.TFMDirectoryName); + candidatePath = Path.Combine(candidatePath, "ref", PathUtilities.TargetFramework); if (Directory.Exists(candidatePath)) return candidatePath; throw new InvalidOperationException($"Could not determine ref pack path. Computed path {candidatePath} doesn't exist."); } + public IEnumerable GetCommonSourceFiles() + { + yield return _testCase.RootCasesDirectory.Parent + .Combine("Mono.Linker.Tests.Cases.Expectations") + .Combine("Support") + .Combine("DynamicallyAccessedMembersAttribute.cs"); + } + public virtual IEnumerable GetCommonReferencedAssemblies(NPath workingDirectory) { yield return workingDirectory.Combine("Mono.Linker.Tests.Cases.Expectations.dll").ToString(); @@ -245,6 +253,11 @@ public virtual IEnumerable GetSetupCompileAssembliesAfter() .Select(CreateSetupCompileAssemblyInfo); } + public bool GetGenerateTargetFrameworkAttribute() + { + return GetOptionAttributeValue(nameof(GenerateTargetFrameworkAttribute), true); + } + private SetupCompileInfo CreateSetupCompileAssemblyInfo(CustomAttribute attribute) { var ctorArguments = attribute.ConstructorArguments; diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs index d9828e9660df7c..0cdbb6c6b44299 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -36,7 +36,7 @@ public TestCaseCompiler(TestCaseSandbox sandbox, TestCaseCompilationMetadataProv _metadataProvider = metadataProvider; } - public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable sourceFiles, string[] commonReferences, string[] mainAssemblyReferences, IEnumerable defines, NPath[] resources, string[] additionalArguments) + public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable sourceFiles, string[] commonReferences, string[] mainAssemblyReferences, IEnumerable defines, NPath[] resources, bool generateTargetFrameworkAttribute, string[] additionalArguments) { var originalCommonReferences = commonReferences.Select(r => r.ToNPath()).ToArray(); var originalDefines = defines?.ToArray() ?? Array.Empty(); @@ -44,7 +44,13 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable Prepare(outputDirectory); var removeFromLinkerInputAssemblies = new List(); - var compiledReferences = CompileBeforeTestCaseAssemblies(outputDirectory, originalCommonReferences, originalDefines, removeFromLinkerInputAssemblies).ToArray(); + var compiledReferences = CompileBeforeTestCaseAssemblies( + outputDirectory, + originalCommonReferences, + originalDefines, + removeFromLinkerInputAssemblies, + generateTargetFrameworkAttribute) + .ToArray(); var allTestCaseReferences = originalCommonReferences .Concat(compiledReferences) .Concat(mainAssemblyReferences.Select(r => r.ToNPath())) @@ -56,6 +62,7 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable allTestCaseReferences, originalDefines, resources, + generateTargetFrameworkAttribute, additionalArguments); var testAssembly = CompileAssembly(options); @@ -65,7 +72,12 @@ public NPath CompileTestIn(NPath outputDirectory, string outputName, IEnumerable // behavior of skipping the after test compile if (outputDirectory != _sandbox.ExpectationsDirectory) { - CompileAfterTestCaseAssemblies(outputDirectory, originalCommonReferences, originalDefines, removeFromLinkerInputAssemblies); + CompileAfterTestCaseAssemblies( + outputDirectory, + originalCommonReferences, + originalDefines, + removeFromLinkerInputAssemblies, + generateTargetFrameworkAttribute); foreach (var assemblyToRemove in removeFromLinkerInputAssemblies) assemblyToRemove.DeleteIfExists(); @@ -78,7 +90,7 @@ protected virtual void Prepare(NPath outputDirectory) { } - protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, string[] additionalArguments) + protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, bool generateTargetFrameworkAttribute, string[] additionalArguments) { return new CompilerOptions { @@ -88,11 +100,12 @@ protected virtual CompilerOptions CreateOptionsForTestCase(NPath outputPath, NPa Defines = defines.Concat(_metadataProvider.GetDefines()).ToArray(), Resources = resources, AdditionalArguments = additionalArguments, - CompilerToUse = _metadataProvider.GetCSharpCompilerToUse() + CompilerToUse = _metadataProvider.GetCSharpCompilerToUse(), + GenerateTargetFrameworkAttribute = generateTargetFrameworkAttribute }; } - protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources) + protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompileInfo setupCompileInfo, NPath outputDirectory, NPath[] sourceFiles, NPath[] references, string[] defines, NPath[] resources, bool generateTargetFrameworkAttribute) { var allDefines = defines.Concat(setupCompileInfo.Defines ?? Array.Empty()).ToArray(); var allReferences = references.Concat(setupCompileInfo.References?.Select(p => MakeSupportingAssemblyReferencePathAbsolute(outputDirectory, p)) ?? Array.Empty()).ToArray(); @@ -105,11 +118,12 @@ protected virtual CompilerOptions CreateOptionsForSupportingAssembly(SetupCompil Defines = allDefines, Resources = resources, AdditionalArguments = additionalArguments, - CompilerToUse = setupCompileInfo.CompilerToUse?.ToLower() + CompilerToUse = setupCompileInfo.CompilerToUse?.ToLower(), + GenerateTargetFrameworkAttribute = generateTargetFrameworkAttribute }; } - private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, List removeFromLinkerInputAssemblies) + private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, List removeFromLinkerInputAssemblies, bool generateTargetFrameworkAttribute) { foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesBefore()) { @@ -130,7 +144,8 @@ private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory CollectSetupBeforeSourcesFiles(setupCompileInfo), references, defines, - CollectSetupBeforeResourcesFiles(setupCompileInfo)); + CollectSetupBeforeResourcesFiles(setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly(options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -141,7 +156,7 @@ private IEnumerable CompileBeforeTestCaseAssemblies(NPath outputDirectory } } - private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, List removeFromLinkerInputAssemblies) + private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] references, string[] defines, List removeFromLinkerInputAssemblies, bool generateTargetFrameworkAttribute) { foreach (var setupCompileInfo in _metadataProvider.GetSetupCompileAssembliesAfter()) { @@ -151,7 +166,8 @@ private void CompileAfterTestCaseAssemblies(NPath outputDirectory, NPath[] refer CollectSetupAfterSourcesFiles(setupCompileInfo), references, defines, - CollectSetupAfterResourcesFiles(setupCompileInfo)); + CollectSetupAfterResourcesFiles(setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly(options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -179,15 +195,15 @@ private NPath[] CollectSetupAfterResourcesFiles(SetupCompileInfo info) return _sandbox.AfterReferenceResourceDirectoryFor(info.OutputName).Files().ToArray(); } - private static NPath[] CollectSourceFilesFrom(NPath directory) + private NPath[] CollectSourceFilesFrom(NPath directory) { - var sourceFiles = directory.Files("*.cs").ToArray(); - if (sourceFiles.Length > 0) - return sourceFiles; + var sourceFiles = directory.Files("*.cs"); + if (sourceFiles.Any()) + return sourceFiles.Concat(_metadataProvider.GetCommonSourceFiles()).ToArray(); - sourceFiles = directory.Files("*.il").ToArray(); - if (sourceFiles.Length > 0) - return sourceFiles; + sourceFiles = directory.Files("*.il"); + if (sourceFiles.Any()) + return sourceFiles.ToArray(); throw new FileNotFoundException($"Didn't find any sources files in {directory}"); } @@ -298,9 +314,21 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) var syntaxTrees = options.SourceFiles.Select(p => CSharpSyntaxTree.ParseText( text: p.ReadAllText(), - options: parseOptions + options: parseOptions, + path: p.ToString(), + Encoding.UTF8 ) - ); + ).ToList(); + + if (options.GenerateTargetFrameworkAttribute) + { + syntaxTrees.Add(CSharpSyntaxTree.ParseText( + text: GenerateTargetFrameworkAttributeSource(), + options: parseOptions, + path: "AssemblyInfo.g.cs", + Encoding.UTF8 + )); + } var compilation = CSharpCompilation.Create( assemblyName: options.OutputPath.FileNameWithoutExtension, @@ -429,5 +457,14 @@ protected NPath CompileIlAssembly(CompilerOptions options) { return _ilCompiler.Compile(options); } + + private string GenerateTargetFrameworkAttributeSource() + { + var tfm = PathUtilities.TargetFrameworkMoniker; + var tfmDisplayName = PathUtilities.TargetFrameworkMonikerDisplayName; + return $""" + [assembly: System.Runtime.Versioning.TargetFramework("{tfm}", FrameworkDisplayName = "{tfmDisplayName}")] + """; + } } } diff --git a/src/tools/illink/test/Trimming.Tests.Shared/AssemblyChecker.cs b/src/tools/illink/test/Trimming.Tests.Shared/AssemblyChecker.cs index ce50e31279260a..7f4ccb879b5559 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/AssemblyChecker.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/AssemblyChecker.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Mono.Cecil; +using Mono.Linker.Tests.Extensions; +using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.TestCasesRunner { @@ -17,7 +19,17 @@ static bool SkipKeptItemsValidation(IMemberDefinition member) // going to change/increase over time). // So we're effectively disabling Kept validation on compiler generated members - return IsCompilerGeneratedMember(member); + if (IsCompilerGeneratedMember(member)) + return true; + + do + { + if (ResultChecker.HasActiveSkipKeptItemsValidationAttribute(member)) + return true; + } + while ((member = member.DeclaringType) != null); + + return false; static bool IsCompilerGeneratedMember(IMemberDefinition member) { diff --git a/src/tools/illink/test/Trimming.Tests.Shared/CompilerOptions.cs b/src/tools/illink/test/Trimming.Tests.Shared/CompilerOptions.cs index d5f0174771cdd2..96eddf4b46a346 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/CompilerOptions.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/CompilerOptions.cs @@ -16,5 +16,6 @@ public class CompilerOptions public NPath[] Resources; public string[] AdditionalArguments; public string CompilerToUse; + public bool GenerateTargetFrameworkAttribute; } } diff --git a/src/tools/illink/test/Trimming.Tests.Shared/PathUtilities.cs b/src/tools/illink/test/Trimming.Tests.Shared/PathUtilities.cs index 111e40a191b661..732b85599aef84 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/PathUtilities.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/PathUtilities.cs @@ -11,7 +11,11 @@ namespace Mono.Linker.Tests.TestCasesRunner { public static class PathUtilities { - public static string TFMDirectoryName => (string)AppContext.GetData("Mono.Linker.Tests.TargetFramework")!; + public static string TargetFramework => (string)AppContext.GetData("Mono.Linker.Tests.TargetFramework")!; + + public static string TargetFrameworkMoniker => (string)AppContext.GetData("Mono.Linker.Tests.TargetFrameworkMoniker")!; + + public static string TargetFrameworkMonikerDisplayName => (string)AppContext.GetData("Mono.Linker.Tests.TargetFrameworkMonikerDisplayName")!; public static string GetTestsSourceRootDirectory([CallerFilePath] string? thisFile = null) => Path.GetFullPath((string)AppContext.GetData("Mono.Linker.Tests.LinkerTestDir")!); @@ -21,7 +25,7 @@ public static string GetTestAssemblyRoot(string assemblyName) string artifactsBinDirectory = (string)AppContext.GetData("Mono.Linker.Tests.ArtifactsBinDir")!; string configuration = (string)AppContext.GetData("Mono.Linker.Tests.Configuration")!; - return Path.GetFullPath(Path.Combine(artifactsBinDirectory, assemblyName, configuration, TFMDirectoryName)); + return Path.GetFullPath(Path.Combine(artifactsBinDirectory, assemblyName, configuration, TargetFramework)); } } } diff --git a/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs b/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs index d59da9842793ce..6173d9fc125355 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs @@ -70,7 +70,9 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil { var inputCompiler = _factory.CreateCompiler(sandbox, metadataProvider); var expectationsCompiler = _factory.CreateCompiler(sandbox, metadataProvider); - var sourceFiles = sandbox.SourceFiles.Select(s => s.ToString()).ToArray(); + var testSourceFiles = sandbox.SourceFiles.Select(s => s.ToString()); + var commonSourceFiles = metadataProvider.GetCommonSourceFiles(); + var sourceFiles = testSourceFiles.Concat(commonSourceFiles.Select(s => s.ToString())).ToArray(); var assemblyName = metadataProvider.GetAssemblyName(); @@ -81,6 +83,7 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil var expectationsCommonReferences = metadataProvider.GetCommonReferencedAssemblies(sandbox.ExpectationsDirectory).ToArray(); var expectationsMainAssemblyReferences = metadataProvider.GetReferencedAssemblies(sandbox.ExpectationsDirectory).ToArray(); + var generateTargetFrameworkAttribute = metadataProvider.GetGenerateTargetFrameworkAttribute(); var additionalDefines = GetAdditionalDefines(); var inputTask = Task.Run(() => inputCompiler.CompileTestIn( @@ -91,6 +94,7 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil mainAssemblyReferences, additionalDefines?.ToArray(), resources, + generateTargetFrameworkAttribute, additionalArguments)); var expectationsDefines = new string[] { "INCLUDE_EXPECTATIONS" }; @@ -105,6 +109,7 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil expectationsMainAssemblyReferences, expectationsDefines, resources, + generateTargetFrameworkAttribute, additionalArguments)); NPath? inputAssemblyPath = null;