Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ public static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotatio
if (flowAnnotations.HasGenericParameterAnnotation(method))
return true;

// No need to check for new constraint, because we handle that as DAMT.PublicParameterlessConstructor.

foreach (TypeDesc typeParameter in method.Instantiation)
{
if (RequiresGenericArgumentDataFlow(flowAnnotations, typeParameter))
Expand All @@ -142,6 +144,8 @@ public static bool RequiresGenericArgumentDataFlow(FlowAnnotations flowAnnotatio
if (flowAnnotations.HasGenericParameterAnnotation(type))
return true;

// No need to check for new constraint, because we handle that as DAMT.PublicParameterlessConstructor.

if (type.HasInstantiation)
{
foreach (TypeDesc typeParameter in type.Instantiation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
{
DependencyList dependencies = null;

if (_typeDefinition.HasBaseType)
{
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(ref dependencies, factory, new MessageOrigin(_typeDefinition), _typeDefinition.BaseType, _typeDefinition);
}

if (_typeDefinition is MetadataType metadataType)
{
foreach (var interfaceType in metadataType.ExplicitlyImplementedInterfaces)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,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 in the log
// missingMessageWarnings.Add(Environment.NewLine + "All Messages:" + Environment.NewLine);
// missingMessageWarnings.AddRange(allMessages.Select(m => m.ToString()));
Assert.Fail(string.Join(Environment.NewLine, missingMessageWarnings));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,41 @@ public ImmutableArray<ISymbol> GetSpecialIncompatibleMembers(RequiresAnalyzerBas

public Compilation Compilation { get; }

public readonly bool EnableTrimAnalyzer { get; }
public readonly RequiresUnreferencedCodeAnalyzer? TrimAnalyzer { get; }

public readonly bool AnyAnalyzersEnabled => EnableTrimAnalyzer || _enabledAnalyzers.Count > 0;
public readonly bool AnyAnalyzersEnabled => TrimAnalyzer is not null || _enabledAnalyzers.Count > 0;

private DataFlowAnalyzerContext(
Dictionary<RequiresAnalyzerBase, ImmutableArray<ISymbol>> enabledAnalyzers,
bool enableTrimAnalyzer,
RequiresUnreferencedCodeAnalyzer? trimAnalyzer,
Compilation compilation)
{
_enabledAnalyzers = enabledAnalyzers;
EnableTrimAnalyzer = enableTrimAnalyzer;
TrimAnalyzer = trimAnalyzer;
Compilation = compilation;
}

public static DataFlowAnalyzerContext Create(AnalyzerOptions options, Compilation compilation, ImmutableArray<RequiresAnalyzerBase> requiresAnalyzers)
{
var enabledAnalyzers = new Dictionary<RequiresAnalyzerBase, ImmutableArray<ISymbol>>();
RequiresUnreferencedCodeAnalyzer? trimAnalyzer = null;

foreach (var analyzer in requiresAnalyzers)
{
if (analyzer.IsAnalyzerEnabled(options))
{
var incompatibleMembers = analyzer.GetSpecialIncompatibleMembers(compilation);
enabledAnalyzers.Add(analyzer, incompatibleMembers);

if (analyzer is RequiresUnreferencedCodeAnalyzer rucAnalyzer)
{
trimAnalyzer = rucAnalyzer;
}
}
}
return new DataFlowAnalyzerContext(
enabledAnalyzers,
options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableTrimAnalyzer),
trimAnalyzer,
compilation);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public override void Initialize(AnalysisContext context)
});

// Remaining actions are only for DynamicallyAccessedMembers analysis.
if (!dataFlowAnalyzerContext.EnableTrimAnalyzer)
if (dataFlowAnalyzerContext.TrimAnalyzer is null)
return;

// Examine generic instantiations in base types and interface list
Expand All @@ -143,9 +143,7 @@ public override void Initialize(AnalysisContext context)
var location = GetPrimaryLocation(type.Locations);

var typeNameResolver = new TypeNameResolver(context.Compilation);
var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic);
if (type.BaseType is INamedTypeSymbol baseType)
genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType);
var genericArgumentDataFlow = new GenericArgumentDataFlow(dataFlowAnalyzerContext.TrimAnalyzer, FeatureContext.None, typeNameResolver, type, location, context.ReportDiagnostic);

foreach (var interfaceType in type.Interfaces)
genericArgumentDataFlow.ProcessGenericArgumentDataFlow(interfaceType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.Shared.TrimAnalysis;

namespace ILLink.RoslynAnalyzer
{
internal interface IGenericInstantiationAnalysis
{
void ProcessGenericInstantiation(
ITypeSymbol typeArgument,
ITypeParameterSymbol typeParameter,
FeatureContext featureContext,
TypeNameResolver typeNameResolver,
ISymbol owningSymbol,
Location location,
Action<Diagnostic>? reportDiagnostic);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.Shared;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TrimAnalysis;
Expand All @@ -18,7 +19,7 @@

namespace ILLink.RoslynAnalyzer
{
public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer
public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer, IGenericInstantiationAnalysis
{
private protected abstract string RequiresAttributeName { get; }

Expand All @@ -34,6 +35,34 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer
private protected abstract DiagnosticDescriptor RequiresOnStaticCtor { get; }
private protected abstract DiagnosticDescriptor RequiresOnEntryPoint { get; }

public virtual void ProcessGenericInstantiation(
ITypeSymbol typeArgument,
ITypeParameterSymbol typeParameter,
FeatureContext featureContext,
TypeNameResolver typeNameResolver,
ISymbol owningSymbol,
Location location,
Action<Diagnostic>? reportDiagnostic)
{
// Check constructor constraint (new())
if (typeParameter.HasConstructorConstraint)
{
// Check if this type has a public parameterless constructor
var namedTypeSymbol = typeArgument as INamedTypeSymbol;
var publicParameterlessConstructor = namedTypeSymbol?.InstanceConstructors.FirstOrDefault(c => c.Parameters.IsEmpty && c.DeclaredAccessibility == Accessibility.Public);

if (publicParameterlessConstructor != null)
{
var diagnosticContext = new DiagnosticContext(location, reportDiagnostic);
CheckAndCreateRequiresDiagnostic(
publicParameterlessConstructor,
owningSymbol,
ImmutableArray<ISymbol>.Empty,
diagnosticContext);
}
}
}

private protected virtual ImmutableArray<(Action<SyntaxNodeAnalysisContext> Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions { get; } = ImmutableArray<(Action<SyntaxNodeAnalysisContext> Action, SyntaxKind[] SyntaxKind)>.Empty;
private protected virtual ImmutableArray<(Action<SymbolAnalysisContext> Action, SymbolKind[] SymbolKind)> ExtraSymbolActions { get; } = ImmutableArray<(Action<SymbolAnalysisContext> Action, SymbolKind[] SymbolKind)>.Empty;
private protected virtual ImmutableArray<Action<CompilationAnalysisContext>> ExtraCompilationActions { get; } = ImmutableArray<Action<CompilationAnalysisContext>>.Empty;
Expand Down Expand Up @@ -74,7 +103,6 @@ public override void Initialize(AnalysisContext context)
CheckMatchingAttributesInInterfaces(symbolAnalysisContext, typeSymbol);
}, SymbolKind.NamedType);


foreach (var extraSyntaxNodeAction in ExtraSyntaxNodeActions)
context.RegisterSyntaxNodeAction(extraSyntaxNodeAction.Action, extraSyntaxNodeAction.SyntaxKind);

Expand Down Expand Up @@ -175,6 +203,12 @@ private void AnalyzeImplicitBaseCtor(SymbolAnalysisContext context)
implicitCtor,
ImmutableArray<ISymbol>.Empty,
diagnosticContext);

var dataFlowAnalyzerContext = DataFlowAnalyzerContext.Create(context.Options, context.Compilation, ImmutableArray.Create(this));
var typeNameResolver = new TypeNameResolver(context.Compilation);
var genericArgumentDataFlow = new GenericArgumentDataFlow(this, FeatureContext.None, typeNameResolver, implicitCtor, typeSymbol.Locations[0], context.ReportDiagnostic);
if (typeSymbol.BaseType is INamedTypeSymbol baseType)
genericArgumentDataFlow.ProcessGenericArgumentDataFlow(baseType);
}

[Flags]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@

using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.Shared;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
Expand All @@ -27,8 +31,10 @@ public sealed class RequiresUnreferencedCodeAnalyzer : RequiresAnalyzerBase

private static readonly DiagnosticDescriptor s_referenceNotMarkedIsTrimmableRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReferenceNotMarkedIsTrimmable);

private static readonly DiagnosticDescriptor s_dynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameterRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint, s_referenceNotMarkedIsTrimmableRule);
ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint, s_referenceNotMarkedIsTrimmableRule, s_dynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameterRule);

private protected override string RequiresAttributeName => RequiresUnreferencedCodeAttribute;

Expand Down Expand Up @@ -93,5 +99,33 @@ protected override bool VerifyAttributeArguments(AttributeData attribute) =>

protected override string GetMessageFromAttribute(AttributeData? requiresAttribute) =>
RequiresUnreferencedCodeUtils.GetMessageFromAttribute(requiresAttribute);

public override void ProcessGenericInstantiation(
ITypeSymbol typeArgument,
ITypeParameterSymbol typeParameter,
FeatureContext featureContext,
TypeNameResolver typeNameResolver,
ISymbol owningSymbol,
Location location,
Action<Diagnostic>? reportDiagnostic)
{
base.ProcessGenericInstantiation(typeArgument, typeParameter, featureContext, typeNameResolver, owningSymbol, location, reportDiagnostic);

var parameterRequirements = typeParameter.GetDynamicallyAccessedMemberTypes();
// Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor
if (typeParameter.HasConstructorConstraint)
parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;

var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements);
if (!owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) &&
!featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) &&
genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None)
{
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!;
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(reportDiagnostic, typeNameResolver, typeHierarchyType: null);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(this, featureContext, typeNameResolver, location, reportDiagnostic, reflectionAccessAnalyzer, owningSymbol);
requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action<Diagnostic
{
var diagnosticContext = new DiagnosticContext(Operation.Syntax.GetLocation(), reportDiagnostic);
// For now, feature check validation is enabled only when trim analysis is enabled.
if (!context.EnableTrimAnalyzer)
if (context.TrimAnalyzer is null)
return;

if (!OwningSymbol.IsStatic || OwningSymbol.Type.SpecialType != SpecialType.System_Boolean || OwningSymbol.SetMethod != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
{
internal readonly struct GenericArgumentDataFlow
{
private readonly DataFlowAnalyzerContext _context;
private readonly IGenericInstantiationAnalysis? _analyzer;
private readonly FeatureContext _featureContext;
private readonly TypeNameResolver _typeNameResolver;
private readonly ISymbol _owningSymbol;
private readonly Location _location;
private readonly Action<Diagnostic>? _reportDiagnostic;

public GenericArgumentDataFlow(
DataFlowAnalyzerContext context,
IGenericInstantiationAnalysis? analyzer,
FeatureContext featureContext,
TypeNameResolver typeNameResolver,
ISymbol owningSymbol,
Location location,
Action<Diagnostic>? reportDiagnostic)
{
_context = context;
_analyzer = analyzer;
_featureContext = featureContext;
_typeNameResolver = typeNameResolver;
_owningSymbol = owningSymbol;
Expand Down Expand Up @@ -68,48 +68,19 @@ private void ProcessGenericArgumentDataFlow(
ImmutableArray<ITypeSymbol> typeArguments,
ImmutableArray<ITypeParameterSymbol> typeParameters)
{
var diagnosticContext = new DiagnosticContext(_location, _reportDiagnostic);
for (int i = 0; i < typeArguments.Length; i++)
{
var typeArgument = typeArguments[i];
var typeParameter = typeParameters[i];

// Process new() constraint: if present, check Requires* on the public parameterless constructor
// And that also takes care of any DynamicallyAccessedMembers.PublicParameterlessConstructor.
if (typeParameter.HasConstructorConstraint && typeArgument is INamedTypeSymbol namedTypeArg && namedTypeArg.InstanceConstructors.Length > 0)
{
var paramlessPublicCtor = namedTypeArg.InstanceConstructors.FirstOrDefault(ctor => ctor.Parameters.IsEmpty && ctor.DeclaredAccessibility == Accessibility.Public);
if (paramlessPublicCtor is not null)
{
foreach (var analyzer in _context.EnabledRequiresAnalyzers)
{
var attrName = analyzer.RequiresAttributeFullyQualifiedName;

if (_featureContext.IsEnabled(attrName))
continue;

analyzer.CheckAndCreateRequiresDiagnostic(paramlessPublicCtor, _owningSymbol, incompatibleMembers: ImmutableArray<ISymbol>.Empty, diagnosticContext);
}
}
}

var parameterRequirements = typeParameter.GetDynamicallyAccessedMemberTypes();
// Avoid duplicate warnings for new() and DAMT.PublicParameterlessConstructor
if (typeParameter.HasConstructorConstraint)
parameterRequirements &= ~DynamicallyAccessedMemberTypes.PublicParameterlessConstructor;

// Apply annotations to the generic argument
var genericParameterValue = new GenericParameterValue(typeParameter, parameterRequirements);
if (_context.EnableTrimAnalyzer &&
!_owningSymbol.IsInRequiresUnreferencedCodeAttributeScope(out _) &&
!_featureContext.IsEnabled(RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute) &&
genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None)
{
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol(typeArgument)!;
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer(_reportDiagnostic, _typeNameResolver, typeHierarchyType: null);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(_context, _featureContext, _typeNameResolver, _location, _reportDiagnostic, reflectionAccessAnalyzer, _owningSymbol);
requireDynamicallyAccessedMembersAction.Invoke(genericArgumentValue, genericParameterValue);
}
_analyzer?.ProcessGenericInstantiation(
typeArgument,
typeParameter,
_featureContext,
_typeNameResolver,
_owningSymbol,
_location,
_reportDiagnostic);

// Recursively process generic argument data flow on the generic argument if it itself is generic
if (typeArgument is INamedTypeSymbol namedTypeArgument && namedTypeArgument.IsGenericType)
Expand Down
Loading
Loading