Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature/dataflow] Port Expression.* pattern recognition to new infra #1039

Merged
merged 9 commits into from
Apr 6, 2020
276 changes: 145 additions & 131 deletions src/linker/Linker.Steps/MarkStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2819,137 +2819,6 @@ public void Process (ref ReflectionPatternContext reflectionContext)

break;

//
// System.Linq.Expressions.Expression
//
case "Expression" when methodCalledType.Namespace == "System.Linq.Expressions":
Instruction second_argument;
TypeDefinition declaringType;

if (!methodCalled.IsStatic)
break;

switch (methodCalled.Name) {

//
// static Call (Type, String, Type[], Expression[])
//
case "Call": {
reflectionContext.AnalyzingPattern ();

var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 4);
if (first_arg_instr < 0) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed");
break;
}

var first_arg = _instructions [first_arg_instr];
if (first_arg.OpCode == OpCodes.Ldtoken)
first_arg_instr++;

declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr);
if (declaringType == null) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 1st argument which cannot be analyzed");
break;
}

var second_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 3);
second_argument = _instructions [second_arg_instr];
if (second_argument.OpCode != OpCodes.Ldstr) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 2nd argument which cannot be analyzed");
break;
}

var name = (string)second_argument.Operand;

MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, name, null, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}

break;

//
// static Property(Expression, Type, String)
// static Field (Expression, Type, String)
//
case "Property":
case "Field": {
reflectionContext.AnalyzingPattern ();

var second_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 2);
if (second_arg_instr < 0) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed");
break;
}

var second_arg = _instructions [second_arg_instr];
if (second_arg.OpCode == OpCodes.Ldtoken)
second_arg_instr++;

declaringType = FindReflectionTypeForLookup (_instructions, second_arg_instr);
if (declaringType == null) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 2nd argument which cannot be analyzed");
break;
}

var third_arg_inst = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 1);
var third_argument = _instructions [third_arg_inst];
if (third_argument.OpCode != OpCodes.Ldstr) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with the 3rd argument which cannot be analyzed");
break;
}

var name = (string)third_argument.Operand;

//
// The first argument can be any expression but we are looking only for simple null
// which we can convert to static only field lookup
//
var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 3);
bool staticOnly = false;

if (first_arg_instr >= 0) {
var first_arg = _instructions [first_arg_instr];
if (first_arg.OpCode == OpCodes.Ldnull)
staticOnly = true;
}

if (methodCalled.Name [0] == 'P')
MarkPropertiesFromReflectionCall (ref reflectionContext, declaringType, name, staticOnly);
else
MarkFieldsFromReflectionCall (ref reflectionContext, declaringType, name, staticOnly);
}

break;

//
// static New (Type)
//
case "New": {
reflectionContext.AnalyzingPattern ();

var first_arg_instr = GetInstructionAtStackDepth (_instructions, instructionIndex - 1, 1);
if (first_arg_instr < 0) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' couldn't be decomposed");
break;
}

var first_arg = _instructions [first_arg_instr];
if (first_arg.OpCode == OpCodes.Ldtoken)
first_arg_instr++;

declaringType = FindReflectionTypeForLookup (_instructions, first_arg_instr);
if (declaringType == null) {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{methodCalled.FullName}' inside '{_methodCalling.FullName}' was detected with 1st argument which cannot be analyzed");
break;
}

MarkMethodsFromReflectionCall (ref reflectionContext, declaringType, ".ctor", 0, BindingFlags.Instance, parametersCount: 0);
}
break;
}

break;

//
// System.Reflection.RuntimeReflectionExtensions
//
Expand Down Expand Up @@ -3607,6 +3476,97 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c
}
break;

//
// static Call (Type, String, Type[], Expression[])
//
case "Call" when calledMethod.DeclaringType.Name == "Expression"
&& calledMethod.DeclaringType.Namespace == "System.Linq.Expressions"
&& calledMethod.Parameters.Count == 4
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
&& calledMethod.Parameters [0].ParameterType.FullName == "System.Type": {

reflectionContext.AnalyzingPattern ();

foreach (var value in methodParams [0].UniqueValues ()) {
if (value is SystemTypeValue systemTypeValue) {
foreach (var stringParam in methodParams [1].UniqueValues ()) {
// TODO: Change this as needed after deciding whether or not we are to keep
// all methods on a type that was accessed via reflection.
if (stringParam is KnownStringValue stringValue)
MarkMethodsFromReflectionCall (ref reflectionContext, systemTypeValue.TypeRepresented, stringValue.Contents, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related note: This should also work on methods on a base class - see #1042. No need to address that in this PR, but maybe as a follow-up change we could fix it here as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment applies to Property and Field.

else
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 2nd argument which cannot be analyzed");
}
} else if (value == NullValue.Instance) {
reflectionContext.RecordHandledPattern ();
} else if (value is MethodParameterValue) {
// TODO: Check if parameter is annotated.
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 1st argument which cannot be analyzed");
} else {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 1st argument which cannot be analyzed");
}
}
}
break;

//
// static Field (Expression, Type, String)
// static Property (Expression, Type, String)
//
case var fieldOrProperty when ((fieldOrProperty == "Field" || fieldOrProperty == "Property")
&& calledMethod.DeclaringType.Namespace == "System.Linq.Expressions"
&& calledMethod.DeclaringType.Name == "Expression"
&& calledMethod.Parameters.Count == 3
&& calledMethod.Parameters [1].ParameterType.FullName == "System.Type"): {

reflectionContext.AnalyzingPattern ();

foreach (var value in methodParams [1].UniqueValues ()) {
if (value is SystemTypeValue systemTypeValue) {
foreach (var stringParam in methodParams [2].UniqueValues ()) {
if (stringParam is KnownStringValue stringValue) {
bool staticOnly = methodParams [0].Kind == ValueNodeKind.Null;
// TODO: Change this as needed after deciding if we are to keep all fields/properties on a type
// that is accessed via reflection. For now, let's only keep the field/property that is retrieved.
if (calledMethod.Name [0] == 'P')
MarkPropertiesFromReflectionCall (ref reflectionContext, systemTypeValue.TypeRepresented, stringValue.Contents, staticOnly);
else
MarkFieldsFromReflectionCall (ref reflectionContext, systemTypeValue.TypeRepresented, stringValue.Contents, staticOnly);
} else {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 3rd argument which cannot be analyzed");
}
}
} else if (value == NullValue.Instance) {
reflectionContext.RecordHandledPattern ();
} else if (value is MethodParameterValue) {
// TODO: Check if parameter is annotated.
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 2nd argument which cannot be analyzed");
} else {
reflectionContext.RecordUnrecognizedPattern ($"Expression call '{calledMethod.FullName}' inside '{callingMethodBody.Method.FullName}' was detected with 2nd argument which cannot be analyzed");
}
}
}
break;

//
// static New (Type)
//
case "New" when calledMethod.DeclaringType.Name == "Expression"
mateoatr marked this conversation as resolved.
Show resolved Hide resolved
&& calledMethod.DeclaringType.Namespace == "System.Linq.Expressions"
&& calledMethod.Parameters.Count == 1
&& calledMethod.Parameters[0].ParameterType.FullName == "System.Type":
{

reflectionContext.AnalyzingPattern();

foreach (var value in methodParams [0].UniqueValues ()) {
if (value is SystemTypeValue systemTypeValue)
MarkMethodsFromReflectionCall (ref reflectionContext, systemTypeValue.TypeRepresented, ".ctor", BindingFlags.Instance, parametersCount: 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this should include Public | NonPublic - since the runtime version will handle both cases.

else
RequireDynamicallyAccessedMembers (ref reflectionContext, DynamicallyAccessedMemberKinds.DefaultConstructor, value, calledMethod.Parameters [0]);
}
}
break;

//
// static CreateInstance (System.Type type)
// static CreateInstance (System.Type type, bool nonPublic)
Expand Down Expand Up @@ -3848,6 +3808,60 @@ void MarkMethodsFromReflectionCall (ref ReflectionPatternContext reflectionConte
}
}

void MarkPropertiesFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name, bool staticOnly = false)
{
bool foundMatch = false;
foreach (var property in declaringType.Properties) {
if (property.Name != name)
continue;

bool markedAny = false;
var methodCalling = reflectionContext.MethodCalling;

// It is not easy to reliably detect in the IL code whether the getter or setter (or both) are used.
// Be conservative and mark everything for the property.
var getter = property.GetMethod;
if (getter != null && (!staticOnly || staticOnly && getter.IsStatic)) {
reflectionContext.RecordRecognizedPattern (getter, () => _markStep.MarkIndirectlyCalledMethod (getter, new DependencyInfo (DependencyKind.AccessedViaReflection, methodCalling)));
markedAny = true;
}

var setter = property.SetMethod;
if (setter != null && (!staticOnly || staticOnly && setter.IsStatic)) {
reflectionContext.RecordRecognizedPattern (setter, () => _markStep.MarkIndirectlyCalledMethod (setter, new DependencyInfo (DependencyKind.AccessedViaReflection, methodCalling)));
markedAny = true;
}

if (markedAny) {
foundMatch = true;
reflectionContext.RecordRecognizedPattern (property, () => _markStep.MarkProperty (property, new DependencyInfo (DependencyKind.AccessedViaReflection, methodCalling)));
}
}

if (!foundMatch)
reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{reflectionContext.MethodCalled.FullName}' inside '{reflectionContext.MethodCalling.FullName}' could not resolve property `{name}` on type `{declaringType.FullName}`.");
}

void MarkFieldsFromReflectionCall (ref ReflectionPatternContext reflectionContext, TypeDefinition declaringType, string name, bool staticOnly = false)
{
bool foundMatch = false;
var methodCalling = reflectionContext.MethodCalling;
foreach (var field in declaringType.Fields) {
if (field.Name != name)
continue;

if (staticOnly && !field.IsStatic)
continue;

foundMatch = true;
reflectionContext.RecordRecognizedPattern (field, () => _markStep.MarkField (field, new DependencyInfo (DependencyKind.AccessedViaReflection, methodCalling)));
break;
}

if (!foundMatch)
reflectionContext.RecordUnrecognizedPattern ($"Reflection call '{reflectionContext.MethodCalled.FullName}' inside '{reflectionContext.MethodCalling.FullName}' could not resolve field `{name}` on type `{declaringType.FullName}`.");
}

void MarkConstructorsOnType (ref ReflectionPatternContext reflectionContext, TypeDefinition type, Func<MethodDefinition, bool> filter)
{
foreach (var method in type.Methods) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public RecognizedReflectionAccessPatternAttribute (Type reflectionMethodType, st
}

public RecognizedReflectionAccessPatternAttribute (Type reflectionMethodType, string reflectionMethodName, Type [] reflectionMethodParameters,
Type accessedItemType, string accessedItemName, string [] accessedItemParameters)
Type accessedItemType, string accessedItemName, string [] accessedItemParameters = null)
{
if (reflectionMethodType == null)
throw new ArgumentException ("Value cannot be null or empty.", nameof (reflectionMethodType));
Expand Down
Loading