Browse Source

Add DynamicCallSiteTransform

pull/1165/head
Siegfried Pammer 7 years ago
parent
commit
5e4b571a62
  1. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 89
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 4
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 2
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  5. 593
      ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs
  6. 52
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  7. 4
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
  8. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -87,6 +87,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -87,6 +87,7 @@ namespace ICSharpCode.Decompiler.CSharp
// is already collapsed into stloc(V, ...).
new RemoveDeadVariableInit(),
new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements
new DynamicCallSiteTransform(),
new SwitchDetection(),
new SwitchOnStringTransform(),
new SwitchOnNullableTransform(),

89
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2333,6 +2333,95 @@ namespace ICSharpCode.Decompiler.CSharp @@ -2333,6 +2333,95 @@ namespace ICSharpCode.Decompiler.CSharp
.WithRR(new ResolveResult(NullableType.GetUnderlyingType(arg.Type)));
}
protected internal override TranslatedExpression VisitDynamicConvertInstruction(DynamicConvertInstruction inst, TranslationContext context)
{
return Translate(inst.Argument).ConvertTo(inst.Type, this, inst.IsChecked);
}
protected internal override TranslatedExpression VisitDynamicGetIndexInstruction(DynamicGetIndexInstruction inst, TranslationContext context)
{
var target = Translate(inst.Arguments[0], SpecialType.Dynamic);
return new IndexerExpression(target, inst.Arguments.Skip(1).Select(arg => Translate(arg, SpecialType.Dynamic).Expression))
.WithILInstruction(inst)
.WithRR(new ResolveResult(SpecialType.Dynamic));
}
protected internal override TranslatedExpression VisitDynamicGetMemberInstruction(DynamicGetMemberInstruction inst, TranslationContext context)
{
var target = Translate(inst.Target, SpecialType.Dynamic);
return new MemberReferenceExpression(target, inst.Name)
.WithILInstruction(inst)
.WithRR(new ResolveResult(SpecialType.Dynamic));
}
protected internal override TranslatedExpression VisitDynamicInvokeConstructorInstruction(DynamicInvokeConstructorInstruction inst, TranslationContext context)
{
IL.Transforms.TransformExpressionTrees.MatchGetTypeFromHandle(inst.Arguments[0], out var constructorType);
return new ObjectCreateExpression(ConvertType(constructorType), inst.Arguments.Skip(1).Select(arg => Translate(arg, SpecialType.Dynamic).Expression))
.WithILInstruction(inst).WithRR(new ResolveResult(constructorType));
}
protected internal override TranslatedExpression VisitDynamicInvokeMemberInstruction(DynamicInvokeMemberInstruction inst, TranslationContext context)
{
TranslatedExpression target;
if (!IL.Transforms.TransformExpressionTrees.MatchGetTypeFromHandle(inst.Arguments[0], out var callTargetType))
target = Translate(inst.Arguments[0], SpecialType.Dynamic);
else
target = new TypeReferenceExpression(ConvertType(callTargetType))
.WithoutILInstruction()
.WithRR(new TypeResolveResult(callTargetType));
return new InvocationExpression(new MemberReferenceExpression(target, inst.Name, inst.TypeArguments.Select(ConvertType)), inst.Arguments.Skip(1).Select(arg => Translate(arg, SpecialType.Dynamic).Expression))
.WithILInstruction(inst)
.WithRR(new ResolveResult(SpecialType.Dynamic));
}
protected internal override TranslatedExpression VisitDynamicSetIndexInstruction(DynamicSetIndexInstruction inst, TranslationContext context)
{
Debug.Assert(inst.Arguments.Count >= 3);
var target = Translate(inst.Arguments[0], SpecialType.Dynamic);
var value = Translate(inst.Arguments.Last(), SpecialType.Dynamic);
return Assignment(
new IndexerExpression(target, inst.Arguments.Skip(1).Take(inst.Arguments.Count - 2).Select(arg => Translate(arg, SpecialType.Dynamic).Expression)).WithoutILInstruction().WithRR(new ResolveResult(SpecialType.Dynamic)),
value
).WithILInstruction(inst);
}
protected internal override TranslatedExpression VisitDynamicSetMemberInstruction(DynamicSetMemberInstruction inst, TranslationContext context)
{
var target = Translate(inst.Target, SpecialType.Dynamic);
var value = Translate(inst.Value, SpecialType.Dynamic);
return Assignment(
new MemberReferenceExpression(target, inst.Name).WithoutILInstruction().WithRR(new ResolveResult(SpecialType.Dynamic)),
value
).WithILInstruction(inst);
}
protected internal override TranslatedExpression VisitDynamicBinaryOperatorInstruction(DynamicBinaryOperatorInstruction inst, TranslationContext context)
{
switch (inst.Operation) {
case ExpressionType.Add:
return CreateArithmeticBinaryOperator(BinaryOperatorType.Add);
case ExpressionType.Subtract:
return CreateArithmeticBinaryOperator(BinaryOperatorType.Subtract);
case ExpressionType.Multiply:
return CreateArithmeticBinaryOperator(BinaryOperatorType.Multiply);
case ExpressionType.Divide:
return CreateArithmeticBinaryOperator(BinaryOperatorType.Divide);
case ExpressionType.Modulo:
return CreateArithmeticBinaryOperator(BinaryOperatorType.Modulus);
default:
return base.VisitDynamicBinaryOperatorInstruction(inst, context);
}
TranslatedExpression CreateArithmeticBinaryOperator(BinaryOperatorType operatorType)
{
var left = Translate(inst.Left);
var right = Translate(inst.Right);
return new BinaryOperatorExpression(left.Expression, operatorType, right.Expression)
.WithILInstruction(inst).WithRR(new ResolveResult(SpecialType.Dynamic));
}
}
protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context)
{
string message = "Error";

4
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -285,6 +285,7 @@ @@ -285,6 +285,7 @@
<Compile Include="DotNetCore\LightJson\Serialization\TextScanner.cs" />
<Compile Include="DotNetCore\UniversalAssemblyResolver.cs" />
<Compile Include="DotNetCore\UnresolvedAssemblyNameReference.cs" />
<Compile Include="IL\Instructions\DynamicInstructions.cs" />
<Compile Include="IL\PointerArithmeticOffset.cs" />
<Compile Include="IL\ControlFlow\AwaitInCatchTransform.cs" />
<Compile Include="IL\ILAstWritingOptions.cs" />
@ -294,8 +295,9 @@ @@ -294,8 +295,9 @@
<Compile Include="IL\SequencePoint.cs" />
<Compile Include="IL\Instructions\CallIndirect.cs" />
<Compile Include="IL\Instructions\DefaultValue.cs" />
<Compile Include="IL\Transforms\DynamicCallSiteTransform.cs" />
<Compile Include="IL\Transforms\EarlyExpressionTransforms.cs" />
<Compile Include="IL\Transforms\ExpressionTreeCast.cs" />
<Compile Include="IL\Instructions\ExpressionTreeCast.cs" />
<Compile Include="IL\Transforms\HighLevelLoopTransform.cs" />
<Compile Include="IL\Transforms\NullPropagationTransform.cs" />
<Compile Include="IL\Transforms\ProxyCallReplacer.cs" />

2
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -326,7 +326,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -326,7 +326,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
base.VisitNumericCompoundAssign(inst);
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign)));
}
}
}
}
#endregion

593
ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs

@ -0,0 +1,593 @@ @@ -0,0 +1,593 @@
// 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;
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.DynamicExpressions)
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 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, 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);
}
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))
storeParentBlock.Instructions.RemoveAt(stLoc.ChildIndex);
if (value.MatchLdFld(out cacheFieldLoad, out var targetFieldCopy) && cacheFieldLoad.MatchLdsFld(out cacheFieldCopy) && cacheField.Equals(cacheFieldCopy) && targetField.Equals(targetFieldCopy))
storeParentBlock.Instructions.RemoveAt(stLoc.ChildIndex);
}
}
}
modifiedContainers.Add((BlockContainer)block.Parent);
}
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));
return new DynamicConvertInstruction(
binderFlags: callsite.Flags,
context: callsite.Context,
type: callsite.ConvertTargetType,
argument: targetInvokeCall.Arguments[2]
);
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:
deadArguments.AddRange(targetInvokeCall.Arguments.Take(2));
return new DynamicInvokeConstructorInstruction(
binderFlags: callsite.Flags,
context: callsite.Context,
argumentInfo: callsite.ArgumentInfos,
arguments: targetInvokeCall.Arguments.Skip(2).ToArray()
);
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 NotSupportedException();
}
throw new NotImplementedException();
}
bool ScanCallSiteInitBlock(Block callSiteInitBlock, IField callSiteCacheField, 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 != 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.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(variable, 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(callSiteInitBlock, 3, variable, typeArgsNewArr.Type, 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;
}
}
// Fourth argument: context type
if (!binderCall.Arguments[3].MatchLdLoc(out variable))
return false;
if (!callSiteInitBlock.Instructions[3 + numberOfTypeArguments].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 + numberOfTypeArguments].MatchStLoc(variable, out value))
return false;
if (value is NewArr newArr && newArr.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr.Indices.Count == 1 && newArr.Indices[0].MatchLdcI4(out int numberOfArguments)) {
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(callSiteInitBlock, 5 + numberOfTypeArguments, variable, newArr.Type, numberOfArguments, out var arguments, out _))
return false;
int i = 0;
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];
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;
string argumentName = null;
if (!createCall.Arguments[1].MatchLdStr(out argumentName))
if (!createCall.Arguments[1].MatchLdNull())
return false;
callSiteInfo.ArgumentInfos[i] = new CSharpArgumentInfo { Flags = (CSharpArgumentInfoFlags)argumentInfoFlags, Name = argumentName };
i++;
}
}
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 (value is NewArr newArr2 && newArr2.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr2.Indices.Count == 1 && newArr2.Indices[0].MatchLdcI4(out numberOfArguments)) {
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(callSiteInitBlock, 4, variable, newArr2.Type, numberOfArguments, out var arguments, out _))
return false;
int i = 0;
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];
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;
string argumentName = null;
if (!createCall.Arguments[1].MatchLdStr(out argumentName))
if (!createCall.Arguments[1].MatchLdNull())
return false;
callSiteInfo.ArgumentInfos[i] = new CSharpArgumentInfo { Flags = (CSharpArgumentInfoFlags)argumentInfoFlags, Name = argumentName };
i++;
}
}
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 NotSupportedException();
}
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 (value is NewArr newArr3 && newArr3.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr3.Indices.Count == 1 && newArr3.Indices[0].MatchLdcI4(out numberOfArguments)) {
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(callSiteInitBlock, 3, variable, newArr3.Type, numberOfArguments, out var arguments, out _))
return false;
int i = 0;
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];
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;
string argumentName = null;
if (!createCall.Arguments[1].MatchLdStr(out argumentName))
if (!createCall.Arguments[1].MatchLdNull())
return false;
callSiteInfo.ArgumentInfos[i] = new CSharpArgumentInfo { Flags = (CSharpArgumentInfoFlags)argumentInfoFlags, Name = argumentName };
i++;
}
}
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 (value is NewArr newArr4 && newArr4.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr4.Indices.Count == 1 && newArr4.Indices[0].MatchLdcI4(out numberOfArguments)) {
if (!TransformArrayInitializers.HandleSimpleArrayInitializer(callSiteInitBlock, 4, variable, newArr4.Type, numberOfArguments, out var arguments, out _))
return false;
int i = 0;
callSiteInfo.ArgumentInfos = new CSharpArgumentInfo[numberOfArguments];
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;
string argumentName = null;
if (!createCall.Arguments[1].MatchLdStr(out argumentName))
if (!createCall.Arguments[1].MatchLdNull())
return false;
callSiteInfo.ArgumentInfos[i] = new CSharpArgumentInfo { Flags = (CSharpArgumentInfoFlags)argumentInfoFlags, Name = argumentName };
i++;
}
}
return true;
default:
return false;
}
}
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
}
}
}

52
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -329,6 +330,57 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -329,6 +330,57 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (new NullableLiftingTransform(context).Run(inst))
return;
if (TransformDynamicAddAssignOrRemoveAssign(inst))
return;
}
/// <summary>
/// if (logic.not(dynamic.isevent (ldloc a))) Block IL_0045 {
/// dynamic.setmember.compound Setter2(ldloc a, dynamic.binary.operator AddAssign(dynamic.getmember Setter2(ldloc a), ldc.i4 5))
/// } else Block IL_0144 {
/// dynamic.invokemember.invokespecial.discard add_Setter2(ldloc a, ldc.i4 5)
/// }
/// =>
/// dynamic.setmember.compound Setter2(ldloc a, dynamic.binary.operator AddAssign(dynamic.getmember Setter2(ldloc a), ldc.i4 5))
/// </summary>
bool TransformDynamicAddAssignOrRemoveAssign(IfInstruction inst)
{
if (!inst.Condition.MatchLogicNot(out var possibleIsEvent))
return false;
if (!(possibleIsEvent is DynamicIsEventInstruction isEvent))
return false;
var trueInst = Block.Unwrap(inst.TrueInst);
var falseInst = Block.Unwrap(inst.FalseInst);
if (!(trueInst is DynamicSetMemberInstruction dynamicSetMember
&& dynamicSetMember.BinderFlags.HasFlag(CSharpBinderFlags.ValueFromCompoundAssignment)
&& isEvent.Argument.Match(dynamicSetMember.Target).Success
&& dynamicSetMember.Value is DynamicBinaryOperatorInstruction binaryOp
&& binaryOp.Left is DynamicGetMemberInstruction dynamicGetMember
&& dynamicGetMember.Target.Match(dynamicSetMember.Target).Success
&& dynamicSetMember.Name == dynamicGetMember.Name
&& falseInst is DynamicInvokeMemberInstruction invokeMember
&& invokeMember.BinderFlags.HasFlag(CSharpBinderFlags.InvokeSpecialName)
&& invokeMember.Arguments.Count == 2 && invokeMember.Arguments[0].Match(dynamicGetMember.Target).Success)
) {
return false;
}
switch (binaryOp.Operation) {
case ExpressionType.AddAssign:
if (invokeMember.Name != "add_" + dynamicGetMember.Name)
return false;
break;
case ExpressionType.SubtractAssign:
if (invokeMember.Name != "remove_" + dynamicGetMember.Name)
return false;
break;
default:
return false;
}
if (!binaryOp.Right.Match(invokeMember.Arguments[1]).Success)
return false;
inst.ReplaceWith(trueInst);
return true;
}
IfInstruction HandleConditionalOperator(IfInstruction inst)

4
ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs

@ -161,11 +161,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -161,11 +161,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return new DefaultValue(elementType);
}
}
/// <summary>
/// Handle simple case where RuntimeHelpers.InitializeArray is not used.
/// </summary>
bool HandleSimpleArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILInstruction[] values, out int instructionsToRemove)
internal static bool HandleSimpleArrayInitializer(Block block, int pos, ILVariable store, IType elementType, int length, out ILInstruction[] values, out int instructionsToRemove)
{
instructionsToRemove = 0;
values = null;

2
ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs

@ -1034,7 +1034,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1034,7 +1034,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
bool MatchGetTypeFromHandle(ILInstruction inst, out IType type)
internal static bool MatchGetTypeFromHandle(ILInstruction inst, out IType type)
{
type = null;
return inst is CallInstruction getTypeCall

Loading…
Cancel
Save