File: TrimAnalysis\ReflectionAccessAnalyzer.cs
Web Access
Project: src\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer)
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using Microsoft.CodeAnalysis;
 
namespace ILLink.RoslynAnalyzer.TrimAnalysis
{
	readonly struct ReflectionAccessAnalyzer
	{
		readonly Action<Diagnostic>? _reportDiagnostic;
		readonly INamedTypeSymbol? _typeHierarchyType;
 
		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)
		{
			typeSymbol = typeSymbol.OriginalDefinition;
			foreach (var member in typeSymbol.GetDynamicallyAccessedMembers (requiredMemberTypes, declaredOnly)) {
				switch (member) {
				case IMethodSymbol method:
					GetReflectionAccessDiagnosticsForMethod (location, method);
					break;
				case IFieldSymbol field:
					GetDiagnosticsForField (location, field);
					break;
				case IPropertySymbol property:
					GetReflectionAccessDiagnosticsForProperty (location, property);
					break;
				/* Skip Type and InterfaceImplementation marking since doesnt seem relevant for diagnostic generation
				case ITypeSymbol nestedType:
					MarkType (location, nestedType);
					break;
				case InterfaceImplementation interfaceImplementation:
					MarkInterfaceImplementation (location, interfaceImplementation, dependencyKind);
					break;
				*/
				case IEventSymbol @event:
					GetDiagnosticsForEvent (location, @event);
					break;
				}
			}
		}
 
		internal void GetReflectionAccessDiagnosticsForEventsOnTypeHierarchy (Location location, ITypeSymbol typeSymbol, string name, BindingFlags? bindingFlags)
		{
			foreach (var @event in typeSymbol.GetEventsOnTypeHierarchy (e => e.Name == name, bindingFlags))
				GetDiagnosticsForEvent (location, @event);
		}
 
		internal void GetReflectionAccessDiagnosticsForFieldsOnTypeHierarchy (Location location, ITypeSymbol typeSymbol, string name, BindingFlags? bindingFlags)
		{
			foreach (var field in typeSymbol.GetFieldsOnTypeHierarchy (f => f.Name == name, bindingFlags))
				GetDiagnosticsForField (location, field);
		}
 
		internal void GetReflectionAccessDiagnosticsForPropertiesOnTypeHierarchy (Location location, ITypeSymbol typeSymbol, string name, BindingFlags? bindingFlags)
		{
			foreach (var prop in typeSymbol.GetPropertiesOnTypeHierarchy (p => p.Name == name, bindingFlags))
				GetReflectionAccessDiagnosticsForProperty (location, prop);
		}
 
		internal void GetReflectionAccessDiagnosticsForConstructorsOnType (Location location, ITypeSymbol typeSymbol, BindingFlags? bindingFlags, int? parameterCount)
		{
			foreach (var c in typeSymbol.GetConstructorsOnType (filter: parameterCount.HasValue ? c => c.Parameters.Length == parameterCount.Value : null, bindingFlags: bindingFlags))
				GetReflectionAccessDiagnosticsForMethod (location, c);
		}
 
		internal void GetReflectionAccessDiagnosticsForPublicParameterlessConstructor (Location location, ITypeSymbol typeSymbol)
		{
			foreach (var c in typeSymbol.GetConstructorsOnType (filter: m => (m.DeclaredAccessibility == Accessibility.Public) && m.Parameters.Length == 0))
				GetReflectionAccessDiagnosticsForMethod (location, c);
		}
 
		void ReportRequiresUnreferencedCodeDiagnostic (Location location, AttributeData requiresAttributeData, ISymbol member)
		{
			var message = RequiresUnreferencedCodeUtils.GetMessageFromAttribute (requiresAttributeData);
			var url = RequiresAnalyzerBase.GetUrlFromAttribute (requiresAttributeData);
			var diagnosticContext = new DiagnosticContext (location, _reportDiagnostic);
			diagnosticContext.AddDiagnostic (DiagnosticId.RequiresUnreferencedCode, member.GetDisplayName (), message, url);
		}
 
		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);
			if (methodSymbol.IsVirtual && FlowAnnotations.GetMethodReturnValueAnnotation (methodSymbol) != DynamicallyAccessedMemberTypes.None) {
				diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection, methodSymbol.GetDisplayName ());
			} else {
				foreach (var parameter in methodSymbol.GetParameters ()) {
					if (FlowAnnotations.GetMethodParameterAnnotation (parameter) != DynamicallyAccessedMemberTypes.None) {
						diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection, methodSymbol.GetDisplayName ());
						break;
					}
				}
			}
		}
 
		internal void GetReflectionAccessDiagnosticsForProperty (Location location, IPropertySymbol propertySymbol)
		{
			if (propertySymbol.SetMethod is not null)
				GetReflectionAccessDiagnosticsForMethod (location, propertySymbol.SetMethod);
			if (propertySymbol.GetMethod is not null)
				GetReflectionAccessDiagnosticsForMethod (location, propertySymbol.GetMethod);
		}
 
		void GetDiagnosticsForEvent (Location location, IEventSymbol eventSymbol)
		{
			if (eventSymbol.AddMethod is not null)
				GetReflectionAccessDiagnosticsForMethod (location, eventSymbol.AddMethod);
			if (eventSymbol.RemoveMethod is not null)
				GetReflectionAccessDiagnosticsForMethod (location, eventSymbol.RemoveMethod);
			if (eventSymbol.RaiseMethod is not null)
				GetReflectionAccessDiagnosticsForMethod (location, eventSymbol.RaiseMethod);
		}
 
		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);
 
			if (FlowAnnotations.GetFieldAnnotation (fieldSymbol) != DynamicallyAccessedMemberTypes.None) {
				var diagnosticContext = new DiagnosticContext (location, _reportDiagnostic);
				diagnosticContext.AddDiagnostic (DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName ());
			}
		}
	}
}