File: TrimAnalysis\HandleCallAction.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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.RoslynAnalyzer;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.RoslynAnalyzer.TrimAnalysis;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Operations;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;
 
namespace ILLink.Shared.TrimAnalysis
{
	internal partial struct HandleCallAction
	{
#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
#pragma warning disable IDE0060 // Unused parameters - the other partial implementation may need the parameter
 
		readonly ISymbol _owningSymbol;
		readonly IOperation _operation;
		readonly ReflectionAccessAnalyzer _reflectionAccessAnalyzer;
		ValueSetLattice<SingleValue> _multiValueLattice;
 
		public HandleCallAction (
			Location location,
			ISymbol owningSymbol,
			IOperation operation,
			ValueSetLattice<SingleValue> multiValueLattice,
			Action<Diagnostic>? reportDiagnostic)
		{
			_owningSymbol = owningSymbol;
			_operation = operation;
			_isNewObj = operation.Kind == OperationKind.ObjectCreation;
			_diagnosticContext = new DiagnosticContext (location, reportDiagnostic);
			_annotations = FlowAnnotations.Instance;
			_reflectionAccessAnalyzer = new (reportDiagnostic, typeHierarchyType: null);
			_requireDynamicallyAccessedMembersAction = new (_diagnosticContext, _reflectionAccessAnalyzer);
			_multiValueLattice = multiValueLattice;
		}
 
		private partial bool TryHandleIntrinsic (
			MethodProxy calledMethod,
			MultiValue instanceValue,
			IReadOnlyList<MultiValue> argumentValues,
			IntrinsicId intrinsicId,
			out MultiValue? methodReturnValue)
		{
			MultiValue? maybeMethodReturnValue = methodReturnValue = null;
			ValueSetLattice<SingleValue> multiValueLattice = _multiValueLattice;
 
			switch (intrinsicId) {
			case IntrinsicId.Array_Empty:
				AddReturnValue (ArrayValue.Create (0));
				break;
 
			case IntrinsicId.TypeDelegator_Ctor:
				if (_operation is IObjectCreationOperation)
					AddReturnValue (argumentValues[0]);
 
				break;
 
			case IntrinsicId.Object_GetType: {
					if (instanceValue.IsEmpty ()) {
						AddReturnValue (MultiValueLattice.Top);
						break;
					}
 
					foreach (var valueNode in instanceValue.AsEnumerable ()) {
						// Note that valueNode can be statically typed as some generic argument type.
						// For example:
						//   void Method<T>(T instance) { instance.GetType().... }
						// But it could be that T is annotated with for example PublicMethods:
						//   void Method<[DAM(PublicMethods)] T>(T instance) { instance.GetType().GetMethod("Test"); }
						// In this case it's in theory possible to handle it, by treating the T basically as a base class
						// for the actual type of "instance". But the analysis for this would be pretty complicated (as the marking
						// has to happen on the callsite, which doesn't know that GetType() will be used...).
						// For now we're intentionally ignoring this case - it will produce a warning.
						// The counter example is:
						//   Method<Base>(new Derived);
						// In this case to get correct results, trimmer would have to mark all public methods on Derived. Which
						// currently it won't do.
 
						ITypeSymbol? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
						ITypeSymbol? staticTypeDef = staticType?.OriginalDefinition;
						if (staticType is null || staticTypeDef is null || staticType is ITypeParameterSymbol) {
							DynamicallyAccessedMemberTypes annotation = default;
							if (staticType is ITypeParameterSymbol genericParam) {
								foreach (var constraintType in genericParam.ConstraintTypes) {
									if (constraintType.IsTypeOf ("System", "Enum"))
										annotation = DynamicallyAccessedMemberTypes.PublicFields;
								}
							}
 
							if (annotation != default) {
								AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj, annotation));
							} else {
								// We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations"
								AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj));
							}
						} else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate") || staticType.TypeKind == TypeKind.Array) {
							// We can treat this one the same as if it was a typeof() expression
 
							// We can allow Object.GetType to be modeled as System.Delegate because we keep all methods
							// on delegates anyway so reflection on something this approximation would miss is actually safe.
 
							// We can also treat all arrays as "sealed" since it's not legal to derive from Array type (even though it is not sealed itself)
 
							// We ignore the fact that the type can be annotated (see below for handling of annotated types)
							// This means the annotations (if any) won't be applied - instead we rely on the exact knowledge
							// of the type. So for example even if the type is annotated with PublicMethods
							// but the code calls GetProperties on it - it will work - mark properties, don't mark methods
							// since we ignored the fact that it's annotated.
							// This can be seen a little bit as a violation of the annotation, but we already have similar cases
							// where a parameter is annotated and if something in the method sets a specific known type to it
							// we will also make it just work, even if the annotation doesn't match the usage.
							AddReturnValue (new SystemTypeValue (new (staticType)));
						} else if (staticType.IsTypeOf ("System", "Enum")) {
							AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj, DynamicallyAccessedMemberTypes.PublicFields));
						} else {
							var annotation = FlowAnnotations.GetTypeAnnotation (staticType);
							AddReturnValue (FlowAnnotations.Instance.GetMethodReturnValue (calledMethod, _isNewObj, annotation));
						}
					}
				break;
			}
 
			// Some intrinsics are unimplemented by the analyzer.
			// These will fall back to the usual return-value handling.
			case IntrinsicId.Array_CreateInstance:
			case IntrinsicId.Assembly_GetFile:
			case IntrinsicId.Assembly_GetFiles:
			case IntrinsicId.AssemblyName_get_EscapedCodeBase:
			case IntrinsicId.Assembly_get_Location:
			case IntrinsicId.AssemblyName_get_CodeBase:
			case IntrinsicId.Delegate_get_Method:
			case IntrinsicId.Enum_GetValues:
			case IntrinsicId.Marshal_DestroyStructure:
			case IntrinsicId.Marshal_GetDelegateForFunctionPointer:
			case IntrinsicId.Marshal_OffsetOf:
			case IntrinsicId.Marshal_PtrToStructure:
			case IntrinsicId.Marshal_SizeOf:
			case IntrinsicId.RuntimeReflectionExtensions_GetMethodInfo:
				break;
 
			default:
				return false;
			}
 
			methodReturnValue = maybeMethodReturnValue;
			return true;
 
			void AddReturnValue (MultiValue value)
			{
				maybeMethodReturnValue = (maybeMethodReturnValue is null) ? value : multiValueLattice.Meet ((MultiValue) maybeMethodReturnValue, value);
			}
		}
 
		private partial IEnumerable<SystemReflectionMethodBaseValue> GetMethodsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
		{
			foreach (var method in type.Type.GetMethodsOnTypeHierarchy (m => m.Name == name, bindingFlags))
				yield return new SystemReflectionMethodBaseValue (new MethodProxy (method));
		}
 
		private partial IEnumerable<SystemTypeValue> GetNestedTypesOnType (TypeProxy type, string name, BindingFlags? bindingFlags)
		{
			foreach (var nestedType in type.Type.GetNestedTypesOnType (t => t.Name == name, bindingFlags))
				yield return new SystemTypeValue (new TypeProxy (nestedType));
		}
 
		private partial bool MethodIsTypeConstructor (MethodProxy method)
		{
			if (!method.Method.IsConstructor ())
				return false;
			var type = method.Method.ContainingType;
			while (type is not null) {
				if (type.IsTypeOf (WellKnownType.System_Type))
					return true;
				type = type.BaseType;
			}
			return false;
		}
 
		private partial bool TryGetBaseType (TypeProxy type, out TypeProxy? baseType)
		{
			if (type.Type.BaseType is not null) {
				baseType = new TypeProxy (type.Type.BaseType);
				return true;
			}
 
			baseType = null;
			return false;
		}
 
		private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType)
		{
			// Intentionally never resolve anything. Analyzer can really only see types from the current compilation unit. For other assemblies
			// it typically only sees reference assemblies and thus just public API. It's not worth (at least for now) to try to resolve
			// the assembly name and type name as it should be rare this is actually ever used and even rarer to have problems (Warnings).
			// In any case the trimmer will process this correctly as it has a global view.
			resolvedType = default;
			return false;
		}
 
		private partial void MarkStaticConstructor (TypeProxy type)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForConstructorsOnType (_diagnosticContext.Location, type.Type, BindingFlags.Static, parameterCount: 0);
 
		private partial void MarkEventsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForEventsOnTypeHierarchy (_diagnosticContext.Location, type.Type, name, bindingFlags);
 
		private partial void MarkFieldsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForFieldsOnTypeHierarchy (_diagnosticContext.Location, type.Type, name, bindingFlags);
 
		private partial void MarkPropertiesOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForPropertiesOnTypeHierarchy (_diagnosticContext.Location, type.Type, name, bindingFlags);
 
		private partial void MarkPublicParameterlessConstructorOnType (TypeProxy type)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForPublicParameterlessConstructor (_diagnosticContext.Location, type.Type);
 
		private partial void MarkConstructorsOnType (TypeProxy type, BindingFlags? bindingFlags, int? parameterCount)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForConstructorsOnType (_diagnosticContext.Location, type.Type, bindingFlags, parameterCount);
 
		private partial void MarkMethod (MethodProxy method)
			=> _reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForMethod (_diagnosticContext.Location, method.Method);
 
		// TODO: Does the analyzer need to do something here?
		private partial void MarkType (TypeProxy type) { }
 
		private partial bool MarkAssociatedProperty (MethodProxy method)
		{
			if (method.Method.MethodKind == MethodKind.PropertyGet || method.Method.MethodKind == MethodKind.PropertySet) {
				var property = (IPropertySymbol) method.Method.AssociatedSymbol!;
				Debug.Assert (property != null);
				_reflectionAccessAnalyzer.GetReflectionAccessDiagnosticsForProperty (_diagnosticContext.Location, property!);
				return true;
			}
 
			return false;
		}
 
		private partial string GetContainingSymbolDisplayName () => _operation.FindContainingSymbol (_owningSymbol).GetDisplayName ();
	}
}