Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ILCompilerOptions
public List<string> InitAssemblies = new List<string>();
public List<string> TrimAssemblies = new List<string>();
public List<string> AdditionalRootAssemblies = new List<string>();
public List<string> RootEntireAssemblies = new List<string>();
public Dictionary<string, bool> FeatureSwitches = new Dictionary<string, bool>();
public List<string> Descriptors = new List<string>();
public bool FrameworkCompilation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using ILCompiler;
using ILCompiler.Logging;
using Internal.TypeSystem;
using Mono.Cecil;
Expand Down Expand Up @@ -206,17 +207,18 @@ private void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogge
List<string> 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("<G>"))
{
// 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;
}

Expand Down Expand Up @@ -295,7 +297,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)
continue;
Expand Down Expand Up @@ -353,12 +355,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)
Expand All @@ -376,19 +378,25 @@ attrMember.DeclaringType is TypeDefinition declaringType &&
unmatchedMessages.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;
unmatchedMessages.Remove(loggedMessage);
break;
}
if (GetMemberName(GetOwningType(memberDesc))!.StartsWith("<G>") == 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.<Main>$(String[])")
if (NameUtils.GetActualOriginDisplayName(memberDesc) == "Program.<Main>$(String[])")
{
expectedWarningFound = true;
unmatchedMessages.Remove(loggedMessage);
Expand Down Expand Up @@ -453,8 +461,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;
}

unexpectedMessageWarnings.Add($"Unexpected warning found: {mc}");
}
Expand All @@ -464,8 +476,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));
}

Expand Down Expand Up @@ -512,6 +525,7 @@ static bool LogMessageHasSameOriginMember(MessageContainer mc, ICustomAttributeP
DefType defType => defType.ContainingType,
MethodDesc method => method.OwningType,
FieldDesc field => field.OwningType,
PropertyPseudoDesc property => property.OwningType,
_ => null
};

Expand All @@ -520,6 +534,7 @@ static bool LogMessageHasSameOriginMember(MessageContainer mc, ICustomAttributeP
DefType defType => defType.GetName(),
MethodDesc method => method.GetName(),
FieldDesc field => field.GetName(),
PropertyPseudoDesc property => property.Name,
_ => null
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ public class TestCaseLinkerOptions
public List<string> Substitutions = new List<string>();

public List<string> LinkAttributes = new List<string>();

public List<string> RootEntireAssemblies = new List<string>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ public virtual TestCaseLinkerOptions GetLinkerOptions(NPath inputPath)
tclo.AdditionalArguments.Add(new KeyValuePair<string, string[]>((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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public ILScanResults Trim(ILCompilerOptions options, TrimmingCustomizations? cus
options: default,
logger: logger,
featureSwitchValues: options.FeatureSwitches,
rootEntireAssembliesModules: Array.Empty<string>(),
rootEntireAssembliesModules: options.RootEntireAssemblies,
additionalRootedAssemblies: options.AdditionalRootAssemblies.ToArray(),
trimmedAssemblies: options.TrimAssemblies.ToArray(),
satelliteAssemblyFilePaths: Array.Empty<string>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ private static ImmutableArray<RequiresAnalyzerBase> GetRequiresAnalyzers() =>

public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics()
{
var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder<DiagnosticDescriptor>(26);
var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder<DiagnosticDescriptor>(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));
Expand Down Expand Up @@ -190,9 +191,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()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ internal static bool IsLocalFunction(ReadOnlySpan<byte> 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("<G>");
}

internal static bool IsExtensionType(ReadOnlySpan<byte> typeName)
{
return typeName.StartsWith("<G>"u8);
}
}

file static class Extensions
Expand Down
1 change: 1 addition & 0 deletions src/tools/illink/src/ILLink.Shared/DiagnosticId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public enum DiagnosticId
TypeMapGroupTypeCannotBeStaticallyDetermined = 2124,
ReferenceNotMarkedIsTrimmable = 2125,
DataflowAnalysisDidNotConverge = 2126,
DynamicallyAccessedMembersIsNotAllowedOnExtensionProperties = 2127,
_EndTrimAnalysisWarningsSentinel,

// Single-file diagnostic ids.
Expand Down
6 changes: 6 additions & 0 deletions src/tools/illink/src/ILLink.Shared/SharedStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,12 @@
<data name="DynamicallyAccessedMembersConflictsBetweenPropertyAndAccessorMessage" xml:space="preserve">
<value>'DynamicallyAccessedMembersAttribute' on property '{0}' conflicts with the same attribute on its accessor '{1}'.</value>
</data>
<data name="DynamicallyAccessedMembersIsNotAllowedOnExtensionPropertiesTitle" xml:space="preserve">
<value>'DynamicallyAccessedMembersAttribute' on extension property is ignored</value>
</data>
<data name="DynamicallyAccessedMembersIsNotAllowedOnExtensionPropertiesMessage" xml:space="preserve">
<value>'DynamicallyAccessedMembersAttribute' on extension property '{0}' is not supported and has no effect. Apply the attribute to the accessor return value or value parameter instead.</value>
</data>
<data name="XmlCouldNotFindAnyTypeInNamespaceTitle" xml:space="preserve">
<value>The XML descriptor specifies a namespace but there are no types found in such namespace.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Request that the specified assembly's entire contents be kept.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class SetupRootEntireAssemblyAttribute : BaseMetadataAttribute
{
public SetupRootEntireAssemblyAttribute(string assembly)
{
if (string.IsNullOrEmpty(assembly))
throw new ArgumentNullException(nameof(assembly));
}
}
}
Loading
Loading