|
// 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 ILLink.RoslynAnalyzer;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
#nullable enable
namespace ILLink.Shared.TrimAnalysis
{
public sealed partial class FlowAnnotations
{
// In the analyzer there's no stateful data the flow annotations need to store
// so we just create a singleton on demand.
private static readonly Lazy<FlowAnnotations> _instance = new(() => new FlowAnnotations(), isThreadSafe: true);
public static FlowAnnotations Instance { get => _instance.Value; }
// Hide the default .ctor so that only the one singleton instance can be created
private FlowAnnotations() { }
public static bool RequiresDataFlowAnalysis(IMethodSymbol method)
{
if (GetMethodReturnValueAnnotation(method) != DynamicallyAccessedMemberTypes.None)
return true;
foreach (var param in method.GetParameters())
{
if (GetMethodParameterAnnotation(param) != DynamicallyAccessedMemberTypes.None)
return true;
}
return false;
}
internal static bool ShouldWarnWhenAccessedForReflection(ISymbol symbol) =>
symbol switch
{
IMethodSymbol method => ShouldWarnWhenAccessedForReflection(method),
IFieldSymbol field => ShouldWarnWhenAccessedForReflection(field),
_ => false
};
private 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); }
// }
//
// 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;
}
}
private static bool ShouldWarnWhenAccessedForReflection(IFieldSymbol field)
{
return GetFieldAnnotation(field) != DynamicallyAccessedMemberTypes.None;
}
internal static DynamicallyAccessedMemberTypes GetFieldAnnotation(IFieldSymbol field)
{
if (!field.OriginalDefinition.Type.IsTypeInterestingForDataflow(isByRef: field.RefKind is not RefKind.None))
return DynamicallyAccessedMemberTypes.None;
return field.GetDynamicallyAccessedMemberTypes();
}
internal static DynamicallyAccessedMemberTypes GetBackingFieldAnnotation(IPropertySymbol property)
{
if (!property.OriginalDefinition.Type.IsTypeInterestingForDataflow(isByRef: false))
return DynamicallyAccessedMemberTypes.None;
return property.GetDynamicallyAccessedMemberTypes();
}
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)
{
if (param.IsImplicitThis)
{
if (!param.Method.Method.ContainingType.IsTypeInterestingForDataflow(isByRef: false))
return DynamicallyAccessedMemberTypes.None;
return param.Method.Method.GetDynamicallyAccessedMemberTypes();
}
IParameterSymbol parameter = param.ParameterSymbol!;
bool isByRef = parameter.RefKind is not RefKind.None;
if (!parameter.OriginalDefinition.Type.IsTypeInterestingForDataflow(isByRef))
return DynamicallyAccessedMemberTypes.None;
var damt = parameter.GetDynamicallyAccessedMemberTypes();
var parameterMethod = (IMethodSymbol)parameter.ContainingSymbol;
Debug.Assert(parameterMethod != null);
// If there are conflicts between the setter and the property annotation,
// the setter annotation wins. (But DAMT.None is ignored)
// Is this a property setter `value` parameter?
if (parameterMethod!.MethodKind == MethodKind.PropertySet
&& damt == DynamicallyAccessedMemberTypes.None
&& parameter.Ordinal == parameterMethod.Parameters.Length - 1)
{
var property = (IPropertySymbol)parameterMethod.AssociatedSymbol!;
Debug.Assert(property != null);
damt = property!.GetDynamicallyAccessedMemberTypes();
}
return damt;
}
public static DynamicallyAccessedMemberTypes GetMethodReturnValueAnnotation(IMethodSymbol method)
{
if (!method.OriginalDefinition.ReturnType.IsTypeInterestingForDataflow(isByRef: method.ReturnsByRef))
return DynamicallyAccessedMemberTypes.None;
var returnDamt = method.GetDynamicallyAccessedMemberTypesOnReturnType();
// Is this a property getter?
// If there are conflicts between the getter and the property annotation,
// the getter annotation wins. (But DAMT.None is ignored)
if (method.MethodKind is MethodKind.PropertyGet && returnDamt == DynamicallyAccessedMemberTypes.None)
{
var property = (IPropertySymbol)method.AssociatedSymbol!;
Debug.Assert(property != null);
returnDamt = property!.GetDynamicallyAccessedMemberTypes();
}
return returnDamt;
}
public static DynamicallyAccessedMemberTypes GetTypeAnnotation(ITypeSymbol type)
{
var typeAnnotation = type.GetDynamicallyAccessedMemberTypes();
ITypeSymbol? baseType = type.BaseType;
while (baseType != null)
{
typeAnnotation |= baseType.GetDynamicallyAccessedMemberTypes();
baseType = baseType.BaseType;
}
foreach (var interfaceType in type.AllInterfaces)
{
typeAnnotation |= interfaceType.GetDynamicallyAccessedMemberTypes();
}
return typeAnnotation;
}
#pragma warning disable CA1822 // Mark members as static - the other partial implementations might need to be instance methods
// TODO: This is relatively expensive on the analyzer since it doesn't cache the annotation information
// For trimming tools this is an optimization to avoid the heavy lifting of analysis if there's no point
// it's unclear if the same optimization makes sense for the analyzer.
internal partial bool MethodRequiresDataFlowAnalysis(MethodProxy method)
=> RequiresDataFlowAnalysis(method.Method);
internal partial MethodReturnValue GetMethodReturnValue(MethodProxy method, bool isNewObj, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
=> new MethodReturnValue(method.Method, isNewObj, dynamicallyAccessedMemberTypes);
internal partial MethodReturnValue GetMethodReturnValue(MethodProxy method, bool isNewObj)
=> GetMethodReturnValue(method, isNewObj, GetMethodReturnValueAnnotation(method.Method));
internal partial GenericParameterValue GetGenericParameterValue(GenericParameterProxy genericParameter, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
=> new GenericParameterValue(genericParameter.TypeParameterSymbol, dynamicallyAccessedMemberTypes);
internal partial GenericParameterValue GetGenericParameterValue(GenericParameterProxy genericParameter)
=> new GenericParameterValue(genericParameter.TypeParameterSymbol);
internal partial MethodParameterValue GetMethodThisParameterValue(MethodProxy method, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
{
if (!method.HasImplicitThis())
throw new InvalidOperationException($"Cannot get 'this' parameter of method {method.GetDisplayName()} with no 'this' parameter.");
return GetMethodParameterValue(new ParameterProxy(method, (ParameterIndex)0), dynamicallyAccessedMemberTypes);
}
internal partial MethodParameterValue GetMethodThisParameterValue(MethodProxy method)
{
if (!method.HasImplicitThis())
throw new InvalidOperationException($"Cannot get 'this' parameter of method {method.GetDisplayName()} with no 'this' parameter.");
ParameterProxy param = new(method, (ParameterIndex)0);
var damt = GetMethodParameterAnnotation(param);
return GetMethodParameterValue(new ParameterProxy(method, (ParameterIndex)0), damt);
}
internal MethodParameterValue GetMethodParameterValue(MethodProxy method, ParameterIndex parameterIndex, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
=> new MethodParameterValue(new(method, parameterIndex), dynamicallyAccessedMemberTypes);
internal partial MethodParameterValue GetMethodParameterValue(ParameterProxy param)
=> new MethodParameterValue(param, GetMethodParameterAnnotation(param));
internal partial MethodParameterValue GetMethodParameterValue(ParameterProxy param, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
=> new MethodParameterValue(param, dynamicallyAccessedMemberTypes);
#pragma warning restore CA1822
}
}
|