|
// 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.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker.Steps;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;
namespace Mono.Linker.Dataflow
{
sealed class ReflectionMethodBodyScanner : MethodBodyScanner
{
readonly MarkStep _markStep;
MessageOrigin _origin;
readonly FlowAnnotations _annotations;
readonly ReflectionMarker _reflectionMarker;
public readonly TrimAnalysisPatternStore TrimAnalysisPatterns;
public static bool RequiresReflectionMethodBodyScannerForCallSite (LinkContext context, MethodReference calledMethod)
{
MethodDefinition? methodDefinition = context.TryResolve (calledMethod);
if (methodDefinition == null)
return false;
return Intrinsics.GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (methodDefinition) ||
context.Annotations.DoesMethodRequireUnreferencedCode (methodDefinition, out _) ||
IsPInvokeDangerous (methodDefinition, context, out _);
}
public static bool RequiresReflectionMethodBodyScannerForMethodBody (LinkContext context, MethodDefinition methodDefinition)
{
return Intrinsics.GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (methodDefinition);
}
public static bool RequiresReflectionMethodBodyScannerForAccess (LinkContext context, FieldReference field)
{
FieldDefinition? fieldDefinition = context.TryResolve (field);
if (fieldDefinition == null)
return false;
return context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (fieldDefinition);
}
public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, MessageOrigin origin)
: base (context)
{
_markStep = parent;
_origin = origin;
_annotations = context.Annotations.FlowAnnotations;
_reflectionMarker = new ReflectionMarker (context, parent, enabled: false);
TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context);
}
public override void InterproceduralScan (MethodIL methodIL)
{
base.InterproceduralScan (methodIL);
var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true);
TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep);
}
protected override void Scan (MethodIL methodIL, ref InterproceduralState interproceduralState)
{
_origin = new MessageOrigin (methodIL.Method);
base.Scan (methodIL, ref interproceduralState);
}
protected override void WarnAboutInvalidILInMethod (MethodBody method, int ilOffset)
{
// Serves as a debug helper to make sure valid IL is not considered invalid.
//
// The .NET Native compiler used to warn if it detected invalid IL during treeshaking,
// but the warnings were often triggered in autogenerated dead code of a major game engine
// and resulted in support calls. No point in warning. If the code gets exercised at runtime,
// an InvalidProgramException will likely be raised.
Debug.Fail ("Invalid IL or a bug in the scanner");
}
protected override ValueWithDynamicallyAccessedMembers GetMethodParameterValue (ParameterProxy parameter)
=> GetMethodParameterValue (parameter, _context.Annotations.FlowAnnotations.GetParameterAnnotation (parameter));
MethodParameterValue GetMethodParameterValue (ParameterProxy parameter, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
=> _annotations.GetMethodParameterValue (parameter, dynamicallyAccessedMemberTypes);
protected override MultiValue GetFieldValue (FieldReference field) => _annotations.GetFieldValue (field);
protected override MethodReturnValue GetReturnValue (MethodDefinition method) => _annotations.GetMethodReturnValue (method, isNewObj: false);
private void HandleStoreValueWithDynamicallyAccessedMembers (ValueWithDynamicallyAccessedMembers targetValue, Instruction operation, MultiValue sourceValue, int? parameterIndex)
{
if (targetValue.DynamicallyAccessedMemberTypes != 0) {
_origin = _origin.WithInstructionOffset (operation.Offset);
TrimAnalysisPatterns.Add (new TrimAnalysisAssignmentPattern (sourceValue, targetValue, _origin, parameterIndex));
}
}
protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore, int? parameterIndex)
=> HandleStoreValueWithDynamicallyAccessedMembers (field, operation, valueToStore, parameterIndex);
protected override void HandleStoreParameter (MethodDefinition method, MethodParameterValue parameter, Instruction operation, MultiValue valueToStore, int? parameterIndex)
=> HandleStoreValueWithDynamicallyAccessedMembers (parameter, operation, valueToStore, parameterIndex);
protected override void HandleReturnValue (MethodDefinition method, MethodReturnValue returnValue, Instruction operation, MultiValue valueToStore)
=> HandleStoreValueWithDynamicallyAccessedMembers (returnValue, operation, valueToStore, null);
public override MultiValue HandleCall (MethodBody callingMethodBody, MethodReference calledMethod, Instruction operation, ValueNodeList methodParams)
{
var reflectionProcessed = _markStep.ProcessReflectionDependency (callingMethodBody, operation);
if (reflectionProcessed) {
return UnknownValue.Instance;
}
Debug.Assert (callingMethodBody.Method == _origin.Provider);
var calledMethodDefinition = _context.TryResolve (calledMethod);
if (calledMethodDefinition == null) {
return UnknownValue.Instance;
}
_origin = _origin.WithInstructionOffset (operation.Offset);
MultiValue instanceValue;
ImmutableArray<MultiValue> arguments;
if (calledMethodDefinition.HasImplicitThis ()) {
instanceValue = methodParams[0];
arguments = methodParams.Skip (1).ToImmutableArray ();
} else {
instanceValue = MultiValueLattice.Top;
arguments = methodParams.ToImmutableArray ();
}
TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern (
operation,
calledMethod,
instanceValue,
arguments,
_origin
));
var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: false, _context);
return HandleCall (
operation,
calledMethod,
instanceValue,
arguments,
diagnosticContext,
_reflectionMarker,
_context,
_markStep);
}
public static MultiValue HandleCall (
Instruction operation,
MethodReference calledMethod,
MultiValue instanceValue,
ImmutableArray<MultiValue> argumentValues,
DiagnosticContext diagnosticContext,
ReflectionMarker reflectionMarker,
LinkContext context,
MarkStep markStep)
{
var origin = diagnosticContext.Origin;
if (!MethodProxy.TryCreate (calledMethod, context, out MethodProxy? calledMethodProxy)) {
Debug.Fail ("Should only be called for resolvable methods");
return UnknownValue.Instance;
}
var calledMethodDefinition = calledMethodProxy.Value.Definition;
var callingMethodDefinition = origin.Provider as MethodDefinition;
Debug.Assert (callingMethodDefinition != null);
bool requiresDataFlowAnalysis = context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition);
bool isNewObj = operation.OpCode.Code == Code.Newobj;
var annotatedMethodReturnValue = context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodProxy.Value, isNewObj);
Debug.Assert (requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None);
var handleCallAction = new HandleCallAction (context, operation, markStep, reflectionMarker, diagnosticContext, callingMethodDefinition);
var intrinsicId = Intrinsics.GetIntrinsicIdForMethod (calledMethodProxy.Value);
if (!handleCallAction.Invoke (calledMethodProxy.Value, instanceValue, argumentValues, intrinsicId, out MultiValue methodReturnValue))
throw new NotImplementedException ($"Unhandled intrinsic: {intrinsicId}");
return methodReturnValue;
}
static bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType, LinkContext context)
{
// This is best effort. One can likely find ways how to get COM without triggering these alarms.
// AsAny marshalling of a struct with an object-typed field would be one, for example.
// This logic roughly corresponds to MarshalInfo::MarshalInfo in CoreCLR,
// not trying to handle invalid cases and distinctions that are not interesting wrt
// "is this COM?" question.
NativeType nativeType = NativeType.None;
if (marshalInfoProvider.HasMarshalInfo) {
nativeType = marshalInfoProvider.MarshalInfo.NativeType;
}
if (nativeType == NativeType.IUnknown || nativeType == NativeType.IDispatch || nativeType == NativeType.IntF) {
// This is COM by definition
return true;
}
if (nativeType == NativeType.None) {
// Resolve will look at the element type
var parameterTypeDef = context.TryResolve (parameterType);
if (parameterTypeDef != null) {
if (parameterTypeDef.IsTypeOf (WellKnownType.System_Array)) {
// System.Array marshals as IUnknown by default
return true;
} else if (parameterTypeDef.IsTypeOf (WellKnownType.System_String) ||
parameterTypeDef.IsTypeOf ("System.Text", "StringBuilder")) {
// String and StringBuilder are special cased by interop
return false;
}
if (parameterTypeDef.IsValueType) {
// Value types don't marshal as COM
return false;
} else if (parameterTypeDef.IsInterface) {
// Interface types marshal as COM by default
return true;
} else if (parameterTypeDef.IsMulticastDelegate ()) {
// Delegates are special cased by interop
return false;
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", context)) {
// Subclasses of CriticalHandle are special cased by interop
return false;
} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", context)) {
// Subclasses of SafeHandle are special cased by interop
return false;
} else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) {
// Rest of classes that don't have layout marshal as COM
return true;
}
}
}
return false;
}
internal static bool IsPInvokeDangerous (MethodDefinition methodDefinition, LinkContext context, out bool comDangerousMethod)
{
// The method in ILLink only detects one condition - COM Dangerous, but it's structured like this
// so that the code looks very similar to AOT which has more than one condition.
if (!methodDefinition.IsPInvokeImpl) {
comDangerousMethod = false;
return false;
}
comDangerousMethod = IsComInterop (methodDefinition.MethodReturnType, methodDefinition.ReturnType, context);
#pragma warning disable RS0030 // MethodDefinition.Parameters is banned. Here we iterate through the parameters and don't need to worry about the 'this' parameter.
foreach (ParameterDefinition pd in methodDefinition.Parameters) {
comDangerousMethod |= IsComInterop (pd, pd.ParameterType, context);
}
#pragma warning restore RS0030
return comDangerousMethod;
}
}
}
|