From e216e06dcd94a9523e16d3c4439ae41f0d52dd06 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 10 Jul 2025 17:10:08 +0000 Subject: [PATCH 01/21] Add support for extra source files in tests --- .../TestCaseCompilationMetadataProvider.cs | 8 ++++++++ .../TestCasesRunner/TestCaseCompiler.cs | 17 +++++++++-------- .../test/Trimming.Tests.Shared/TestRunner.cs | 4 +++- 3 files changed, 20 insertions(+), 9 deletions(-) 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..7731e07739f2c2 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -151,6 +151,14 @@ static string GetReferenceDir() 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(); 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 53a1946af7495d..bb53cca48fbdb5 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -179,15 +179,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,7 +298,8 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) var syntaxTrees = options.SourceFiles.Select(p => CSharpSyntaxTree.ParseText( text: p.ReadAllText(), - options: parseOptions + options: parseOptions, + path: p.ToString() ) ); diff --git a/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs b/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs index d59da9842793ce..f797a943471616 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(); From 61e372296390822b3f72353ec5037cc2a5748ef0 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 10 Jul 2025 17:18:48 +0000 Subject: [PATCH 02/21] Add test DAM attribute with CompilerLoweringPreserve --- ...ono.Linker.Tests.Cases.Expectations.csproj | 4 ++ .../DynamicallyAccessedMembersAttribute.cs | 37 +++++++++++++++++++ .../OverrideAttributeRemoval.xml | 3 ++ .../Mono.Linker.Tests.Cases.csproj | 4 ++ .../TopLevelStatements/Directory.Build.props | 4 ++ .../TestCasesRunner/ResultChecker.cs | 18 ++++----- .../Trimming.Tests.Shared/AssemblyChecker.cs | 14 ++++++- 7 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Support/DynamicallyAccessedMembersAttribute.cs 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/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/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/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) { From 45692be2c2fd4da685e577ce5949e6aa1c91beca Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 10 Jul 2025 17:19:11 +0000 Subject: [PATCH 03/21] Add primary ctor test --- .../DataFlowTests.cs | 6 ++++ .../AttributePrimaryConstructorDataflow.cs | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePrimaryConstructorDataflow.cs diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index 50258259939eb7..ca346e8f58e487 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() { 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(); + } + } +} From 3a6fa900b1e24bc40c36587c7eed2ae786d6d0ea Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 11 Jul 2025 18:36:12 +0000 Subject: [PATCH 04/21] Don't use generated code heuristics for .NET10 Adds support for checking TargetFrameworkAttribute. For .NET10 we don't want to use the heuristics to decode compiler-generated state machines. Adds test support for injecting TargetFrameworkAttribute. Adds test coverage for .NET 9 and .NET 10 behavior. --- .../Linker.Dataflow/CompilerGeneratedState.cs | 17 +++-- .../linker/Linker.Dataflow/FlowAnnotations.cs | 6 +- .../Linker/AssemblyDefinitionExtensions.cs | 25 +++++++ .../GenerateTargetFrameworkAttribute.cs | 15 ++++ .../DataFlow/CompilerGeneratedTypes.cs | 73 ++++++++++++++++++- .../DataFlow/CompilerGeneratedTypesNet90.cs | 26 +++++++ .../DataFlow/CompilerGeneratedTypesRelease.cs | 1 + .../CompilerGeneratedTypesReleaseNet90.cs | 27 +++++++ .../Mono.Linker.Tests.csproj | 6 ++ .../TestCasesRunner/AssemblyChecker.cs | 6 ++ .../TestCaseCompilationMetadataProvider.cs | 9 ++- .../TestCasesRunner/TestCaseCompiler.cs | 62 ++++++++++++---- .../TestCasesRunner/TestCaseLinkerOptions.cs | 1 + .../Trimming.Tests.Shared/CompilerOptions.cs | 1 + .../Trimming.Tests.Shared/PathUtilities.cs | 8 +- .../test/Trimming.Tests.Shared/TestRunner.cs | 3 + 16 files changed, 259 insertions(+), 27 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/GenerateTargetFrameworkAttribute.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesNet90.cs create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypesReleaseNet90.cs diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 56133bce64f42d..cb9800ca64b79f 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -23,7 +23,7 @@ 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); + 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 @@ -516,12 +516,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. + /// attributes on the corresponding type arguments 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 (generatedType.Module.Assembly.GetTargetFrameworkVersion() >= new Version(10, 0)) + { + 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/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/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/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index 1af43fcd0487b7..424806eef757df 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,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow [ExpectedNoWarnings] [SkipKeptItemsValidation] + [Define("INCLUDE_UNEXPECTED_LOWERING_WARNINGS")] // https://github.com/dotnet/roslyn/issues/79333 + [Define("DEBUG")] public class CompilerGeneratedTypes { public static void Main() @@ -66,6 +70,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 +90,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 +115,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 +137,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()) @@ -138,13 +163,31 @@ private static void NestedIterators() } } +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#if !DEBUG + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333")] + [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)] +#endif +#endif private static void IteratorInsideClosure() { Outer(); + +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS && DEBUG + [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 | Tool.NativeAot, "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++; @@ -154,10 +197,23 @@ private static void IteratorInsideClosure() } } +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#if !DEBUG + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333")] + [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)] +#endif +#endif private static void IteratorInsideClosureMismatch() { Outer(); +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS && DEBUG + [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 | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T1>() { int x = 0; @@ -178,6 +234,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 +250,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 +265,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 +289,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..60ebf7fa0cfe2c 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,7 @@ 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 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/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 2d5b326cdd7bf5..b067853ef4cd59 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/TestCaseCompilationMetadataProvider.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs index 7731e07739f2c2..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,7 +144,7 @@ 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; @@ -253,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 bb53cca48fbdb5..7d9a054eb53385 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) @@ -236,7 +252,7 @@ protected virtual NPath CompileCSharpAssemblyWithDefaultCompiler(CompilerOptions #if NET return CompileCSharpAssemblyWithRoslyn(options); #else - return CompileCSharpAssemblyWithCsc (options); + return CompileCSharpAssemblyWithCsc (options); #endif } @@ -301,7 +317,16 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) options: parseOptions, path: p.ToString() ) - ); + ).ToList(); + + if (options.GenerateTargetFrameworkAttribute) + { + syntaxTrees.Add(CSharpSyntaxTree.ParseText( + text: GenerateTargetFrameworkAttributeSource(), + options: parseOptions, + path: "AssemblyInfo.g.cs" + )); + } var compilation = CSharpCompilation.Create( assemblyName: options.OutputPath.FileNameWithoutExtension, @@ -347,7 +372,7 @@ protected virtual NPath CompileCSharpAssemblyWithCsc(CompilerOptions options) #if NET return CompileCSharpAssemblyWithRoslyn(options); #else - return CompileCSharpAssemblyWithExternalCompiler (LocateCscExecutable (), options, "/shared "); + return CompileCSharpAssemblyWithExternalCompiler (LocateCscExecutable(), options, "/shared "); #endif } @@ -430,5 +455,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/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs index 912b247d751c23..eefced65e8c8ff 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs @@ -21,6 +21,7 @@ public class TestCaseLinkerOptions public bool StripSubstitutions; public bool StripLinkAttributes; public bool DumpDependencies; + public bool GenerateTargetFrameworkAttribute; public List> AdditionalArguments = new List>(); 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 f797a943471616..6173d9fc125355 100644 --- a/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs +++ b/src/tools/illink/test/Trimming.Tests.Shared/TestRunner.cs @@ -83,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( @@ -93,6 +94,7 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil mainAssemblyReferences, additionalDefines?.ToArray(), resources, + generateTargetFrameworkAttribute, additionalArguments)); var expectationsDefines = new string[] { "INCLUDE_EXPECTATIONS" }; @@ -107,6 +109,7 @@ private ManagedCompilationResult Compile(TestCaseSandbox sandbox, TestCaseCompil expectationsMainAssemblyReferences, expectationsDefines, resources, + generateTargetFrameworkAttribute, additionalArguments)); NPath? inputAssemblyPath = null; From 8f91d6130c1a06c21c0fcd3cbfd49b43721e7598 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 11 Jul 2025 20:18:50 +0000 Subject: [PATCH 05/21] Make new behavior opt-in This behavior can be turned on by default when we pick up a version of Roslyn with a fix for https://github.com/dotnet/roslyn/issues/79333 --- .../src/linker/Linker.Dataflow/CompilerGeneratedState.cs | 2 +- src/tools/illink/src/linker/Linker/Driver.cs | 6 ++++++ src/tools/illink/src/linker/Linker/LinkContext.cs | 2 ++ .../DataFlow/CompilerGeneratedTypes.cs | 1 + .../DataFlow/CompilerGeneratedTypesRelease.cs | 1 + 5 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index cb9800ca64b79f..6564f756da2d80 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -524,7 +524,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDefinition method, // Avoid the heuristics for .NET10+, where DynamicallyAccessedMembers flows to generated code // because it is annotated with CompilerLoweringPreserveAttribute. - if (generatedType.Module.Assembly.GetTargetFrameworkVersion() >= new Version(10, 0)) + if (_context.DisableGeneratedCodeHeuristics && generatedType.Module.Assembly.GetTargetFrameworkVersion() >= new Version(10, 0)) { 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/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index 424806eef757df..744abd26f2e9d8 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 @@ -18,6 +18,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow [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() 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 60ebf7fa0cfe2c..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 @@ -12,6 +12,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow [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 From 4f5582ac60f1f828be1d1a0e614fae2d62275d76 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 18:17:41 +0000 Subject: [PATCH 06/21] Add test infra for ILC --- .../TestCasesRunner/ResultChecker.cs | 15 ++-- .../TestCaseCompilationMetadataProvider.cs | 18 ++++- .../TestCasesRunner/TestCaseCompiler.cs | 79 +++++++++++++------ .../TestCasesRunner/TestCaseLinkerOptions.cs | 1 - 4 files changed, 81 insertions(+), 32 deletions(-) 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 a6eb6007661f62..7b992d85ad1748 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -71,16 +71,17 @@ public virtual void Check (TrimmedTestCaseResult testResult) } finally { _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 825663ac8bdae1..b2cb94119e6986 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -118,7 +118,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) @@ -201,7 +210,12 @@ public virtual IEnumerable GetSetupCompileAssembliesAfter () .Select (CreateSetupCompileAssemblyInfo); } - private SetupCompileInfo CreateSetupCompileAssemblyInfo (CustomAttribute attribute) + public bool GetGenerateTargetFrameworkAttribute() + { + return GetOptionAttributeValue(nameof(GetGenerateTargetFrameworkAttribute), true); + } + + private SetupCompileInfo CreateSetupCompileAssemblyInfo(CustomAttribute attribute) { var ctorArguments = attribute.ConstructorArguments; return new SetupCompileInfo { 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 b20c16631302c2..75f4e56763a557 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, TestCaseCompilationMetadataPro _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, IEnumerabl 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, IEnumerabl allTestCaseReferences, originalDefines, resources, + generateTargetFrameworkAttribute, additionalArguments); var testAssembly = CompileAssembly (options); @@ -64,7 +71,12 @@ public NPath CompileTestIn (NPath outputDirectory, string outputName, IEnumerabl // expectations assemblies because this would undermine our ability to inspect them for expected results during ResultChecking. The UnityLinker UnresolvedHandling tests depend on this // 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 (); @@ -77,7 +89,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 { OutputPath = outputPath, @@ -86,27 +98,30 @@ protected virtual CompilerOptions CreateOptionsForTestCase (NPath outputPath, NP 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 (); string[]? additionalArguments = setupCompileInfo.AdditionalArguments; - return new CompilerOptions { - OutputPath = outputDirectory.Combine (setupCompileInfo.OutputName), + return new CompilerOptions + { + OutputPath = outputDirectory.Combine(setupCompileInfo.OutputName), SourceFiles = sourceFiles, References = allReferences, 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 ()) { NPath outputFolder; @@ -123,7 +138,8 @@ private IEnumerable CompileBeforeTestCaseAssemblies (NPath outputDirector CollectSetupBeforeSourcesFiles (setupCompileInfo), references, defines, - CollectSetupBeforeResourcesFiles (setupCompileInfo)); + CollectSetupBeforeResourcesFiles (setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly (options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -134,7 +150,7 @@ private IEnumerable CompileBeforeTestCaseAssemblies (NPath outputDirector } } - 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 ()) { var options = CreateOptionsForSupportingAssembly ( @@ -143,7 +159,8 @@ private void CompileAfterTestCaseAssemblies (NPath outputDirectory, NPath[] refe CollectSetupAfterSourcesFiles (setupCompileInfo), references, defines, - CollectSetupAfterResourcesFiles (setupCompileInfo)); + CollectSetupAfterResourcesFiles (setupCompileInfo), + generateTargetFrameworkAttribute); var output = CompileAssembly (options); if (setupCompileInfo.RemoveFromLinkerInput) @@ -171,15 +188,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}"); } @@ -288,9 +305,18 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn (CompilerOptions options var syntaxTrees = options.SourceFiles.Select (p => CSharpSyntaxTree.ParseText ( text: p.ReadAllText (), - options: parseOptions + options: parseOptions, + path: p.ToString () ) - ); + ).ToList (); + + if (options.GenerateTargetFrameworkAttribute) { + syntaxTrees.Add (CSharpSyntaxTree.ParseText ( + text: GenerateTargetFrameworkAttributeSource (), + options: parseOptions, + path: "AssemblyInfo.g.cs" + )); + } var compilation = CSharpCompilation.Create ( assemblyName: options.OutputPath.FileNameWithoutExtension, @@ -417,5 +443,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/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs index eefced65e8c8ff..912b247d751c23 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseLinkerOptions.cs @@ -21,7 +21,6 @@ public class TestCaseLinkerOptions public bool StripSubstitutions; public bool StripLinkAttributes; public bool DumpDependencies; - public bool GenerateTargetFrameworkAttribute; public List> AdditionalArguments = new List>(); From 7e175b99dd34afe34e8def05794af032a06048bb Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 18:25:14 +0000 Subject: [PATCH 07/21] Fix validation for System members In a "copy" assembly, the DAM and CompilerLoweringPreserve types that are compiled in are kept, but the test infrastructure was filtering these out of the "linkedMembers", leading to validation errors. --- .../TestCasesRunner/AssemblyChecker.cs | 12 ++++++++---- .../Generics/NewConstraintOnClass.cs | 5 ++--- 2 files changed, 10 insertions(+), 7 deletions(-) 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 f26ac7eb06f8d4..9eca791456cf39 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 "", @@ -276,12 +280,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); } @@ -1939,7 +1940,10 @@ private IEnumerable VerifyKeptAllTypesAndMembersInAssembly (string assem 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) { var linkedType = linkedTypes[originalKvp.Key]; 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 { } From da23c7386a92a9ebc13aa0badb57c55f758e255c Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 20:18:15 +0000 Subject: [PATCH 08/21] Add ILC implementation --- .../Compiler/AssemblyExtensions.cs | 41 +++++++++++++++++++ .../Dataflow/CompilerGeneratedState.cs | 13 +++++- .../ILCompiler.Compiler/Compiler/Logger.cs | 19 +++++---- .../ILCompiler.Compiler.csproj | 1 + .../ILCompiler.Trimming.Tests.csproj | 6 +++ .../TestCasesRunner/ILCompilerOptions.cs | 1 + .../TrimmingArgumentBuilder.cs | 3 ++ .../TestCasesRunner/TrimmingDriver.cs | 5 ++- .../aot/ILCompiler/ILCompilerRootCommand.cs | 3 ++ src/coreclr/tools/aot/ILCompiler/Program.cs | 2 +- .../Linker.Dataflow/CompilerGeneratedState.cs | 2 +- .../DataFlow/CompilerGeneratedTypes.cs | 34 +++++++++------ 12 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AssemblyExtensions.cs 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 24707c75f8c293..9dce646a343e41 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -31,10 +31,13 @@ private readonly record struct TypeArgumentInfo( 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,14 @@ 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)) + { + 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 8f0bb038d21fe1..b48f0b52ceca36 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/ILCompilerOptions.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs index 926f3e593b8c70..27a4d5e79c8da9 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/TrimmingArgumentBuilder.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs index e108cced4bde5a..5c06639e920372 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs @@ -193,6 +193,9 @@ public virtual void AddAdditionalArgument (string flag, string[] values) else if (flag == "--noaotwarn") { 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 a97f9638c5f8f0..6f37baeaa3f513 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs @@ -88,7 +88,8 @@ public ILScanResults Trim (ILCompilerOptions options, TrimmingCustomizations? cu singleWarnDisabledModules: Enumerable.Empty (), suppressedCategories: options.SuppressedWarningCategories, treatWarningsAsErrors: options.TreatWarningsAsErrors, - warningsAsErrors: options.WarningsAsErrors); + warningsAsErrors: options.WarningsAsErrors, + disableGeneratedCodeHeuristics: options.DisableGeneratedCodeHeuristics); foreach (var descriptor in options.Descriptors) { if (!File.Exists (descriptor)) @@ -115,7 +116,7 @@ public ILScanResults Trim (ILCompilerOptions options, TrimmingCustomizations? cu 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 0cf9f83308e207..253cad6d74557c 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -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 6564f756da2d80..f601196b59b7f7 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -516,7 +516,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDefinition method, /// /// Gets the attributes on the "original" method of a generated type, i.e. the - /// attributes on the corresponding type arguments from the owning method. + /// attributes on the corresponding type parameters from the owning method. /// public IList? GetGeneratedTypeAttributes(TypeDefinition generatedType) { 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 744abd26f2e9d8..e1045cd9fa2c8b 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 @@ -165,21 +165,26 @@ private static void NestedIterators() } #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, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] #if !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333")] - [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")] + [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 #endif private static void IteratorInsideClosure() { Outer(); -#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS && DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS +#if DEBUG [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)] +#else // !DEBUG + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif #endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { @@ -199,21 +204,26 @@ private static void IteratorInsideClosure() } #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, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] #if !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333")] - [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")] + [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 #endif private static void IteratorInsideClosureMismatch() { Outer(); -#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS && DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS +#if DEBUG [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)] +#else // !DEBUG + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.NativeAot,"https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.NativeAot,"https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] +#endif #endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T1>() { From f82e5bfbcb6528fea46b044f9ec7bbab9e0f2f7e Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 20:20:58 +0000 Subject: [PATCH 09/21] Add CompilerLoweringPreserveAttribute --- .../src/System.Private.CoreLib.Shared.projitems | 1 + .../DynamicallyAccessedMembersAttribute.cs | 3 +++ .../CompilerLoweringPreserveAttribute.cs | 11 +++++++++++ 3 files changed, 15 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 150b1d6a8e11cd..7838ca9a5ce548 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -834,6 +834,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs index 45166d5259b5ff..4d3734b3241c0a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; + namespace System.Diagnostics.CodeAnalysis { /// @@ -25,6 +27,7 @@ namespace System.Diagnostics.CodeAnalysis /// should only be used on instance methods of types assignable to System.Type (or string, but no methods /// will use it there). /// + [CompilerLoweringPreserve] [AttributeUsage( AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs new file mode 100644 index 00000000000000..f2bd0a11187b02 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public class CompilerLoweringPreserveAttribute : Attribute + { + public CompilerLoweringPreserveAttribute() { } + } +} From 069ee1e3100a96077dee3a0086138322b198669d Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 20:52:31 +0000 Subject: [PATCH 10/21] Fix formatting --- .../TestCasesRunner/AssemblyChecker.cs | 32 +++++++++---------- .../TestCasesRunner/TestCaseCompiler.cs | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) 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 3734d7ba80c00b..2bbc6f55408258 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs @@ -51,22 +51,22 @@ class LinkedMethodEntity : LinkedEntity // Note: It's enough to exclude the type name, all of its members will also be excluded then private static readonly HashSet ExcludeDisplayNames = new() { - // Ignore compiler injected attributes to describe language version - "Microsoft.CodeAnalysis.EmbeddedAttribute", - "System.Runtime.CompilerServices.RefSafetyRulesAttribute", - - // Ignore NativeAOT injected members - ".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 - "", - }; + // Ignore compiler injected attributes to describe language version + "Microsoft.CodeAnalysis.EmbeddedAttribute", + "System.Runtime.CompilerServices.RefSafetyRulesAttribute", + + // Ignore NativeAOT injected members + ".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 + "", + }; public AssemblyChecker( BaseAssemblyResolver originalsResolver, 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 41db0042284ca3..0aaa3e196e4ae3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -464,8 +464,8 @@ private string GenerateTargetFrameworkAttributeSource() var tfm = PathUtilities.TargetFrameworkMoniker; var tfmDisplayName = PathUtilities.TargetFrameworkMonikerDisplayName; return $""" - [assembly: System.Runtime.Versioning.TargetFramework("{tfm}", FrameworkDisplayName = "{tfmDisplayName}")] - """; + [assembly: System.Runtime.Versioning.TargetFramework("{tfm}", FrameworkDisplayName = "{tfmDisplayName}")] + """; } } } From 84e99505b59cc23aeb5a27d7706087e5444e0f5b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 21:20:38 +0000 Subject: [PATCH 11/21] Fix ref assembly --- src/libraries/System.Runtime/ref/System.Runtime.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8ba41f11d65e45..c255b23438f9c7 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -8885,7 +8885,8 @@ public sealed partial class DoesNotReturnIfAttribute : System.Attribute public DoesNotReturnIfAttribute(bool parameterValue) { } public bool ParameterValue { get { throw null; } } } - [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Field | System.AttributeTargets.GenericParameter | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Struct, Inherited=false)] + [System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute] + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Field | System.AttributeTargets.GenericParameter | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Struct, Inherited = false)] public sealed partial class DynamicallyAccessedMembersAttribute : System.Attribute { public DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes memberTypes) { } @@ -13265,6 +13266,11 @@ public enum CompilationRelaxations { NoStringInterning = 8, } + [System.AttributeUsageAttribute(System.AttributeTargets.Class, Inherited = false)] + public class CompilerLoweringPreserveAttribute : System.Attribute + { + public CompilerLoweringPreserveAttribute() { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method | System.AttributeTargets.Module)] public partial class CompilationRelaxationsAttribute : System.Attribute { From bc53e75ff03be8a0bcefbb0b1917d0fa1329e212 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 22:34:07 +0000 Subject: [PATCH 12/21] Fix warning origin for compiler-generated code warnings --- .../src/linker/Linker.Steps/MarkStep.cs | 10 +++++-- .../DataFlow/CompilerGeneratedTypes.cs | 28 ++----------------- 2 files changed, 10 insertions(+), 28 deletions(-) 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/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/CompilerGeneratedTypes.cs index e1045cd9fa2c8b..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 @@ -164,27 +164,15 @@ private static void NestedIterators() } } -#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] -#if !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333")] - [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 -#endif private static void IteratorInsideClosure() { Outer(); #if INCLUDE_UNEXPECTED_LOWERING_WARNINGS -#if DEBUG [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)] -#else // !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] - [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] -#endif + [UnexpectedWarning("IL2091", "T1", "PublicMethods", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] #endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T1>() { @@ -203,27 +191,15 @@ private static void IteratorInsideClosure() } } -#if INCLUDE_UNEXPECTED_LOWERING_WARNINGS - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] -#if !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333")] - [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 -#endif private static void IteratorInsideClosureMismatch() { Outer(); #if INCLUDE_UNEXPECTED_LOWERING_WARNINGS -#if DEBUG [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)] -#else // !DEBUG - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.NativeAot,"https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] - [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.NativeAot,"https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] -#endif + [UnexpectedWarning("IL2091", "T1", "PublicProperties", Tool.Trimmer, "https://github.com/dotnet/roslyn/issues/79333", CompilerGeneratedCode = true)] #endif IEnumerable Outer<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T1>() { From 1f6355ce84718d815a06892925dd196a9cc39885 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 14 Jul 2025 22:58:43 +0000 Subject: [PATCH 13/21] Fix ILC build --- src/coreclr/tools/aot/ILCompiler/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 001102579aa21f..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 From 5635c899e2a308070825261554053a74916a78b8 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 15 Jul 2025 23:28:39 +0000 Subject: [PATCH 14/21] Fix STJ build, make attribute internal --- .../CompilerServices/CompilerLoweringPreserveAttribute.cs | 7 ++++++- src/libraries/System.Runtime/ref/System.Runtime.cs | 2 +- src/libraries/System.Text.Json/ref/System.Text.Json.csproj | 1 + src/libraries/System.Text.Json/src/System.Text.Json.csproj | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs index f2bd0a11187b02..98fc84544098dd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs @@ -4,7 +4,12 @@ namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class, Inherited = false)] - public class CompilerLoweringPreserveAttribute : Attribute +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class CompilerLoweringPreserveAttribute : Attribute { public CompilerLoweringPreserveAttribute() { } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index c255b23438f9c7..740f31a33a5502 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13267,7 +13267,7 @@ public enum CompilationRelaxations NoStringInterning = 8, } [System.AttributeUsageAttribute(System.AttributeTargets.Class, Inherited = false)] - public class CompilerLoweringPreserveAttribute : System.Attribute + public sealed class CompilerLoweringPreserveAttribute : System.Attribute { public CompilerLoweringPreserveAttribute() { } } diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index dc2e4d28e75f03..e10aaaa3992b1d 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj @@ -20,6 +20,7 @@ + diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 9b1a8898636082..20fd9c0562fb13 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -350,6 +350,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET + From 6338a5da9950a6528464c9a144cb8de8b41339d4 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 15 Jul 2025 23:38:26 +0000 Subject: [PATCH 15/21] Add encoding to fix CS8055 --- .../TestCasesRunner/TestCaseCompiler.cs | 6 ++++-- .../Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) 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 0aaa3e196e4ae3..f043024e96a741 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -318,7 +318,8 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) CSharpSyntaxTree.ParseText( text: p.ReadAllText(), options: parseOptions, - path: p.ToString() + path: p.ToString(), + Encoding.UTF8 ) ).ToList(); @@ -327,7 +328,8 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) syntaxTrees.Add(CSharpSyntaxTree.ParseText( text: GenerateTargetFrameworkAttributeSource(), options: parseOptions, - path: "AssemblyInfo.g.cs" + path: "AssemblyInfo.g.cs", + Encoding.UTF8 )); } 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 fe5c9abd6242d7..b2fba4a87f4780 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -315,7 +315,8 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) CSharpSyntaxTree.ParseText( text: p.ReadAllText(), options: parseOptions, - path: p.ToString() + path: p.ToString(), + Encoding.UTF8 ) ).ToList(); @@ -324,7 +325,8 @@ protected virtual NPath CompileCSharpAssemblyWithRoslyn(CompilerOptions options) syntaxTrees.Add(CSharpSyntaxTree.ParseText( text: GenerateTargetFrameworkAttributeSource(), options: parseOptions, - path: "AssemblyInfo.g.cs" + path: "AssemblyInfo.g.cs", + Encoding.UTF8 )); } From 7d05176965b6175db007afedc4ccc9be49acd9a8 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 16 Jul 2025 18:22:06 +0000 Subject: [PATCH 16/21] Fix analyzer tests --- .../ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs | 12 ++++++++++++ .../TestCaseCompilation.cs | 14 +++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index ca346e8f58e487..5c6d4dcfd0aa18 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -94,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)); From 64c6da5ecc71702d86d3eab464d5afeb15e22cb4 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 16 Jul 2025 18:25:36 +0000 Subject: [PATCH 17/21] Revert framework changes --- .../src/System.Private.CoreLib.Shared.projitems | 1 - .../DynamicallyAccessedMembersAttribute.cs | 3 --- .../CompilerLoweringPreserveAttribute.cs | 16 ---------------- .../System.Runtime/ref/System.Runtime.cs | 8 +------- .../System.Text.Json/ref/System.Text.Json.csproj | 1 - .../System.Text.Json/src/System.Text.Json.csproj | 1 - 6 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 7838ca9a5ce548..150b1d6a8e11cd 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -834,7 +834,6 @@ - diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs index 4d3734b3241c0a..45166d5259b5ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/DynamicallyAccessedMembersAttribute.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; - namespace System.Diagnostics.CodeAnalysis { /// @@ -27,7 +25,6 @@ namespace System.Diagnostics.CodeAnalysis /// should only be used on instance methods of types assignable to System.Type (or string, but no methods /// will use it there). /// - [CompilerLoweringPreserve] [AttributeUsage( AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs deleted file mode 100644 index 98fc84544098dd..00000000000000 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerLoweringPreserveAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Runtime.CompilerServices -{ - [AttributeUsage(AttributeTargets.Class, Inherited = false)] -#if SYSTEM_PRIVATE_CORELIB - public -#else - internal -#endif - sealed class CompilerLoweringPreserveAttribute : Attribute - { - public CompilerLoweringPreserveAttribute() { } - } -} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 740f31a33a5502..8ba41f11d65e45 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -8885,8 +8885,7 @@ public sealed partial class DoesNotReturnIfAttribute : System.Attribute public DoesNotReturnIfAttribute(bool parameterValue) { } public bool ParameterValue { get { throw null; } } } - [System.Runtime.CompilerServices.CompilerLoweringPreserveAttribute] - [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Field | System.AttributeTargets.GenericParameter | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Struct, Inherited = false)] + [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Field | System.AttributeTargets.GenericParameter | System.AttributeTargets.Interface | System.AttributeTargets.Method | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue | System.AttributeTargets.Struct, Inherited=false)] public sealed partial class DynamicallyAccessedMembersAttribute : System.Attribute { public DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes memberTypes) { } @@ -13266,11 +13265,6 @@ public enum CompilationRelaxations { NoStringInterning = 8, } - [System.AttributeUsageAttribute(System.AttributeTargets.Class, Inherited = false)] - public sealed class CompilerLoweringPreserveAttribute : System.Attribute - { - public CompilerLoweringPreserveAttribute() { } - } [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class | System.AttributeTargets.Method | System.AttributeTargets.Module)] public partial class CompilationRelaxationsAttribute : System.Attribute { diff --git a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj index e10aaaa3992b1d..dc2e4d28e75f03 100644 --- a/src/libraries/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/ref/System.Text.Json.csproj @@ -20,7 +20,6 @@ - diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 20fd9c0562fb13..9b1a8898636082 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -350,7 +350,6 @@ The System.Text.Json library is built-in as part of the shared framework in .NET - From cc289620cf8fd2efea374d7cb0c8d89376a13812 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 16 Jul 2025 18:30:49 +0000 Subject: [PATCH 18/21] Run the old logic for coverage --- .../Compiler/Dataflow/CompilerGeneratedState.cs | 2 ++ .../illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs | 2 ++ 2 files changed, 4 insertions(+) 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 0efd4259e6d49e..0656035bc9bee0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -667,6 +667,8 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN 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; } diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 7b9af8594dce34..546002eba29d3d 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -526,6 +526,8 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDefinition method, // 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; } From a726c8748c5eddd777f3b2af08897d3cd9684639 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 16 Jul 2025 19:02:57 +0000 Subject: [PATCH 19/21] Fix DependencyGraphTests build --- .../tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(), From 0bf54e73785dd531c520da5c0caf341a82a2b83b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 16 Jul 2025 21:20:50 +0000 Subject: [PATCH 20/21] Add doc about lowering/heuristics behavior --- .../compiler-generated-code-handling.md | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) 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 From 850ec2347bf2f15960430adac1e36e593fb6358b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 17 Jul 2025 16:27:04 +0000 Subject: [PATCH 21/21] PR feedback - Fix doc comment - Fix formatting --- .../Compiler/Dataflow/CompilerGeneratedState.cs | 2 +- .../illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs | 2 +- .../test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 0656035bc9bee0..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,7 +24,7 @@ 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; diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index 546002eba29d3d..f28de73c2932f5 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -22,7 +22,7 @@ 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 + /// Generic parameters of the creator used as type arguments for the type IList? OriginalAttributes); readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; 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 b2fba4a87f4780..0cdbb6c6b44299 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompiler.cs @@ -374,7 +374,7 @@ protected virtual NPath CompileCSharpAssemblyWithCsc(CompilerOptions options) #if NET return CompileCSharpAssemblyWithRoslyn(options); #else - return CompileCSharpAssemblyWithExternalCompiler (LocateCscExecutable(), options, "/shared "); + return CompileCSharpAssemblyWithExternalCompiler(LocateCscExecutable(), options, "/shared "); #endif }