You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
25 KiB
614 lines
25 KiB
// Copyright (c) 2018 Siegfried Pammer
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
|
|
using ICSharpCode.Decompiler.TypeSystem;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.IL.Transforms
|
|
{
|
|
/// <summary>
|
|
/// Transforms the "callsite initialization pattern" into DynamicInstructions.
|
|
/// </summary>
|
|
public class DynamicCallSiteTransform : IILTransform
|
|
{
|
|
ILTransformContext context;
|
|
|
|
const string CallSiteTypeName = "System.Runtime.CompilerServices.CallSite";
|
|
const string CSharpBinderTypeName = "Microsoft.CSharp.RuntimeBinder.Binder";
|
|
|
|
public void Run(ILFunction function, ILTransformContext context)
|
|
{
|
|
if (!context.Settings.Dynamic)
|
|
return;
|
|
|
|
this.context = context;
|
|
|
|
Dictionary<IField, CallSiteInfo> callsites = new Dictionary<IField, CallSiteInfo>();
|
|
HashSet<BlockContainer> modifiedContainers = new HashSet<BlockContainer>();
|
|
|
|
foreach (var block in function.Descendants.OfType<Block>())
|
|
{
|
|
if (block.Instructions.Count < 2)
|
|
continue;
|
|
// Check if, we deal with a callsite cache field null check:
|
|
// if (comp(ldsfld <>p__3 == ldnull)) br IL_000c
|
|
// br IL_002b
|
|
if (!(block.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
|
|
continue;
|
|
if (!(block.Instructions.LastOrDefault() is Branch branchAfterInit))
|
|
continue;
|
|
if (!MatchCallSiteCacheNullCheck(ifInst.Condition, out var callSiteCacheField, out var callSiteDelegate, out bool invertBranches))
|
|
continue;
|
|
if (!ifInst.TrueInst.MatchBranch(out var trueBlock))
|
|
continue;
|
|
Block callSiteInitBlock, targetBlockAfterInit;
|
|
if (invertBranches)
|
|
{
|
|
callSiteInitBlock = branchAfterInit.TargetBlock;
|
|
targetBlockAfterInit = trueBlock;
|
|
}
|
|
else
|
|
{
|
|
callSiteInitBlock = trueBlock;
|
|
targetBlockAfterInit = branchAfterInit.TargetBlock;
|
|
}
|
|
if (!ScanCallSiteInitBlock(callSiteInitBlock, callSiteCacheField, callSiteDelegate, out var callSiteInfo, out var blockAfterInit))
|
|
continue;
|
|
if (targetBlockAfterInit != blockAfterInit)
|
|
continue;
|
|
callSiteInfo.DelegateType = callSiteDelegate;
|
|
callSiteInfo.ConditionalJumpToInit = ifInst;
|
|
callSiteInfo.Inverted = invertBranches;
|
|
callSiteInfo.BranchAfterInit = branchAfterInit;
|
|
callsites.Add(callSiteCacheField, callSiteInfo);
|
|
}
|
|
|
|
var storesToRemove = new List<StLoc>();
|
|
|
|
foreach (var invokeCall in function.Descendants.OfType<CallVirt>())
|
|
{
|
|
if (invokeCall.Method.DeclaringType.Kind != TypeKind.Delegate || invokeCall.Method.Name != "Invoke" || invokeCall.Arguments.Count == 0)
|
|
continue;
|
|
var firstArgument = invokeCall.Arguments[0];
|
|
if (firstArgument.MatchLdLoc(out var stackSlot) && stackSlot.Kind == VariableKind.StackSlot && stackSlot.IsSingleDefinition)
|
|
{
|
|
firstArgument = ((StLoc)stackSlot.StoreInstructions[0]).Value;
|
|
}
|
|
if (!firstArgument.MatchLdFld(out var cacheFieldLoad, out var targetField))
|
|
continue;
|
|
if (!cacheFieldLoad.MatchLdsFld(out var cacheField))
|
|
continue;
|
|
if (!callsites.TryGetValue(cacheField, out var callsite))
|
|
continue;
|
|
context.Stepper.Step("Transform callsite for " + callsite.MemberName);
|
|
var deadArguments = new List<ILInstruction>();
|
|
ILInstruction replacement = MakeDynamicInstruction(callsite, invokeCall, deadArguments);
|
|
if (replacement == null)
|
|
continue;
|
|
invokeCall.ReplaceWith(replacement);
|
|
Debug.Assert(callsite.ConditionalJumpToInit?.Parent is Block);
|
|
var block = ((Block)callsite.ConditionalJumpToInit.Parent);
|
|
if (callsite.Inverted)
|
|
{
|
|
block.Instructions.Remove(callsite.ConditionalJumpToInit);
|
|
callsite.BranchAfterInit.ReplaceWith(callsite.ConditionalJumpToInit.TrueInst);
|
|
}
|
|
else
|
|
{
|
|
block.Instructions.Remove(callsite.ConditionalJumpToInit);
|
|
}
|
|
foreach (var arg in deadArguments)
|
|
{
|
|
if (arg.MatchLdLoc(out var temporary) && temporary.Kind == VariableKind.StackSlot && temporary.IsSingleDefinition && temporary.LoadCount == 0)
|
|
{
|
|
StLoc stLoc = (StLoc)temporary.StoreInstructions[0];
|
|
if (stLoc.Parent is Block storeParentBlock)
|
|
{
|
|
var value = stLoc.Value;
|
|
if (value.MatchLdsFld(out var cacheFieldCopy) && cacheFieldCopy.Equals(cacheField))
|
|
storesToRemove.Add(stLoc);
|
|
if (value.MatchLdFld(out cacheFieldLoad, out var targetFieldCopy) && cacheFieldLoad.MatchLdsFld(out cacheFieldCopy) && cacheField.Equals(cacheFieldCopy) && targetField.Equals(targetFieldCopy))
|
|
storesToRemove.Add(stLoc);
|
|
}
|
|
}
|
|
}
|
|
modifiedContainers.Add((BlockContainer)block.Parent);
|
|
}
|
|
|
|
foreach (var inst in storesToRemove)
|
|
{
|
|
Block parentBlock = (Block)inst.Parent;
|
|
parentBlock.Instructions.RemoveAt(inst.ChildIndex);
|
|
}
|
|
|
|
foreach (var container in modifiedContainers)
|
|
container.SortBlocks(deleteUnreachableBlocks: true);
|
|
}
|
|
|
|
ILInstruction MakeDynamicInstruction(CallSiteInfo callsite, CallVirt targetInvokeCall, List<ILInstruction> deadArguments)
|
|
{
|
|
switch (callsite.Kind)
|
|
{
|
|
case BinderMethodKind.BinaryOperation:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicBinaryOperatorInstruction(
|
|
binderFlags: callsite.Flags,
|
|
operation: callsite.Operation,
|
|
context: callsite.Context,
|
|
leftArgumentInfo: callsite.ArgumentInfos[0],
|
|
left: targetInvokeCall.Arguments[2],
|
|
rightArgumentInfo: callsite.ArgumentInfos[1],
|
|
right: targetInvokeCall.Arguments[3]
|
|
);
|
|
case BinderMethodKind.Convert:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
ILInstruction result = new DynamicConvertInstruction(
|
|
binderFlags: callsite.Flags,
|
|
context: callsite.Context,
|
|
type: callsite.ConvertTargetType,
|
|
argument: targetInvokeCall.Arguments[2]
|
|
);
|
|
if (result.ResultType == StackType.Unknown)
|
|
{
|
|
// if references are missing, we need to coerce the primitive type to None.
|
|
// Otherwise we will get loads of assertions.
|
|
result = new Conv(result, PrimitiveType.None, ((DynamicConvertInstruction)result).IsChecked, Sign.None);
|
|
}
|
|
return result;
|
|
case BinderMethodKind.GetIndex:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicGetIndexInstruction(
|
|
binderFlags: callsite.Flags,
|
|
context: callsite.Context,
|
|
argumentInfo: callsite.ArgumentInfos,
|
|
arguments: targetInvokeCall.Arguments.Skip(2).ToArray()
|
|
);
|
|
case BinderMethodKind.GetMember:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicGetMemberInstruction(
|
|
binderFlags: callsite.Flags,
|
|
name: callsite.MemberName,
|
|
context: callsite.Context,
|
|
targetArgumentInfo: callsite.ArgumentInfos[0],
|
|
target: targetInvokeCall.Arguments[2]
|
|
);
|
|
case BinderMethodKind.Invoke:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicInvokeInstruction(
|
|
binderFlags: callsite.Flags,
|
|
context: callsite.Context,
|
|
argumentInfo: callsite.ArgumentInfos,
|
|
arguments: targetInvokeCall.Arguments.Skip(2).ToArray()
|
|
);
|
|
case BinderMethodKind.InvokeConstructor:
|
|
var arguments = targetInvokeCall.Arguments.Skip(2).ToArray();
|
|
// Extract type information from targetInvokeCall:
|
|
// Must either be an inlined type or
|
|
// a reference to a variable that is initialized with a type.
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(arguments[0], out var type))
|
|
{
|
|
if (!(arguments[0].MatchLdLoc(out var temp) && temp.IsSingleDefinition && temp.StoreInstructions.FirstOrDefault() is StLoc initStore))
|
|
return null;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(initStore.Value, out type))
|
|
return null;
|
|
}
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicInvokeConstructorInstruction(
|
|
binderFlags: callsite.Flags,
|
|
type: type ?? SpecialType.UnknownType,
|
|
context: callsite.Context,
|
|
argumentInfo: callsite.ArgumentInfos,
|
|
arguments: arguments
|
|
);
|
|
case BinderMethodKind.InvokeMember:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicInvokeMemberInstruction(
|
|
binderFlags: callsite.Flags,
|
|
name: callsite.MemberName,
|
|
typeArguments: callsite.TypeArguments,
|
|
context: callsite.Context,
|
|
argumentInfo: callsite.ArgumentInfos,
|
|
arguments: targetInvokeCall.Arguments.Skip(2).ToArray()
|
|
);
|
|
case BinderMethodKind.IsEvent:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicIsEventInstruction(
|
|
binderFlags: callsite.Flags,
|
|
name: callsite.MemberName,
|
|
context: callsite.Context,
|
|
argument: targetInvokeCall.Arguments[2]
|
|
);
|
|
case BinderMethodKind.SetIndex:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicSetIndexInstruction(
|
|
binderFlags: callsite.Flags,
|
|
context: callsite.Context,
|
|
argumentInfo: callsite.ArgumentInfos,
|
|
arguments: targetInvokeCall.Arguments.Skip(2).ToArray()
|
|
);
|
|
case BinderMethodKind.SetMember:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicSetMemberInstruction(
|
|
binderFlags: callsite.Flags,
|
|
name: callsite.MemberName,
|
|
context: callsite.Context,
|
|
targetArgumentInfo: callsite.ArgumentInfos[0],
|
|
target: targetInvokeCall.Arguments[2],
|
|
valueArgumentInfo: callsite.ArgumentInfos[1],
|
|
value: targetInvokeCall.Arguments[3]
|
|
);
|
|
case BinderMethodKind.UnaryOperation:
|
|
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
|
|
return new DynamicUnaryOperatorInstruction(
|
|
binderFlags: callsite.Flags,
|
|
operation: callsite.Operation,
|
|
context: callsite.Context,
|
|
operandArgumentInfo: callsite.ArgumentInfos[0],
|
|
operand: targetInvokeCall.Arguments[2]
|
|
);
|
|
default:
|
|
throw new ArgumentOutOfRangeException($"Value {callsite.Kind} is not supported!");
|
|
}
|
|
}
|
|
|
|
bool ScanCallSiteInitBlock(Block callSiteInitBlock, IField callSiteCacheField, IType callSiteDelegateType, out CallSiteInfo callSiteInfo, out Block blockAfterInit)
|
|
{
|
|
callSiteInfo = default(CallSiteInfo);
|
|
blockAfterInit = null;
|
|
int instCount = callSiteInitBlock.Instructions.Count;
|
|
if (callSiteInitBlock.IncomingEdgeCount != 1 || instCount < 2)
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[instCount - 1].MatchBranch(out blockAfterInit))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[instCount - 2].MatchStsFld(out var field, out var value) || !field.Equals(callSiteCacheField))
|
|
return false;
|
|
if (!(value is Call createBinderCall) || createBinderCall.Method.TypeArguments.Count != 0 || createBinderCall.Arguments.Count != 1 || createBinderCall.Method.Name != "Create" || createBinderCall.Method.DeclaringType.FullName != CallSiteTypeName || createBinderCall.Method.DeclaringType.TypeArguments.Count != 1)
|
|
return false;
|
|
if (!(createBinderCall.Arguments[0] is Call binderCall) || binderCall.Method.DeclaringType.FullName != CSharpBinderTypeName || binderCall.Method.DeclaringType.TypeParameterCount != 0)
|
|
return false;
|
|
callSiteInfo.DelegateType = callSiteDelegateType;
|
|
callSiteInfo.InitBlock = callSiteInitBlock;
|
|
switch (binderCall.Method.Name)
|
|
{
|
|
case "IsEvent":
|
|
callSiteInfo.Kind = BinderMethodKind.IsEvent;
|
|
// In the case of Binder.IsEvent all arguments should already be properly inlined, as there is no array initializer:
|
|
// Scan arguments: binder flags, member name, context type
|
|
if (binderCall.Arguments.Count != 3)
|
|
return false;
|
|
if (!binderCall.Arguments[0].MatchLdcI4(out int binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
if (!binderCall.Arguments[1].MatchLdStr(out string name))
|
|
return false;
|
|
callSiteInfo.MemberName = name;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(binderCall.Arguments[2], out var contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
return true;
|
|
case "Convert":
|
|
callSiteInfo.Kind = BinderMethodKind.Convert;
|
|
// In the case of Binder.Convert all arguments should already be properly inlined, as there is no array initializer:
|
|
// Scan arguments: binder flags, target type, context type
|
|
if (binderCall.Arguments.Count != 3)
|
|
return false;
|
|
if (!binderCall.Arguments[0].MatchLdcI4(out binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(binderCall.Arguments[1], out var targetType))
|
|
return false;
|
|
callSiteInfo.ConvertTargetType = targetType;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(binderCall.Arguments[2], out contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
return true;
|
|
case "InvokeMember":
|
|
callSiteInfo.Kind = BinderMethodKind.InvokeMember;
|
|
if (binderCall.Arguments.Count != 5)
|
|
return false;
|
|
// First argument: binder flags
|
|
// The value must be a single ldc.i4 instruction.
|
|
if (!binderCall.Arguments[0].MatchLdLoc(out var variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[0].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdcI4(out binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
// Second argument: method name
|
|
// The value must be a single ldstr instruction.
|
|
if (!binderCall.Arguments[1].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[1].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdStr(out name))
|
|
return false;
|
|
callSiteInfo.MemberName = name;
|
|
// Third argument: type arguments
|
|
// The value must be either ldnull (no type arguments) or an array initializer pattern.
|
|
if (!binderCall.Arguments[2].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[2].MatchStLoc(out var variableOrTemporary, out value))
|
|
return false;
|
|
int numberOfTypeArguments = 0;
|
|
if (!value.MatchLdNull())
|
|
{
|
|
if (value is NewArr typeArgsNewArr && typeArgsNewArr.Type.IsKnownType(KnownTypeCode.Type) && typeArgsNewArr.Indices.Count == 1 && typeArgsNewArr.Indices[0].MatchLdcI4(out numberOfTypeArguments))
|
|
{
|
|
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInitBlock, 3, variableOrTemporary, typeArgsNewArr.Type, new[] { numberOfTypeArguments }, out var typeArguments, out _))
|
|
return false;
|
|
int i = 0;
|
|
callSiteInfo.TypeArguments = new IType[numberOfTypeArguments];
|
|
foreach (var (_, typeArg) in typeArguments)
|
|
{
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(typeArg, out var type))
|
|
return false;
|
|
callSiteInfo.TypeArguments[i] = type;
|
|
i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
int typeArgumentsOffset = numberOfTypeArguments;
|
|
// Special case for csc array initializers:
|
|
if (variableOrTemporary != variable)
|
|
{
|
|
// store temporary from array initializer in variable
|
|
if (!callSiteInitBlock.Instructions[3 + typeArgumentsOffset].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdLoc(variableOrTemporary))
|
|
return false;
|
|
typeArgumentsOffset++;
|
|
}
|
|
// Fourth argument: context type
|
|
if (!binderCall.Arguments[3].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[3 + typeArgumentsOffset].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(value, out contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
// Fifth argument: call parameter info
|
|
if (!binderCall.Arguments[4].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[4 + typeArgumentsOffset].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!ExtractArgumentInfo(value, ref callSiteInfo, 5 + typeArgumentsOffset, variable))
|
|
return false;
|
|
return true;
|
|
case "GetMember":
|
|
case "SetMember":
|
|
callSiteInfo.Kind = binderCall.Method.Name == "GetMember" ? BinderMethodKind.GetMember : BinderMethodKind.SetMember;
|
|
if (binderCall.Arguments.Count != 4)
|
|
return false;
|
|
// First argument: binder flags
|
|
// The value must be a single ldc.i4 instruction.
|
|
if (!binderCall.Arguments[0].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[0].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdcI4(out binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
// Second argument: method name
|
|
// The value must be a single ldstr instruction.
|
|
if (!binderCall.Arguments[1].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[1].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdStr(out name))
|
|
return false;
|
|
callSiteInfo.MemberName = name;
|
|
// Third argument: context type
|
|
if (!binderCall.Arguments[2].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[2].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(value, out contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
// Fourth argument: call parameter info
|
|
if (!binderCall.Arguments[3].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[3].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable))
|
|
return false;
|
|
return true;
|
|
case "GetIndex":
|
|
case "SetIndex":
|
|
case "InvokeConstructor":
|
|
case "Invoke":
|
|
switch (binderCall.Method.Name)
|
|
{
|
|
case "GetIndex":
|
|
callSiteInfo.Kind = BinderMethodKind.GetIndex;
|
|
break;
|
|
case "SetIndex":
|
|
callSiteInfo.Kind = BinderMethodKind.SetIndex;
|
|
break;
|
|
case "InvokeConstructor":
|
|
callSiteInfo.Kind = BinderMethodKind.InvokeConstructor;
|
|
break;
|
|
case "Invoke":
|
|
callSiteInfo.Kind = BinderMethodKind.Invoke;
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
if (binderCall.Arguments.Count != 3)
|
|
return false;
|
|
// First argument: binder flags
|
|
// The value must be a single ldc.i4 instruction.
|
|
if (!binderCall.Arguments[0].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[0].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdcI4(out binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
// Second argument: context type
|
|
if (!binderCall.Arguments[1].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[1].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(value, out contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
// Third argument: call parameter info
|
|
if (!binderCall.Arguments[2].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[2].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!ExtractArgumentInfo(value, ref callSiteInfo, 3, variable))
|
|
return false;
|
|
return true;
|
|
case "UnaryOperation":
|
|
case "BinaryOperation":
|
|
callSiteInfo.Kind = binderCall.Method.Name == "BinaryOperation" ? BinderMethodKind.BinaryOperation : BinderMethodKind.UnaryOperation;
|
|
if (binderCall.Arguments.Count != 4)
|
|
return false;
|
|
// First argument: binder flags
|
|
// The value must be a single ldc.i4 instruction.
|
|
if (!binderCall.Arguments[0].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[0].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdcI4(out binderFlagsInteger))
|
|
return false;
|
|
callSiteInfo.Flags = (CSharpBinderFlags)binderFlagsInteger;
|
|
// Second argument: operation
|
|
// The value must be a single ldc.i4 instruction.
|
|
if (!binderCall.Arguments[1].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[1].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!value.MatchLdcI4(out int operation))
|
|
return false;
|
|
callSiteInfo.Operation = (ExpressionType)operation;
|
|
// Third argument: context type
|
|
if (!binderCall.Arguments[2].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[2].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!TransformExpressionTrees.MatchGetTypeFromHandle(value, out contextType))
|
|
return false;
|
|
callSiteInfo.Context = contextType;
|
|
// Fourth argument: call parameter info
|
|
if (!binderCall.Arguments[3].MatchLdLoc(out variable))
|
|
return false;
|
|
if (!callSiteInitBlock.Instructions[3].MatchStLoc(variable, out value))
|
|
return false;
|
|
if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable))
|
|
return false;
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ExtractArgumentInfo(ILInstruction value, ref CallSiteInfo callSiteInfo, int instructionOffset, ILVariable variable)
|
|
{
|
|
if (!(value is NewArr newArr2 && newArr2.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr2.Indices.Count == 1 && newArr2.Indices[0].MatchLdcI4(out var numberOfArguments)))
|
|
return false;
|
|
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(context.Function, callSiteInfo.InitBlock, instructionOffset, variable, newArr2.Type, new[] { numberOfArguments }, out var arguments, out _))
|
|
return false;
|
|
int i = 0;
|
|
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];
|
|
IMethod invokeMethod = callSiteInfo.DelegateType.GetDelegateInvokeMethod();
|
|
if (invokeMethod == null)
|
|
return false;
|
|
var compileTimeTypes = invokeMethod.Parameters.SelectReadOnlyArray(p => p.Type);
|
|
foreach (var (_, arg) in arguments)
|
|
{
|
|
if (!(arg is Call createCall))
|
|
return false;
|
|
if (!(createCall.Method.Name == "Create" && createCall.Method.DeclaringType.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && createCall.Arguments.Count == 2))
|
|
return false;
|
|
if (!createCall.Arguments[0].MatchLdcI4(out var argumentInfoFlags))
|
|
return false;
|
|
if (!createCall.Arguments[1].MatchLdStr(out string argumentName))
|
|
if (!createCall.Arguments[1].MatchLdNull())
|
|
return false;
|
|
callSiteInfo.ArgumentInfos[i] = new CSharpArgumentInfo { Flags = (CSharpArgumentInfoFlags)argumentInfoFlags, Name = argumentName, CompileTimeType = compileTimeTypes[i + 1] };
|
|
i++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MatchCallSiteCacheNullCheck(ILInstruction condition, out IField callSiteCacheField, out IType callSiteDelegate, out bool invertBranches)
|
|
{
|
|
callSiteCacheField = null;
|
|
callSiteDelegate = null;
|
|
invertBranches = false;
|
|
if (!condition.MatchCompEqualsNull(out var argument))
|
|
{
|
|
if (!condition.MatchCompNotEqualsNull(out argument))
|
|
return false;
|
|
invertBranches = true;
|
|
}
|
|
if (!argument.MatchLdsFld(out callSiteCacheField) || callSiteCacheField.ReturnType.TypeArguments.Count != 1 || callSiteCacheField.ReturnType.FullName != CallSiteTypeName)
|
|
return false;
|
|
callSiteDelegate = callSiteCacheField.ReturnType.TypeArguments[0];
|
|
if (callSiteDelegate.Kind != TypeKind.Delegate)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
struct CallSiteInfo
|
|
{
|
|
public bool Inverted;
|
|
public ILInstruction BranchAfterInit;
|
|
public IfInstruction ConditionalJumpToInit;
|
|
public Block InitBlock;
|
|
public IType DelegateType;
|
|
public BinderMethodKind Kind;
|
|
public CSharpBinderFlags Flags;
|
|
public ExpressionType Operation;
|
|
public IType Context;
|
|
public IType ConvertTargetType;
|
|
public IType[] TypeArguments;
|
|
public CSharpArgumentInfo[] ArgumentInfos;
|
|
public string MemberName;
|
|
}
|
|
|
|
enum BinderMethodKind
|
|
{
|
|
BinaryOperation,
|
|
Convert,
|
|
GetIndex,
|
|
GetMember,
|
|
Invoke,
|
|
InvokeConstructor,
|
|
InvokeMember,
|
|
IsEvent,
|
|
SetIndex,
|
|
SetMember,
|
|
UnaryOperation
|
|
}
|
|
}
|
|
}
|