|
// 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.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker;
using Mono.Linker.Dataflow;
using Mono.Linker.Steps;
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
readonly LinkContext _context;
readonly Instruction _operation;
readonly MarkStep _markStep;
readonly ReflectionMarker _reflectionMarker;
readonly MethodDefinition _callingMethodDefinition;
public HandleCallAction (
LinkContext context,
Instruction operation,
MarkStep markStep,
ReflectionMarker reflectionMarker,
in DiagnosticContext diagnosticContext,
MethodDefinition callingMethodDefinition)
{
_context = context;
_operation = operation;
_isNewObj = operation.OpCode == OpCodes.Newobj;
_markStep = markStep;
_reflectionMarker = reflectionMarker;
_diagnosticContext = diagnosticContext;
_callingMethodDefinition = callingMethodDefinition;
_annotations = context.Annotations.FlowAnnotations;
_requireDynamicallyAccessedMembersAction = new (context, reflectionMarker, diagnosticContext);
}
private partial bool TryHandleIntrinsic (
MethodProxy calledMethod,
MultiValue instanceValue,
IReadOnlyList<MultiValue> argumentValues,
IntrinsicId intrinsicId,
out MultiValue? methodReturnValue)
{
MultiValue? maybeMethodReturnValue = methodReturnValue = null;
switch (intrinsicId) {
case IntrinsicId.None: {
if (ReflectionMethodBodyScanner.IsPInvokeDangerous (calledMethod.Definition, _context, out bool comDangerousMethod)) {
Debug.Assert (comDangerousMethod); // Currently COM dangerous is the only one we detect
_diagnosticContext.AddDiagnostic (DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed, calledMethod.GetDisplayName ());
}
if (_context.Annotations.DoesMethodRequireUnreferencedCode (calledMethod.Definition, out RequiresUnreferencedCodeAttribute? requiresUnreferencedCode))
MarkStep.ReportRequiresUnreferencedCode (calledMethod.GetDisplayName (), requiresUnreferencedCode, _diagnosticContext);
return TryHandleSharedIntrinsic (calledMethod, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
}
case IntrinsicId.TypeDelegator_Ctor: {
// This is an identity function for analysis purposes
if (_operation.OpCode == OpCodes.Newobj)
AddReturnValue (argumentValues[0]);
}
break;
case IntrinsicId.Array_Empty: {
AddReturnValue (ArrayValue.Create (0, ((GenericInstanceMethod) calledMethod.Method).GenericArguments[0]));
}
break;
case IntrinsicId.Array_CreateInstance:
case IntrinsicId.Enum_GetValues:
case IntrinsicId.Marshal_SizeOf:
case IntrinsicId.Marshal_OffsetOf:
case IntrinsicId.Marshal_PtrToStructure:
case IntrinsicId.Marshal_DestroyStructure:
case IntrinsicId.Marshal_GetDelegateForFunctionPointer:
case IntrinsicId.Assembly_get_Location:
case IntrinsicId.Assembly_GetFile:
case IntrinsicId.Assembly_GetFiles:
case IntrinsicId.AssemblyName_get_CodeBase:
case IntrinsicId.AssemblyName_get_EscapedCodeBase:
case IntrinsicId.RuntimeReflectionExtensions_GetMethodInfo:
case IntrinsicId.Delegate_get_Method:
// These intrinsics are not interesting for trimmer (they are interesting for AOT and that's why they are recognized)
break;
//
// System.Object
//
// GetType()
//
case IntrinsicId.Object_GetType: {
if (instanceValue.IsEmpty ()) {
AddReturnValue (MultiValueLattice.Top);
break;
}
foreach (var valueNode in instanceValue.AsEnumerable ()) {
// Note that valueNode can be statically typed in IL as some generic argument type.
// For example:
// void Method<T>(T instance) { instance.GetType().... }
// 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.
TypeReference? staticType = (valueNode as IValueWithStaticType)?.StaticType?.Type;
if (staticType?.IsByReference == true)
staticType = ((ByReferenceType) staticType).ElementType;
TypeDefinition? staticTypeDef = staticType?.ResolveToTypeDefinition (_context);
if (staticType is null || staticTypeDef is null) {
DynamicallyAccessedMemberTypes annotation = default;
if (staticType is GenericParameter genericParam && genericParam.HasConstraints) {
foreach (var constraint in genericParam.Constraints) {
if (constraint.ConstraintType.IsTypeOf ("System", "Enum"))
annotation = DynamicallyAccessedMemberTypes.PublicFields;
}
}
if (annotation != default) {
AddReturnValue (_context.Annotations.FlowAnnotations.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 (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj));
}
} else if (staticTypeDef.IsSealed || staticTypeDef.IsTypeOf ("System", "Delegate") || staticTypeDef.IsTypeOf ("System", "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, _context)));
} else if (staticTypeDef.IsTypeOf ("System", "Enum")) {
AddReturnValue (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj, DynamicallyAccessedMemberTypes.PublicFields));
} else {
// Make sure the type is marked (this will mark it as used via reflection, which is sort of true)
// This should already be true for most cases (method params, fields, ...), but just in case
_reflectionMarker.MarkType (_diagnosticContext.Origin, staticType);
var annotation = _markStep.DynamicallyAccessedMembersTypeHierarchy
.ApplyDynamicallyAccessedMembersToTypeHierarchy (staticTypeDef);
// Return a value which is "unknown type" with annotation. For now we'll use the return value node
// for the method, which means we're loosing the information about which staticType this
// started with. For now we don't need it, but we can add it later on.
AddReturnValue (_context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethod, _isNewObj, annotation));
}
}
}
break;
// Note about Activator.CreateInstance<T>
// There are 2 interesting cases:
// - The generic argument for T is either specific type or annotated - in that case generic instantiation will handle this
// since from .NET 6+ the T is annotated with PublicParameterlessConstructor annotation, so the trimming tools would apply this as for any other method.
// - The generic argument for T is unannotated type - the generic instantiantion handling has a special case for handling PublicParameterlessConstructor requirement
// in such that if the generic argument type has the "new" constraint it will not warn (as it is effectively the same thing semantically).
// For all other cases, the trimming tools would have already produced a warning.
default:
return false;
}
methodReturnValue = maybeMethodReturnValue;
return true;
void AddReturnValue (MultiValue value)
{
maybeMethodReturnValue = (maybeMethodReturnValue is null) ? value : MultiValueLattice.Meet ((MultiValue) maybeMethodReturnValue, value);
}
}
private partial bool MethodIsTypeConstructor (MethodProxy method)
{
if (!method.Definition.IsConstructor)
return false;
TypeDefinition? type = method.Definition.DeclaringType;
while (type is not null) {
if (type.IsTypeOf (WellKnownType.System_Type))
return true;
type = _context.Resolve (type.BaseType);
}
return false;
}
private partial IEnumerable<SystemReflectionMethodBaseValue> GetMethodsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
{
foreach (var method in type.Type.GetMethodsOnTypeHierarchy (_context, m => m.Name == name, bindingFlags)) {
if (MethodProxy.TryCreate (method, _context, out MethodProxy? methodProxy))
yield return new SystemReflectionMethodBaseValue (methodProxy.Value);
}
}
private partial IEnumerable<SystemTypeValue> GetNestedTypesOnType (TypeProxy type, string name, BindingFlags? bindingFlags)
{
foreach (var nestedType in type.Type.GetNestedTypesOnType (_context, t => t.Name == name, bindingFlags))
yield return new SystemTypeValue (new TypeProxy (nestedType, _context));
}
private partial bool TryGetBaseType (TypeProxy type, out TypeProxy? baseType)
{
if (type.Type.ResolveToTypeDefinition (_context)?.BaseType is TypeReference baseTypeRef && _context.TryResolve (baseTypeRef) is TypeDefinition baseTypeDefinition) {
baseType = new TypeProxy (baseTypeDefinition, _context);
return true;
}
baseType = null;
return false;
}
private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType)
{
var resolvedAssembly = _context.TryResolve (assemblyName);
if (resolvedAssembly == null) {
_diagnosticContext.AddDiagnostic (DiagnosticId.UnresolvedAssemblyInCreateInstance,
assemblyName,
calledMethod.GetDisplayName ());
resolvedType = default;
return false;
}
if (!_reflectionMarker.TryResolveTypeNameAndMark (resolvedAssembly, typeName, _diagnosticContext, out TypeReference? foundType)) {
// It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case
// Note that we did find the assembly, so it's not a ILLink config problem, it's either intentional, or wrong versions of assemblies
// but ILLink can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user
// might expect an exception to be thrown.
resolvedType = default;
return false;
}
resolvedType = new TypeProxy (foundType, _context);
return true;
}
private partial void MarkStaticConstructor (TypeProxy type)
=> _reflectionMarker.MarkStaticConstructor (_diagnosticContext.Origin, type.Type);
private partial void MarkEventsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
=> _reflectionMarker.MarkEventsOnTypeHierarchy (_diagnosticContext.Origin, type.Type, e => e.Name == name, bindingFlags);
private partial void MarkFieldsOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
=> _reflectionMarker.MarkFieldsOnTypeHierarchy (_diagnosticContext.Origin, type.Type, f => f.Name == name, bindingFlags);
private partial void MarkPropertiesOnTypeHierarchy (TypeProxy type, string name, BindingFlags? bindingFlags)
=> _reflectionMarker.MarkPropertiesOnTypeHierarchy (_diagnosticContext.Origin, type.Type, p => p.Name == name, bindingFlags);
private partial void MarkPublicParameterlessConstructorOnType (TypeProxy type)
=> _reflectionMarker.MarkConstructorsOnType (_diagnosticContext.Origin, type.Type, m => m.IsPublic && !m.HasMetadataParameters ());
private partial void MarkConstructorsOnType (TypeProxy type, BindingFlags? bindingFlags, int? parameterCount)
=> _reflectionMarker.MarkConstructorsOnType (_diagnosticContext.Origin, type.Type, (parameterCount == null) ? null : m => m.GetMetadataParametersCount () == parameterCount, bindingFlags);
private partial void MarkMethod (MethodProxy method)
=> _reflectionMarker.MarkMethod (_diagnosticContext.Origin, method.Method);
private partial void MarkType (TypeProxy type)
=> _reflectionMarker.MarkType (_diagnosticContext.Origin, type.Type);
private partial bool MarkAssociatedProperty (MethodProxy method)
{
if (method.Definition.TryGetProperty (out PropertyDefinition? propertyDefinition)) {
_reflectionMarker.MarkProperty (_diagnosticContext.Origin, propertyDefinition);
return true;
}
return false;
}
private partial string GetContainingSymbolDisplayName () => _callingMethodDefinition.GetDisplayName ();
}
}
|