Browse Source

First implementation of TransformCollectionAndObjectInitializers

pull/734/merge
Siegfried Pammer 8 years ago
parent
commit
07dd01eafc
  1. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 140
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  4. 2
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  5. 2
      ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs
  6. 136
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -107,6 +107,7 @@ namespace ICSharpCode.Decompiler.CSharp
// opportunities belong in this category. // opportunities belong in this category.
new ExpressionTransforms(), new ExpressionTransforms(),
new TransformArrayInitializers(), new TransformArrayInitializers(),
new TransformCollectionAndObjectInitializers(),
new ILInlining() new ILInlining()
) )
} }

140
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1407,6 +1407,9 @@ namespace ICSharpCode.Decompiler.CSharp
switch (block.Type) { switch (block.Type) {
case BlockType.ArrayInitializer: case BlockType.ArrayInitializer:
return TranslateArrayInitializer(block); return TranslateArrayInitializer(block);
case BlockType.CollectionInitializer:
case BlockType.ObjectInitializer:
return TranslateObjectAndCollectionInitializer(block);
case BlockType.CompoundOperator: case BlockType.CompoundOperator:
return TranslateCompoundOperator(block); return TranslateCompoundOperator(block);
default: default:
@ -1414,6 +1417,143 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
TranslatedExpression TranslateObjectAndCollectionInitializer(Block block)
{
var stloc = block.Instructions.FirstOrDefault() as StLoc;
var final = block.FinalInstruction as LdLoc;
if (stloc == null || final == null || !(stloc.Value is NewObj newObjInst) || stloc.Variable != final.Variable)
throw new ArgumentException("given Block is invalid!");
Stack<List<Expression>> elementsStack = new Stack<List<Expression>>();
var elements = new List<Expression>(block.Instructions.Count);
elementsStack.Push(elements);
var initObjRR = new InitializedObjectResolveResult(newObjInst.Method.DeclaringType);
IMember currentMember = null;
foreach (var inst in block.Instructions) {
switch (inst) {
case CallVirt call:
if (call.Method.IsAccessor) {
if (elementsStack.Count > 1)
elementsStack.Pop();
if (call.Arguments[0].MatchLdLoc(stloc.Variable)) {
currentMember = call.Method.AccessorOwner;
var propertyReference = new IdentifierExpression(currentMember.Name)
.WithRR(new MemberResolveResult(initObjRR, currentMember));
var value = Translate(call.Arguments[1]).ConvertTo(currentMember.ReturnType, this);
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(call));
} else {
IMember newMember;
switch (call.Arguments[0]) {
case CallVirt cv:
newMember = cv.Method.AccessorOwner;
break;
case LdObj lo:
if (lo.Target is LdFlda ldflda) {
newMember = ldflda.Field;
break;
}
throw new NotSupportedException();
default:
throw new NotSupportedException();
}
Expression propertyReference;
if (newMember != currentMember) {
currentMember = newMember;
if (elementsStack.Count > 1) {
propertyReference = new IdentifierExpression(currentMember.Name)
.WithRR(new MemberResolveResult(initObjRR, currentMember));
var prevItems = elementsStack.Pop();
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems)));
}
elementsStack.Push(new List<Expression>(call.Arguments.Count - 1));
}
IMember member = call.Method.AccessorOwner;
propertyReference = new IdentifierExpression(member.Name)
.WithRR(new MemberResolveResult(initObjRR, member));
var value = Translate(call.Arguments[1]).ConvertTo(member.ReturnType, this);
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(call));
}
} else {
var target = call.Arguments[0];
if (block.Type == BlockType.ObjectInitializer) {
IMember newMember;
switch (target) {
case CallVirt cv:
newMember = cv.Method.AccessorOwner;
break;
case LdObj lo:
if (lo.Target is LdFlda ldflda) {
newMember = ldflda.Field;
break;
}
throw new NotSupportedException();
default:
throw new NotSupportedException();
}
if (newMember != currentMember) {
currentMember = newMember;
if (elementsStack.Count > 1) {
var propertyReference = new IdentifierExpression(currentMember.Name)
.WithRR(new MemberResolveResult(initObjRR, currentMember));
var prevItems = elementsStack.Pop();
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems)));
}
elementsStack.Push(new List<Expression>(call.Arguments.Count - 1));
}
}
var args = new List<Expression>(call.Arguments.Count - 1);
foreach (var arg in call.Arguments.Skip(1)) {
var expectedType = call.Method.Parameters[arg.ChildIndex - 1].Type;
var a = Translate(arg).ConvertTo(expectedType, this);
args.Add(a);
}
elementsStack.Peek().Add(args.Count == 1 ? args[0] : new ArrayInitializerExpression(args));
}
break;
case StObj stObj:
if (stObj.Value is Block b && IsInitializer(b)) {
if (stObj.Target is LdFlda ldflda) {
currentMember = ldflda.Field;
var propertyReference = new IdentifierExpression(currentMember.Name)
.WithRR(new MemberResolveResult(initObjRR, currentMember));
Expression value;
switch (block.Type) {
case BlockType.ArrayInitializer:
value = TranslateArrayInitializer(b);
break;
case BlockType.CollectionInitializer:
case BlockType.ObjectInitializer:
value = TranslateObjectAndCollectionInitializer(b);
break;
default:
throw new NotSupportedException();
}
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(stObj));
break;
}
}
throw new NotSupportedException();
}
}
if (elementsStack.Count > 1) {
var propertyReference = new IdentifierExpression(currentMember.Name)
.WithRR(new MemberResolveResult(initObjRR, currentMember));
var prevItems = elementsStack.Pop();
elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems)));
}
var expr = HandleCallInstruction(newObjInst);
var oce = (ObjectCreateExpression)expr.Expression;
oce.Initializer = new ArrayInitializerExpression(elements);
return expr.WithILInstruction(block);
}
bool IsInitializer(Block b)
{
return b.Type == BlockType.ArrayInitializer || b.Type == BlockType.CollectionInitializer || b.Type == BlockType.ObjectInitializer;
}
readonly static ArraySpecifier[] NoSpecifiers = new ArraySpecifier[0]; readonly static ArraySpecifier[] NoSpecifiers = new ArraySpecifier[0];
TranslatedExpression TranslateArrayInitializer(Block block) TranslatedExpression TranslateArrayInitializer(Block block)

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -285,6 +285,7 @@
<Compile Include="IL\Instructions\ILVariableCollection.cs" /> <Compile Include="IL\Instructions\ILVariableCollection.cs" />
<Compile Include="IL\Patterns\AnyNode.cs" /> <Compile Include="IL\Patterns\AnyNode.cs" />
<Compile Include="IL\ControlFlow\YieldReturnDecompiler.cs" /> <Compile Include="IL\ControlFlow\YieldReturnDecompiler.cs" />
<Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" />
<Compile Include="Util\UnicodeNewline.cs" /> <Compile Include="Util\UnicodeNewline.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" /> <Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" /> <Compile Include="FlowAnalysis\DataFlowVisitor.cs" />

2
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -239,6 +239,8 @@ namespace ICSharpCode.Decompiler.IL
public enum BlockType { public enum BlockType {
ControlFlow, ControlFlow,
ArrayInitializer, ArrayInitializer,
CollectionInitializer,
ObjectInitializer,
CompoundOperator CompoundOperator
} }
} }

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

@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
/// <summary> /// <summary>
/// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray. /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray.
/// For collection and object initializers see <see cref="TransformInitializers"/> /// For collection and object initializers see <see cref="TransformCollectionAndObjectInitializers"/>
/// </summary> /// </summary>
public class TransformArrayInitializers : IBlockTransform public class TransformArrayInitializers : IBlockTransform
{ {

136
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray.
/// For collection and object initializers see <see cref="TransformInitializers"/>
/// </summary>
public class TransformCollectionAndObjectInitializers : IBlockTransform
{
BlockTransformContext context;
void IBlockTransform.Run(Block block, BlockTransformContext context)
{
this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--)
{
DoTransform(block, i);
}
}
enum InitializerType
{
None,
Collection,
Object
}
bool DoTransform(Block body, int pos)
{
ILInstruction inst = body.Instructions[pos];
// Match stloc(v, newobj)
if (inst.MatchStLoc(out var v, out var initInst) && v.Kind == VariableKind.StackSlot && initInst is NewObj newObj) {
context.Step("CollectionOrObjectInitializer", inst);
int initializerItemsCount = 0;
var initializerType = InitializerType.None;
// Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument
// if the method is a setter we're dealing with an object initializer
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
while (pos + initializerItemsCount + 1 < body.Instructions.Count
&& (MatchesCallVirt(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType) || MatchesStObj(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType)))
{
initializerItemsCount++;
}
if (initializerItemsCount == 0)
return false;
Block initBlock;
switch (initializerType) {
case InitializerType.Collection:
initBlock = new Block(BlockType.CollectionInitializer);
break;
case InitializerType.Object:
initBlock = new Block(BlockType.ObjectInitializer);
break;
default: return false;
}
var finalSlot = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type);
initBlock.FinalInstruction = new LdLoc(finalSlot);
initBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
for (int i = 1; i <= initializerItemsCount; i++) {
switch (body.Instructions[i + pos]) {
case CallVirt callVirt:
var newCallVirt = new CallVirt(callVirt.Method);
var newTarget = callVirt.Arguments[0].Clone();
foreach (var load in newTarget.Descendants.OfType<LdLoc>())
load.Variable = finalSlot;
newCallVirt.Arguments.Add(newTarget);
newCallVirt.Arguments.AddRange(callVirt.Arguments.Skip(1).Select(a => a.Clone()));
initBlock.Instructions.Add(newCallVirt);
break;
case StObj stObj:
var newStObj = (StObj)stObj.Clone();
foreach (var load in newStObj.Target.Descendants.OfType<LdLoc>())
load.Variable = finalSlot;
initBlock.Instructions.Add(newStObj);
break;
}
}
initInst.ReplaceWith(initBlock);
for (int i = 0; i < initializerItemsCount; i++)
body.Instructions.RemoveAt(pos + 1);
ILInlining.InlineIfPossible(body, ref pos, context);
}
return true;
}
bool MatchesStObj(ILInstruction current, ILVariable v, ref InitializerType initializerType)
{
if (current is StObj so
&& so.Target is LdFlda ldfa
&& ldfa.Target.MatchLdLoc(v))
return true;
return false;
}
bool MatchesCallVirt(ILInstruction current, ILVariable v, ref InitializerType initializerType)
{
return current is CallVirt cv
&& cv.Arguments.Count >= 2
&& !cv.Arguments.Skip(1).Any(a => a.Descendants.OfType<LdLoc>().Any(b => b.MatchLdLoc(v)))
&& IsMatchingTarget(cv.Arguments[0], v)
&& IsMatchingMethod(cv, ref initializerType);
}
bool IsMatchingTarget(ILInstruction target, ILVariable targetVariable)
{
if (target.MatchLdLoc(targetVariable))
return true;
if (target is LdObj lo && lo.Target is LdFlda ldfa && ldfa.Target.MatchLdLoc(targetVariable))
return true;
if (target is CallVirt cv && cv.Method.IsAccessor && cv.Arguments.Count == 1 && cv.Arguments[0].MatchLdLoc(targetVariable))
return true;
return false;
}
bool IsMatchingMethod(CallVirt cv, ref InitializerType type)
{
if (cv.Method.IsAccessor && cv.Method.AccessorOwner is IProperty p && p.Setter == cv.Method) {
type = InitializerType.Object;
return true;
} else if (cv.Method.Name.Equals("Add", StringComparison.Ordinal)) {
if (type == InitializerType.None)
type = InitializerType.Collection;
return true;
}
return false;
}
}
}
Loading…
Cancel
Save