From c178358d7481ea134f4cf186cb2fc29be31f6aab Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 25 Sep 2025 14:35:20 -0700 Subject: [PATCH 1/2] Add warning for annotated extension property (#120005) Fixes https://github.com/dotnet/runtime/issues/119113 by adding a new warning that is shown by the Roslyn analyzer when attempting to annotate an extension property with DynamicallyAccessedMembersAttribute. The warning is also produced by ILLink and ILC, but only when the compiler-generated extension metadata type (which contains the property in IL) is referenced, for example via reflection or when the assembly is rooted. So in practice this will be caught just by the analyzer. See comments in the code that explain some nuances of the test approach (we need a way to assert `ExpectedWarning`s on the extension properties, but not the compiler-generated methods in the extension metadata type). Testing this uncovered a test bug which was hiding https://github.com/dotnet/runtime/issues/120004. --- .../Compiler/Dataflow/FlowAnnotations.cs | 7 +++ .../TestCasesRunner/ILCompilerOptions.cs | 1 + .../TestCasesRunner/NameUtils.cs | 1 + .../TestCasesRunner/ResultChecker.cs | 45 ++++++++++++------- .../TestCasesRunner/TestCaseLinkerOptions.cs | 2 + .../TestCaseMetadataProvider.cs | 7 +++ .../TrimmingArgumentBuilder.cs | 6 +++ .../TestCasesRunner/TrimmingDriver.cs | 2 +- .../DynamicallyAccessedMembersAnalyzer.cs | 10 +++-- .../DataFlow/CompilerGeneratedNames.cs | 5 +++ .../illink/src/ILLink.Shared/DiagnosticId.cs | 1 + .../src/ILLink.Shared/SharedStrings.resx | 6 +++ .../linker/Linker.Dataflow/FlowAnnotations.cs | 7 +++ .../SetupRootEntireAssemblyAttribute.cs | 20 +++++++++ .../DataFlow/AttributePropertyDataflow.cs | 3 ++ .../DataFlow/ExtensionMembersDataFlow.cs | 15 ++++--- .../Metadata/RootAllAssemblyNamesAreKept.cs | 2 +- .../RequiresInRootAllAssembly.cs | 4 +- .../RequiresCapability/RequiresOnAttribute.cs | 6 +++ .../TestCasesRunner/ResultChecker.cs | 33 +++++++++----- .../TestCaseMetadataProvider.cs | 7 +++ 21 files changed, 148 insertions(+), 42 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupRootEntireAssemblyAttribute.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 5333c2bd02af3d..4280cd70110da5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -504,6 +504,13 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) PropertyPseudoDesc property = new PropertyPseudoDesc(ecmaType, propertyHandle); + if (CompilerGeneratedNames.IsExtensionType(ecmaType.Name)) + { + // Annotations on extension properties are not supported. + _logger.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties, property.GetDisplayName()); + continue; + } + if (!IsTypeInterestingForDataflow(property.Signature.ReturnType)) { _logger.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings, property.GetDisplayName()); 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 b3a066627a6167..a8cc056f9aa4d9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ILCompilerOptions.cs @@ -12,6 +12,7 @@ public class ILCompilerOptions public List InitAssemblies = new List(); public List TrimAssemblies = new List(); public List AdditionalRootAssemblies = new List(); + public List RootEntireAssemblies = new List(); public Dictionary FeatureSwitches = new Dictionary(); public List Descriptors = new List(); public bool FrameworkCompilation; diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/NameUtils.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/NameUtils.cs index 53a13f821c8faa..9d5f12e458bd93 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/NameUtils.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/NameUtils.cs @@ -19,6 +19,7 @@ internal static class NameUtils MethodDesc method => TrimAssemblyNamePrefix(method.GetDisplayName()), FieldDesc field => TrimAssemblyNamePrefix(field.ToString()), ModuleDesc module => module.Assembly.GetName().Name, + PropertyPseudoDesc property => TrimAssemblyNamePrefix(property.GetDisplayName()), _ => null }; 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 ff012a7945fee0..382f6ec2c4ac22 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -7,7 +7,9 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using ILCompiler; using ILCompiler.Logging; using Internal.TypeSystem; using Mono.Cecil; @@ -202,17 +204,18 @@ private void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogge List<(ICustomAttributeProvider, CustomAttribute)> expectedNoWarningsAttributes = new(); foreach (var attrProvider in GetAttributeProviders(original)) { - if (attrProvider is IMemberDefinition attrMember && - attrMember is not TypeDefinition && - attrMember.DeclaringType is TypeDefinition declaringType && + if (attrProvider is MethodDefinition attrMethod && + attrMethod.DeclaringType is TypeDefinition declaringType && declaringType.Name.StartsWith("")) { - // Workaround: C# 14 extension members result in a compiler-generated type - // that has a member for each extension member (this is in addition to the type - // which contains the actual extension member implementation). - // The generated members inherit attributes from the extension members, but + // Workaround: C# 14 extension methods result in a compiler-generated type + // that has a method for each extension method (this is in addition to the type + // which contains the actual extension method implementation). + // The generated methods inherit attributes from the extension methods, but // have empty implementations. We don't want to check inherited ExpectedWarningAttributes - // for these members. + // for these methods. + // ExpectedWarnings for extension properties are still included, because those are only + // included once in the generated output (and the user type doesn't have any properties). continue; } @@ -295,7 +298,7 @@ attrMember.DeclaringType is TypeDefinition declaringType && string? expectedOrigin = null; bool expectedWarningFound = false; - foreach (var loggedMessage in loggedMessages) + foreach (var loggedMessage in unmatchedMessages.ToList()) { if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) continue; @@ -353,12 +356,12 @@ attrMember.DeclaringType is TypeDefinition declaringType && } else if (isCompilerGeneratedCode == true) { - if (loggedMessage.Origin?.MemberDefinition is not MethodDesc methodDesc) + if (loggedMessage.Origin?.MemberDefinition is not TypeSystemEntity memberDesc) continue; if (attrProvider is IMemberDefinition expectedMember) { - string? actualName = NameUtils.GetActualOriginDisplayName(methodDesc); + string? actualName = NameUtils.GetActualOriginDisplayName(memberDesc); string expectedTypeName = NameUtils.GetExpectedOriginDisplayName(expectedMember.DeclaringType); if (actualName?.Contains(expectedTypeName) == true && actualName?.Contains("<" + expectedMember.Name + ">") == true) @@ -376,19 +379,25 @@ attrMember.DeclaringType is TypeDefinition declaringType && loggedMessages.Remove(loggedMessage); break; } - if (methodDesc.IsConstructor && + if (memberDesc is MethodDesc methodDesc && methodDesc.IsConstructor && (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || new AssemblyQualifiedToken(methodDesc.OwningType).Equals(new AssemblyQualifiedToken(expectedMember)))) { expectedWarningFound = true; loggedMessages.Remove(loggedMessage); break; } + if (GetMemberName(GetOwningType(memberDesc))!.StartsWith("") == true && GetMemberName(memberDesc) == expectedMember.Name) + { + expectedWarningFound = true; + unmatchedMessages.Remove(loggedMessage); + break; + } } } else if (attrProvider is AssemblyDefinition expectedAssembly) { // Allow assembly-level attributes to match warnings from compiler-generated Main - if (NameUtils.GetActualOriginDisplayName(methodDesc) == "Program.
$(String[])") + if (NameUtils.GetActualOriginDisplayName(memberDesc) == "Program.
$(String[])") { expectedWarningFound = true; loggedMessages.Remove(loggedMessage); @@ -454,8 +463,12 @@ attrMember.DeclaringType is TypeDefinition declaringType && continue; // This is a hacky way to say anything in the "subtree" of the attrProvider - if (attrProvider is IMemberDefinition attrMember && (mc.Origin?.MemberDefinition is TypeSystemEntity member) && member.ToString()?.Contains(attrMember.FullName) != true) - continue; + if (attrProvider is IMemberDefinition attrMember && (mc.Origin?.MemberDefinition is TypeSystemEntity member)) + { + var memberDisplayName = NameUtils.GetActualOriginDisplayName(member); + if (memberDisplayName?.Contains(attrMember.FullName) == false) + continue; + } unexpectedWarningMessage = mc; break; @@ -503,6 +516,7 @@ static bool LogMessageHasSameOriginMember(MessageContainer mc, ICustomAttributeP DefType defType => defType.ContainingType, MethodDesc method => method.OwningType, FieldDesc field => field.OwningType, + PropertyPseudoDesc property => property.OwningType, _ => null }; @@ -511,6 +525,7 @@ static bool LogMessageHasSameOriginMember(MessageContainer mc, ICustomAttributeP DefType defType => defType.Name, MethodDesc method => method.Name, FieldDesc field => field.Name, + PropertyPseudoDesc property => property.Name, _ => null }; diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseLinkerOptions.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseLinkerOptions.cs index bfb43fb162fcde..ca67a38fba7298 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseLinkerOptions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseLinkerOptions.cs @@ -34,5 +34,7 @@ public class TestCaseLinkerOptions public List Substitutions = new List(); public List LinkAttributes = new List(); + + public List RootEntireAssemblies = new List(); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseMetadataProvider.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseMetadataProvider.cs index 15bbb2ac3d1f5a..6fd9b7be6205a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseMetadataProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseMetadataProvider.cs @@ -93,6 +93,13 @@ public virtual TestCaseLinkerOptions GetLinkerOptions(NPath inputPath) tclo.AdditionalArguments.Add(new KeyValuePair((string)ca[0].Value, values)); } + foreach (var rootEntire in _testCaseTypeDefinition.CustomAttributes.Where(attr => attr.AttributeType.Name == nameof(SetupRootEntireAssemblyAttribute))) + { + var ca = rootEntire.ConstructorArguments; + var assemblyName = (string)ca[0].Value; + tclo.RootEntireAssemblies.Add(assemblyName); + } + return tclo; } 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 2a06adb2875633..604795426163de 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingArgumentBuilder.cs @@ -271,6 +271,12 @@ public virtual void ProcessOptions(TestCaseLinkerOptions options) foreach (var additionalArgument in options.AdditionalArguments) AddAdditionalArgument(additionalArgument.Key, additionalArgument.Value); + if (options.RootEntireAssemblies?.Count > 0) + { + foreach (var asm in options.RootEntireAssemblies) + Options.RootEntireAssemblies.Add(asm); + } + if (options.IlcFrameworkCompilation) Options.FrameworkCompilation = true; } 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 97e3256eef782a..e61725ac3ddaef 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingDriver.cs @@ -137,7 +137,7 @@ public ILScanResults Trim(ILCompilerOptions options, TrimmingCustomizations? cus options: default, logger: logger, featureSwitchValues: options.FeatureSwitches, - rootEntireAssembliesModules: Array.Empty(), + rootEntireAssembliesModules: options.RootEntireAssemblies, additionalRootedAssemblies: options.AdditionalRootAssemblies.ToArray(), trimmedAssemblies: options.TrimAssemblies.ToArray(), satelliteAssemblyFilePaths: Array.Empty()); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 6f327374ee2cef..e2fc505f35726e 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -32,9 +32,10 @@ private static ImmutableArray GetRequiresAnalyzers() => public static ImmutableArray GetSupportedDiagnostics() { - var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder(26); + var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder(27); diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnreferencedCode)); diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnMethods)); + diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties)); AddRange(DiagnosticId.MethodParameterCannotBeStaticallyDetermined, DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter); AddRange(DiagnosticId.DynamicallyAccessedMembersOnFieldCanOnlyApplyToTypesOrStrings, DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings); diagDescriptorsArrayBuilder.Add(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnMethodReturnValueCanOnlyApplyToTypesOrStrings)); @@ -188,9 +189,12 @@ private static void VerifyMemberOnlyApplyToTypesOrStrings(SymbolAnalysisContext context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnMethodParameterCanOnlyApplyToTypesOrStrings), location, parameter.GetDisplayName(), member.GetDisplayName())); } } - else if (member is IPropertySymbol property && property.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !property.Type.IsTypeInterestingForDataflow(isByRef: property.ReturnsByRef)) + else if (member is IPropertySymbol property) { - context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings), location, member.GetDisplayName())); + if (property.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && !property.Type.IsTypeInterestingForDataflow(isByRef: property.ReturnsByRef)) + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings), location, member.GetDisplayName())); + if (property.GetDynamicallyAccessedMemberTypes() != DynamicallyAccessedMemberTypes.None && property.ContainingType.ExtensionParameter != null) + context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties), location, member.GetDisplayName())); } } diff --git a/src/tools/illink/src/ILLink.Shared/DataFlow/CompilerGeneratedNames.cs b/src/tools/illink/src/ILLink.Shared/DataFlow/CompilerGeneratedNames.cs index ac0c615ce9d78f..365a595c145aa3 100644 --- a/src/tools/illink/src/ILLink.Shared/DataFlow/CompilerGeneratedNames.cs +++ b/src/tools/illink/src/ILLink.Shared/DataFlow/CompilerGeneratedNames.cs @@ -85,5 +85,10 @@ internal static bool IsLocalFunction(string methodName) // Ignore the method ordinal/generation and local function ordinal/generation. return methodName.Length > i + 1 && methodName[i + 1] == 'g'; } + + internal static bool IsExtensionType(string typeName) + { + return typeName.StartsWith(""); + } } } diff --git a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs index a1e0e3bc04ee91..cac9ced1a482da 100644 --- a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs +++ b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs @@ -191,6 +191,7 @@ public enum DiagnosticId TypeMapGroupTypeCannotBeStaticallyDetermined = 2124, ReferenceNotMarkedIsTrimmable = 2125, DataflowAnalysisDidNotConverge = 2126, + DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties = 2127, _EndTrimAnalysisWarningsSentinel, // Single-file diagnostic ids. diff --git a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx index 04cd8c6e4f7c43..cfa69d7d23b50f 100644 --- a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx +++ b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx @@ -651,6 +651,12 @@ 'DynamicallyAccessedMembersAttribute' on property '{0}' conflicts with the same attribute on its accessor '{1}'. + + 'DynamicallyAccessedMembersAttribute' on extension property is ignored + + + 'DynamicallyAccessedMembersAttribute' on extension property '{0}' is not supported and has no effect. Apply the attribute to the accessor return value or value parameter instead. + The XML descriptor specifies a namespace but there are no types found in such namespace. diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs index 1d24ffaff6b936..537c7912faf633 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs @@ -329,6 +329,13 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) if (annotation == DynamicallyAccessedMemberTypes.None) continue; + if (CompilerGeneratedNames.IsExtensionType(type.Name)) + { + // Annotations on extension properties are not supported. + _context.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties, property.GetDisplayName()); + continue; + } + if (!IsTypeInterestingForDataflow(property.PropertyType)) { _context.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersOnPropertyCanOnlyApplyToTypesOrStrings, property.GetDisplayName()); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupRootEntireAssemblyAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupRootEntireAssemblyAttribute.cs new file mode 100644 index 00000000000000..6957d42a63563d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Metadata/SetupRootEntireAssemblyAttribute.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Mono.Linker.Tests.Cases.Expectations.Metadata +{ + /// + /// Request that the specified assembly's entire contents be kept. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public sealed class SetupRootEntireAssemblyAttribute : BaseMetadataAttribute + { + public SetupRootEntireAssemblyAttribute(string assembly) + { + if (string.IsNullOrEmpty(assembly)) + throw new ArgumentNullException(nameof(assembly)); + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs index b3c301044eafa8..f87e1c8603a62c 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AttributePropertyDataflow.cs @@ -145,6 +145,7 @@ class AttributesOnProperty [Kept] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] + [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static bool Property { [Kept] get; [Kept] set; } @@ -174,6 +175,7 @@ class AttributesOnEvent [KeptEventRemoveMethod] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] + [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static event EventHandler Event_FieldSyntax; @@ -183,6 +185,7 @@ class AttributesOnEvent [KeptEventRemoveMethod] [KeptAttributeAttribute(typeof(KeepsPublicMethodsAttribute))] [ExpectedWarning("IL2026", "--ClassWithKeptPublicMethods--")] + [UnexpectedWarning("IL2026", "--ClassWithKeptPublicMethods--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [KeepsPublicMethods(Type = typeof(ClassWithKeptPublicMethods))] public static event EventHandler Event_PropertySyntax { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index 5d3640648fedb8..9687375716d2db 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -11,7 +11,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow { [SkipKeptItemsValidation] - [IgnoreTestCase("NativeAOT sometimes emits duplicate IL2041: https://github.com/dotnet/runtime/issues/119155", IgnoredBy = Tool.NativeAot)] + // [IgnoreTestCase("NativeAOT sometimes emits duplicate IL2041: https://github.com/dotnet/runtime/issues/119155", IgnoredBy = Tool.NativeAot)] + // Root the entire assembly to ensure that ILLink/ILC analyze extension properties which are otherwise unused in IL. + [SetupRootEntireAssembly("test")] [ExpectedNoWarnings] public class ExtensionMembersDataFlow { @@ -188,6 +190,7 @@ public void ExtensionMembersMethodWithParamsMismatch([DynamicallyAccessedMembers public static void ExtensionMembersStaticMethodRequires() { } [ExpectedWarning("IL2041")] + [ExpectedWarning("IL2041", Tool.Trimmer | Tool.NativeAot, "Analyzer doesn't see generated extension metadata type", CompilerGeneratedCode = true)] [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public void ExtensionMembersMethodAnnotation() @@ -197,13 +200,13 @@ public void ExtensionMembersMethodAnnotation() } [ExpectedWarning("IL2041")] + [ExpectedWarning("IL2041", Tool.Trimmer | Tool.NativeAot, "Analyzer doesn't see generated extension metadata type", CompilerGeneratedCode = true)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public static void ExtensionMembersStaticMethodAnnotation() { } - // Annotations on extension properties have no effect: - // https://github.com/dotnet/runtime/issues/119113 + [ExpectedWarning("IL2127", CompilerGeneratedCode = true)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersProperty { @@ -213,8 +216,7 @@ public Type ExtensionMembersProperty set => value.RequiresPublicMethods(); } - // Annotations on extension properties have no effect: - // https://github.com/dotnet/runtime/issues/119113 + [ExpectedWarning("IL2127", CompilerGeneratedCode = true)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersPropertyMismatch { @@ -249,8 +251,7 @@ public Type ExtensionMembersPropertyRequires get => null; } - // Annotations on extension properties have no effect: - // https://github.com/dotnet/runtime/issues/119113 + [ExpectedWarning("IL2127", CompilerGeneratedCode = true)] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersPropertyConflict { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Metadata/RootAllAssemblyNamesAreKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Metadata/RootAllAssemblyNamesAreKept.cs index e24170ad1a14bb..69296a0728c9b8 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Metadata/RootAllAssemblyNamesAreKept.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Metadata/RootAllAssemblyNamesAreKept.cs @@ -4,7 +4,7 @@ namespace Mono.Linker.Tests.Cases.Metadata { [VerifyMetadataNames] - [SetupLinkerArgument("-a", "test.exe", "all")] + [SetupRootEntireAssembly("test")] [KeptMember(".ctor()")] public class RootAllAssemblyNamesAreKept { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInRootAllAssembly.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInRootAllAssembly.cs index 53533fa02f2e5d..8a71671651af08 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInRootAllAssembly.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresInRootAllAssembly.cs @@ -8,9 +8,7 @@ namespace Mono.Linker.Tests.Cases.RequiresCapability { - [IgnoreTestCase("NativeAOT test infrastructure doesn't implement rooting behavior the same way", IgnoredBy = Tool.NativeAot)] - [SetupLinkerArgument("-a", "test.exe", "all")] - + [SetupRootEntireAssembly("test")] [SkipKeptItemsValidation] [ExpectedNoWarnings] public class RequiresInRootAllAssembly diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs index 6654a03a21bd89..6403bf09aa6957 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnAttribute.cs @@ -116,11 +116,17 @@ static void MethodWithAttributeWhichRequires () { } static int _fieldWithAttributeWhichRequires; [ExpectedWarning ("IL2026", "--AttributeWhichRequiresAttribute.ctor--")] + [UnexpectedWarning ("IL2026", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning ("IL3002", "--AttributeWhichRequiresAttribute.ctor--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] + [UnexpectedWarning ("IL3002", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning ("IL3050", "--AttributeWhichRequiresAttribute.ctor--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] + [UnexpectedWarning ("IL3050", "--AttributeWhichRequiresAttribute.ctor--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning ("IL2026", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--")] + [UnexpectedWarning ("IL2026", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning ("IL3002", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] + [UnexpectedWarning ("IL3002", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [ExpectedWarning ("IL3050", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT-specific warning")] + [UnexpectedWarning ("IL3050", "--AttributeWhichRequiresOnPropertyAttribute.PropertyWhichRequires--", Tool.NativeAot, "https://github.com/dotnet/runtime/issues/120004")] [AttributeWhichRequires] [AttributeWhichRequiresOnProperty (PropertyWhichRequires = true)] static bool PropertyWithAttributeWhichRequires { get; set; } 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 3f57b237025fb6..21abc94c9b4492 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -909,17 +909,19 @@ void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogger logger List unexpectedMessageWarnings = []; foreach (var attrProvider in GetAttributeProviders(original)) { - if (attrProvider is IMemberDefinition attrMember && - attrMember is not TypeDefinition && - attrMember.DeclaringType is TypeDefinition declaringType && + if (attrProvider is MethodDefinition attrMethod && + attrMethod.DeclaringType is TypeDefinition declaringType && declaringType.Name.StartsWith("")) { - // Workaround: C# 14 extension members result in a compiler-generated type - // that has a member for each extension member (this is in addition to the type - // which contains the actual extension member implementation). - // The generated members inherit attributes from the extension members, but + // Workaround: C# 14 extension methods result in a compiler-generated type + // that has a method for each extension method (this is in addition to the type + // which contains the actual extension method implementation). + // The generated methods inherit attributes from the extension methods, but // have empty implementations. We don't want to check inherited ExpectedWarningAttributes - // for these members. + // for these methods. + + // ExpectedWarnings for extension properties are still included, because those + // are only included once in the generated output (and the user type doesn't have any properties). continue; } @@ -998,7 +1000,7 @@ attrMember.DeclaringType is TypeDefinition declaringType && string expectedOrigin = null; bool expectedWarningFound = false; - foreach (var loggedMessage in unmatchedMessages) + foreach (var loggedMessage in unmatchedMessages.ToList()) { if (loggedMessage.Category != MessageCategory.Warning || loggedMessage.Code != expectedWarningCodeNumber) @@ -1073,7 +1075,7 @@ attrMember.DeclaringType is TypeDefinition declaringType && unmatchedMessages.Remove(loggedMessage); break; } - if (memberDefinition is not MethodDefinition) + if (memberDefinition is not (MethodDefinition or PropertyDefinition)) continue; if (actualName.StartsWith(expectedMember.DeclaringType.FullName)) { @@ -1091,6 +1093,12 @@ attrMember.DeclaringType is TypeDefinition declaringType && unmatchedMessages.Remove(loggedMessage); break; } + if (memberDefinition.DeclaringType.Name.StartsWith("") && memberDefinition.Name == expectedMember.Name) + { + expectedWarningFound = true; + unmatchedMessages.Remove(loggedMessage); + break; + } } } else if (attrProvider is AssemblyDefinition expectedAssembly) @@ -1182,8 +1190,9 @@ attrMember.DeclaringType is TypeDefinition declaringType && { missingMessageWarnings.Add("Unmatched Messages:" + Environment.NewLine); missingMessageWarnings.AddRange(unmatchedMessages.Select(m => m.ToString())); - missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); - missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); + // Uncomment to show all messages when diagnosing test infrastructure issues + // missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); + // missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); Assert.Fail(string.Join(Environment.NewLine, missingMessageWarnings)); } diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadataProvider.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadataProvider.cs index 7d0e763d89e0f0..e8e4360312cb16 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadataProvider.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseMetadataProvider.cs @@ -92,6 +92,13 @@ public virtual TestCaseLinkerOptions GetLinkerOptions(NPath inputPath) tclo.AdditionalArguments.Add(new KeyValuePair((string)ca[0].Value, values)); } + foreach (var rootEntire in _testCaseTypeDefinition.CustomAttributes.Where(attr => attr.AttributeType.Name == nameof(SetupRootEntireAssemblyAttribute))) + { + var ca = rootEntire.ConstructorArguments; + var assemblyName = (string)ca[0].Value; + tclo.AdditionalArguments.Add(new KeyValuePair("-a", new[] { assemblyName, "all" })); + } + return tclo; } From 19cfd575f4818fc50908a9e34455485baa73f1de Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 28 Aug 2025 10:04:37 -0700 Subject: [PATCH 2/2] Unify ResultChecker logic (#119157) * Unify ResultChecker logic Ports some changes to the ResultChecker logic from ILLink over to the ILC test infra. Includes https://github.com/dotnet/runtime/commit/80414e96ef5b5e110c52cd92265fb3a348e56a8b, and some other small fixes. * Clean up --- .../TestCasesRunner/ResultChecker.cs | 77 +++++++++++-------- .../TestCasesRunner/TrimmingTestLogger.cs | 5 +- 2 files changed, 46 insertions(+), 36 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 382f6ec2c4ac22..77df6f45afaba3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; @@ -200,8 +201,11 @@ protected virtual void InitialChecking(TrimmedTestCaseResult testResult, Assembl private void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogger logger, bool checkRemainingErrors) { - List loggedMessages = logger.GetLoggedMessages(); + ImmutableArray allMessages = logger.GetLoggedMessages(); + List unmatchedMessages = [.. allMessages]; List<(ICustomAttributeProvider, CustomAttribute)> expectedNoWarningsAttributes = new(); + List missingMessageWarnings = []; + List unexpectedMessageWarnings = []; foreach (var attrProvider in GetAttributeProviders(original)) { if (attrProvider is MethodDefinition attrMethod && @@ -233,33 +237,29 @@ attrMethod.DeclaringType is TypeDefinition declaringType && List matchedMessages; if ((bool)attr.ConstructorArguments[1].Value) - matchedMessages = loggedMessages.Where(m => Regex.IsMatch(m.ToString(), expectedMessage)).ToList(); + matchedMessages = unmatchedMessages.Where(m => Regex.IsMatch(m.ToString(), expectedMessage)).ToList(); else - matchedMessages = loggedMessages.Where(m => MessageTextContains(m.ToString(), expectedMessage)).ToList(); - Assert.True( - matchedMessages.Count > 0, - $"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}Logged messages:{Environment.NewLine}{string.Join(Environment.NewLine, loggedMessages)}"); + matchedMessages = unmatchedMessages.Where(m => MessageTextContains(m.ToString(), expectedMessage)).ToList(); + if (matchedMessages.Count == 0) + missingMessageWarnings.Add($"Expected to find logged message matching `{expectedMessage}`, but no such message was found.{Environment.NewLine}"); foreach (var matchedMessage in matchedMessages) - loggedMessages.Remove(matchedMessage); + unmatchedMessages.Remove(matchedMessage); } break; case nameof(LogDoesNotContainAttribute): { var unexpectedMessage = (string)attr.ConstructorArguments[0].Value; - foreach (var loggedMessage in loggedMessages) + foreach (var loggedMessage in unmatchedMessages) { - var isLogged = () => - { - if ((bool)attr.ConstructorArguments[1].Value) - return !Regex.IsMatch(loggedMessage.ToString(), unexpectedMessage); - return !MessageTextContains(loggedMessage.ToString(), unexpectedMessage); - }; - - Assert.True( - isLogged(), - $"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage}{Environment.NewLine}Logged messages:{Environment.NewLine}{string.Join(Environment.NewLine, loggedMessages)}"); + bool isRegex = (bool)attr.ConstructorArguments[1].Value; + bool foundMatch = isRegex + ? Regex.IsMatch(loggedMessage.ToString(), unexpectedMessage) + : loggedMessage.ToString().Contains(unexpectedMessage); + + if (foundMatch) + unexpectedMessageWarnings.Add($"Expected to not find logged message matching `{unexpectedMessage}`, but found:{Environment.NewLine}{loggedMessage}"); } } break; @@ -367,7 +367,7 @@ attrMethod.DeclaringType is TypeDefinition declaringType && actualName?.Contains("<" + expectedMember.Name + ">") == true) { expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } if (actualName?.StartsWith(expectedTypeName) == true) @@ -376,14 +376,14 @@ attrMethod.DeclaringType is TypeDefinition declaringType && (expectedMember is FieldDefinition || expectedMember is PropertyDefinition)) { expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } if (memberDesc is MethodDesc methodDesc && methodDesc.IsConstructor && (expectedMember is FieldDefinition || expectedMember is PropertyDefinition || new AssemblyQualifiedToken(methodDesc.OwningType).Equals(new AssemblyQualifiedToken(expectedMember)))) { expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } if (GetMemberName(GetOwningType(memberDesc))!.StartsWith("") == true && GetMemberName(memberDesc) == expectedMember.Name) @@ -400,7 +400,7 @@ attrMethod.DeclaringType is TypeDefinition declaringType && if (NameUtils.GetActualOriginDisplayName(memberDesc) == "Program.
$(String[])") { expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } } @@ -411,14 +411,14 @@ attrMethod.DeclaringType is TypeDefinition declaringType && if (LogMessageHasSameOriginMember(loggedMessage, attrProvider)) { expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } continue; } expectedWarningFound = true; - loggedMessages.Remove(loggedMessage); + unmatchedMessages.Remove(loggedMessage); break; } @@ -426,11 +426,11 @@ attrMethod.DeclaringType is TypeDefinition declaringType && ? NameUtils.GetExpectedOriginDisplayName(attrProvider) + ": " : ""; - Assert.True(expectedWarningFound, - $"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + + if (!expectedWarningFound) + missingMessageWarnings.Add($"Expected to find warning: {(fileName != null ? fileName + (sourceLine != null ? $"({sourceLine},{sourceColumn})" : "") + ": " : "")}" + $"warning {expectedWarningCode}: {expectedOriginString}" + $"and message containing {string.Join(" ", expectedMessageContains.Select(m => "'" + m + "'"))}, " + - $"but no such message was found.{Environment.NewLine}Logged messages:{Environment.NewLine}{string.Join(Environment.NewLine, loggedMessages)}"); + $"but no such message was found"); } break; @@ -453,8 +453,7 @@ attrMethod.DeclaringType is TypeDefinition declaringType && int? unexpectedWarningCodeNumber = unexpectedWarningCode == null ? null : int.Parse(unexpectedWarningCode.Substring(2)); - MessageContainer? unexpectedWarningMessage = null; - foreach (var mc in logger.GetLoggedMessages()) + foreach (var mc in unmatchedMessages) { if (mc.Category != MessageCategory.Warning) continue; @@ -470,17 +469,27 @@ attrMethod.DeclaringType is TypeDefinition declaringType && continue; } - unexpectedWarningMessage = mc; - break; + unexpectedMessageWarnings.Add($"Unexpected warning found: {mc}"); } + } - Assert.False(unexpectedWarningMessage.HasValue, - $"Unexpected warning found: {unexpectedWarningMessage}"); + if (missingMessageWarnings.Any()) + { + missingMessageWarnings.Add("Unmatched Messages:" + Environment.NewLine); + missingMessageWarnings.AddRange(unmatchedMessages.Select(m => m.ToString())); + missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine); + missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString())); + Assert.Fail(string.Join(Environment.NewLine, missingMessageWarnings)); + } + + if (unexpectedMessageWarnings.Any()) + { + Assert.Fail(string.Join(Environment.NewLine, unexpectedMessageWarnings)); } if (checkRemainingErrors) { - var remainingErrors = loggedMessages.Where(m => Regex.IsMatch(m.ToString(), @".*(error | warning): \d{4}.*")); + var remainingErrors = unmatchedMessages.Where(m => Regex.IsMatch(m.ToString(), @".*(error | warning): \d{4}.*")); Assert.False(remainingErrors.Any(), $"Found unexpected errors:{Environment.NewLine}{string.Join(Environment.NewLine, remainingErrors)}"); } diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingTestLogger.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingTestLogger.cs index 3bc19c3eef01a2..da5f223229c1d1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingTestLogger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TrimmingTestLogger.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using ILCompiler; using ILCompiler.Logging; @@ -24,9 +25,9 @@ public TrimmingTestLogger() public TextWriter Writer => _infoWriter; - public List GetLoggedMessages() + public ImmutableArray GetLoggedMessages() { - return _messageContainers; + return _messageContainers.ToImmutableArray(); } public void WriteError(MessageContainer error)