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 numberDiff line numberDiff line change
Expand Up@@ -232,7 +232,7 @@ public bool ShouldWarnWhenAccessedForReflection(MethodDesc method)
// public override Type GetTypeWithFields() { return typeof(TestType); }
// }
//
// If TestType from above is trimmed, it may note have all its fields, and there would be no warnings generated.
// If TestType from above is trimmed, it may not have all its fields, and there would be no warnings generated.
// But there has to be code like this somewhere in the app, in order to generate the override:
// class RuntimeTypeGenerator
// {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,9 +325,6 @@ static bool IsDeclaredWithinType(TypeSystemEntity member, TypeDesc type)
var id = reportOnMember ? DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithDynamicallyAccessedMembers : DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers;
_logger.LogWarning(origin, id, _typeHierarchyDataFlowOrigin.GetDisplayName(), entity.GetDisplayName());
}

// We decided to not warn on reflection access to compiler-generated methods:
// https://github.com/dotnet/runtime/issues/85042
}

private void ReportRequires(in MessageOrigin origin, TypeSystemEntity entity, string requiresAttributeName, in CustomAttributeValue<TypeDesc> requiresAttribute)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,6 +39,10 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics ()
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnMethodReturnValueCanOnlyApplyToTypesOrStrings));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithRequiresUnreferencedCode));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithDynamicallyAccessedMembers));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeInRuntimeHelpersRunClassConstructor));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides));
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides));
Expand DownExpand Up@@ -75,7 +79,7 @@ void AddRange (DiagnosticId first, DiagnosticId last)

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => GetSupportedDiagnostics ();

static Location GetPrimaryLocation (ImmutableArray<Location>? locations) {
internal static Location GetPrimaryLocation (ImmutableArray<Location>? locations) {
if (locations is null)
return Location.None;

Expand DownExpand Up@@ -122,6 +126,8 @@ public override void Initialize (AnalysisContext context)

foreach (var interfaceType in type.Interfaces)
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow (location, interfaceType, context.ReportDiagnostic);

DynamicallyAccessedMembersTypeHierarchy.ApplyDynamicallyAccessedMembersToTypeHierarchy (location, type, context.ReportDiagnostic);
}, SymbolKind.NamedType);
context.RegisterSymbolAction (context => {
VerifyMemberOnlyApplyToTypesOrStrings (context, context.Symbol);
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
// 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 Microsoft.CodeAnalysis;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using ILLink.RoslynAnalyzer.TrimAnalysis;

namespace ILLink.RoslynAnalyzer
{
sealed class DynamicallyAccessedMembersTypeHierarchy
{
public static void ApplyDynamicallyAccessedMembersToTypeHierarchy (Location typeLocation, INamedTypeSymbol type, Action<Diagnostic> reportDiagnostic)
{
var annotation = FlowAnnotations.GetTypeAnnotation (type);

// We need to apply annotations to this type, and its base/interface types (recursively)
// But the annotations on base/interfaces may already be applied so we don't need to apply those
// again (and should avoid doing so as it would produce extra warnings).
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, type);
if (type.BaseType is INamedTypeSymbol baseType) {
var baseAnnotation = FlowAnnotations.GetTypeAnnotation (baseType);
var annotationToApplyToBase = Annotations.GetMissingMemberTypes (annotation, baseAnnotation);

// Apply any annotations that didn't exist on the base type to the base type.
// This may produce redundant warnings when the annotation is DAMT.All or DAMT.PublicConstructors and the base already has a
// subset of those annotations.
reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (typeLocation, baseType, annotationToApplyToBase, declaredOnly: false);
}

// Most of the DynamicallyAccessedMemberTypes don't select members on interfaces. We only need to apply
// annotations to interfaces separately if dealing with DAMT.All or DAMT.Interfaces.
if (annotation.HasFlag (DynamicallyAccessedMemberTypes.Interfaces))
{
var annotationToApplyToInterfaces = annotation == DynamicallyAccessedMemberTypes.All ? annotation : DynamicallyAccessedMemberTypes.Interfaces;
foreach (var iface in type.AllInterfaces) {
if (FlowAnnotations.GetTypeAnnotation (iface).HasFlag (annotationToApplyToInterfaces))
continue;

// Apply All or Interfaces to the interface type.
// DAMT.All may produce redundant warnings from implementing types, when the interface type already had some annotations.
reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (typeLocation, iface, annotationToApplyToInterfaces, declaredOnly: false);
Comment thread
jtschuster marked this conversation as resolved.
}
}

// The annotations this type inherited from its base types or interfaces should not produce
// warnings on the respective base/interface members, since those are already covered by applying
// the annotations to those types. So we only need to handle the members directly declared on this type.
reflectionAccessAnalyzer.GetReflectionAccessDiagnostics (typeLocation, type, annotation, declaredOnly: true);
}
}
}
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,97 @@ public static bool RequiresDataFlowAnalysis (IMethodSymbol method)
return false;
}

internal static bool ShouldWarnWhenAccessedForReflection (ISymbol symbol) =>
symbol switch {
IMethodSymbol method => ShouldWarnWhenAccessedForReflection (method),
IFieldSymbol field => ShouldWarnWhenAccessedForReflection (field),
_ => false
};

static bool ShouldWarnWhenAccessedForReflection (IMethodSymbol method)
{
bool? hasParameterAnnotation = null;
if (GetMethodReturnValueAnnotation (method) == DynamicallyAccessedMemberTypes.None) {
if (!HasParameterAnnotation (method))
return false;
hasParameterAnnotation = true;
}

// If the method only has annotation on the return value and it's not virtual avoid warning.
// Return value annotations are "consumed" by the caller of a method, and as such there is nothing
// wrong calling these dynamically. The only problem can happen if something overrides a virtual
// method with annotated return value at runtime - in this case the trimmer can't validate
// that the method will return only types which fulfill the annotation's requirements.
// For example:
// class BaseWithAnnotation
// {
// [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]
// public abstract Type GetTypeWithFields();
// }
//
// class UsingTheBase
// {
// public void PrintFields(Base base)
// {
// // No warning here - GetTypeWithFields is correctly annotated to allow GetFields on the return value.
// Console.WriteLine(string.Join(" ", base.GetTypeWithFields().GetFields().Select(f => f.Name)));
// }
// }
//
// If at runtime (through ref emit) something generates code like this:
// class DerivedAtRuntimeFromBase
// {
// // No point in adding annotation on the return value - nothing will look at it anyway
// // Trimming will not see this code, so there are no checks
// public override Type GetTypeWithFields() { return typeof(TestType); }
// }
Comment thread
jtschuster marked this conversation as resolved.
//
// If TestType from above is trimmed, it may not have all its fields, and there would be no warnings generated.
// But there has to be code like this somewhere in the app, in order to generate the override:
// class RuntimeTypeGenerator
// {
// public MethodInfo GetBaseMethod()
// {
// // This must warn - that the GetTypeWithFields has annotation on the return value
// return typeof(BaseWithAnnotation).GetMethod("GetTypeWithFields");
// }
// }

return method.IsVirtual || method.IsOverride || (hasParameterAnnotation ?? HasParameterAnnotation (method));

static bool HasParameterAnnotation (IMethodSymbol method) {
foreach (var param in method.GetParameters ()) {
if (GetMethodParameterAnnotation (param) != DynamicallyAccessedMemberTypes.None)
return true;
}
return false;
}
}

static bool ShouldWarnWhenAccessedForReflection (IFieldSymbol field)
{
return field.GetDynamicallyAccessedMemberTypes () != DynamicallyAccessedMemberTypes.None;
}

internal static DynamicallyAccessedMemberTypes GetTypeAnnotations (INamedTypeSymbol type)
{
DynamicallyAccessedMemberTypes typeAnnotation = type.GetDynamicallyAccessedMemberTypes ();

// Also inherit annotation from bases
INamedTypeSymbol? baseType = type.BaseType;
while (baseType is not null) {
typeAnnotation |= baseType.GetDynamicallyAccessedMemberTypes ();
baseType = baseType.BaseType;
}

// And inherit them from interfaces
foreach (INamedTypeSymbol interfaceType in type.AllInterfaces) {
typeAnnotation |= interfaceType.GetDynamicallyAccessedMemberTypes ();
}

return typeAnnotation;
}

internal static DynamicallyAccessedMemberTypes GetMethodParameterAnnotation (ParameterProxy param)
{
IMethodSymbol method = param.Method.Method;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,7 @@ static void ProcessGenericArgumentDataFlow (
var genericParameterValue = new GenericParameterValue (typeParameters[i]);
if (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) {
SingleValue genericArgumentValue = SingleValueExtensions.FromTypeSymbol (typeArgument)!;
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeHierarchyType: null);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
requireDynamicallyAccessedMembersAction.Invoke (genericArgumentValue, genericParameterValue);
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,7 +39,7 @@ public HandleCallAction (
_isNewObj = operation.Kind == OperationKind.ObjectCreation;
_diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
_annotations = FlowAnnotations.Instance;
_reflectionAccessAnalyzer = new (reportDiagnostic);
_reflectionAccessAnalyzer = new (reportDiagnostic, typeHierarchyType: null);
_requireDynamicallyAccessedMembersAction = new (_diagnosticContext, _reflectionAccessAnalyzer);
_multiValueLattice = multiValueLattice;
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.RoslynAnalyzer.DataFlow;
Expand All@@ -15,8 +15,13 @@ namespace ILLink.RoslynAnalyzer.TrimAnalysis
readonly struct ReflectionAccessAnalyzer
{
readonly Action<Diagnostic>? _reportDiagnostic;
readonly INamedTypeSymbol? _typeHierarchyType;

public ReflectionAccessAnalyzer (Action<Diagnostic>? reportDiagnostic) => _reportDiagnostic = reportDiagnostic;
public ReflectionAccessAnalyzer (Action<Diagnostic>? reportDiagnostic, INamedTypeSymbol? typeHierarchyType)
{
_reportDiagnostic = reportDiagnostic;
_typeHierarchyType = typeHierarchyType;
}

#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
internal void GetReflectionAccessDiagnostics (Location location, ITypeSymbol typeSymbol, DynamicallyAccessedMemberTypes requiredMemberTypes, bool declaredOnly = false)
Expand DownExpand Up@@ -88,13 +93,61 @@ void ReportRequiresUnreferencedCodeDiagnostic (Location location, AttributeData

internal void GetReflectionAccessDiagnosticsForMethod (Location location, IMethodSymbol methodSymbol)
{
if (_typeHierarchyType is not null) {
GetTypeHierarchyReflectionAccessDiagnostics (location, methodSymbol);
return;
}

if (methodSymbol.IsInRequiresUnreferencedCodeAttributeScope (out var requiresUnreferencedCodeAttributeData)) {
ReportRequiresUnreferencedCodeDiagnostic (location, requiresUnreferencedCodeAttributeData, methodSymbol);
} else {
GetDiagnosticsForReflectionAccessToDAMOnMethod (location, methodSymbol);
}
}

internal void GetTypeHierarchyReflectionAccessDiagnostics (Location location, ISymbol member)
{
Debug.Assert (member is IMethodSymbol or IFieldSymbol);

// Don't check whether the current scope is a RUC type or RUC method because these warnings
// are not suppressed in RUC scopes. Here the scope represents the DynamicallyAccessedMembers
// annotation on a type, not a callsite which uses the annotation. We always want to warn about
// possible reflection access indicated by these annotations.

Debug.Assert (_typeHierarchyType is not null);

static bool IsDeclaredWithinType (ISymbol member, INamedTypeSymbol type)
{
INamedTypeSymbol containingType = member.ContainingType;
while (containingType is not null) {
if (SymbolEqualityComparer.Default.Equals (containingType, type))
return true;

containingType = containingType.ContainingType;
}
return false;
}

var reportOnMember = IsDeclaredWithinType (member, _typeHierarchyType!);
if (reportOnMember)
location = DynamicallyAccessedMembersAnalyzer.GetPrimaryLocation (member.Locations);

var diagnosticContext = new DiagnosticContext (location, _reportDiagnostic);

if (member.IsInRequiresUnreferencedCodeAttributeScope (out AttributeData? requiresUnreferencedCodeAttribute)) {
var id = reportOnMember ? DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithRequiresUnreferencedCode : DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode;
diagnosticContext.AddDiagnostic (id, _typeHierarchyType!.GetDisplayName (),
member.GetDisplayName (),
MessageFormat.FormatRequiresAttributeMessageArg (RequiresUnreferencedCodeUtils.GetMessageFromAttribute (requiresUnreferencedCodeAttribute)),
MessageFormat.FormatRequiresAttributeMessageArg(RequiresAnalyzerBase.GetUrlFromAttribute (requiresUnreferencedCodeAttribute)));
}

if (FlowAnnotations.ShouldWarnWhenAccessedForReflection (member)) {
var id = reportOnMember ? DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithDynamicallyAccessedMembers : DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers;
diagnosticContext.AddDiagnostic (id, _typeHierarchyType!.GetDisplayName (), member.GetDisplayName ());
}
}

internal void GetDiagnosticsForReflectionAccessToDAMOnMethod (Location location, IMethodSymbol methodSymbol)
{
var diagnosticContext = new DiagnosticContext (location, _reportDiagnostic);
Expand DownExpand Up@@ -130,6 +183,11 @@ void GetDiagnosticsForEvent (Location location, IEventSymbol eventSymbol)

void GetDiagnosticsForField (Location location, IFieldSymbol fieldSymbol)
{
if (_typeHierarchyType is not null) {
GetTypeHierarchyReflectionAccessDiagnostics (location, fieldSymbol);
return;
}

if (fieldSymbol.TryGetRequiresUnreferencedCodeAttribute (out var requiresUnreferencedCodeAttributeData))
ReportRequiresUnreferencedCodeDiagnostic (location, requiresUnreferencedCodeAttributeData, fieldSymbol);

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -66,7 +66,7 @@ public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnosti
if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers)
throw new NotImplementedException ();

var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeHierarchyType: null);
var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (diagnosticContext, reflectionAccessAnalyzer);
requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers);
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -46,7 +46,7 @@ public TrimAnalysisReflectionAccessPattern Merge (
public void ReportDiagnostics (DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
{
var location = Operation.Syntax.GetLocation ();
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic);
var reflectionAccessAnalyzer = new ReflectionAccessAnalyzer (reportDiagnostic, typeHierarchyType: null);
if (context.EnableTrimAnalyzer &&
!OwningSymbol.IsInRequiresUnreferencedCodeAttributeScope (out _) &&
!FeatureContext.IsEnabled (RequiresUnreferencedCodeAnalyzer.FullyQualifiedRequiresUnreferencedCodeAttribute)) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -130,7 +130,7 @@ public bool ShouldWarnWhenAccessedForReflection (MethodDefinition method)
// public override Type GetTypeWithFields() { return typeof(TestType); }
// }
//
// If TestType from above is trimmed, it may note have all its fields, and there would be no warnings generated.
// If TestType from above is trimmed, it may not have all its fields, and there would be no warnings generated.
// But there has to be code like this somewhere in the app, in order to generate the override:
// class RuntimeTypeGenerator
// {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -148,6 +148,12 @@ public Task ObjectGetType ()
return RunTest ();
}

[Fact]
public Task ObjectGetTypeLibraryMode ()
{
return RunTest ();
}

[Fact]
public Task PropertyUsedViaReflection ()
{
Expand DownExpand Up@@ -178,18 +184,22 @@ public Task TypeDelegator ()
return RunTest ();
}

[Fact]
public Task TypeHierarchyLibraryModeSuppressions ()
{
return RunTest ();
}

[Fact]
public Task TypeHierarchyReflectionWarnings ()
{
// https://github.com/dotnet/runtime/issues/104742
return RunTest (allowMissingWarnings: true);
return RunTest ();
}

[Fact]
public Task TypeHierarchySuppressions ()
{
// https://github.com/dotnet/runtime/issues/104742
return RunTest (allowMissingWarnings: true);
return RunTest ();
}

[Fact]
Expand Down
Loading