mirror of https://github.com/icsharpcode/ILSpy.git
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.
584 lines
25 KiB
584 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 |
|
} |
|
} |
|
}
|
|
|