Browse Source

Rewrite of TransformCollectionAndObjectInitializers

pull/734/merge
Siegfried Pammer 8 years ago
parent
commit
7312654813
  1. 172
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 181
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  3. 8
      ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs
  4. 12
      ICSharpCode.Decompiler/Tests/TestCases/Correctness/InitializerTests.cs

172
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -25,6 +25,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; @@ -25,6 +25,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
@ -1423,125 +1424,48 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1423,125 +1424,48 @@ namespace ICSharpCode.Decompiler.CSharp
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 initObjRR = new InitializedObjectResolveResult(newObjInst.Method.DeclaringType);
var 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));
}
List<AccessPathElement> currentPath = null;
foreach (var inst in block.Instructions.Skip(1)) {
var info = AccessPathElement.GetAccessPath(inst);
if (info.Kind == AccessPathKind.Invalid) continue;
if (currentPath == null) {
currentPath = info.Path;
} else {
int firstDifferenceIndex = Math.Min(currentPath.Count, info.Path.Count);
int index = 0;
while (index < firstDifferenceIndex && info.Path[index] == currentPath[index])
index++;
firstDifferenceIndex = index;
while (elementsStack.Count - 1 > firstDifferenceIndex) {
var pathElement = currentPath[elementsStack.Count - 2];
var values = elementsStack.Pop();
elementsStack.Peek().Add(MakeInitializerAssignment(pathElement.Member, values));
}
currentPath = info.Path;
}
while (elementsStack.Count < currentPath.Count)
elementsStack.Push(new List<Expression>());
var lastElement = currentPath.Last();
var memberRR = new MemberResolveResult(initObjRR, lastElement.Member);
switch (info.Kind) {
case AccessPathKind.Adder:
elementsStack.Peek().Add(MakeInitializerElements(info.Values, ((IMethod)lastElement.Member).Parameters));
break;
case AccessPathKind.Setter:
var target = new IdentifierExpression(lastElement.Member.Name)
.WithILInstruction(inst).WithRR(memberRR);
elementsStack.Peek().Add(Assignment(target, Translate(info.Values.Single())));
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)));
while (elementsStack.Count > 1) {
var pathElement = currentPath[elementsStack.Count - 2];
var values = elementsStack.Pop();
elementsStack.Peek().Add(MakeInitializerAssignment(pathElement.Member, values));
}
var expr = HandleCallInstruction(newObjInst);
var oce = (ObjectCreateExpression)expr.Expression;
@ -1549,9 +1473,25 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1549,9 +1473,25 @@ namespace ICSharpCode.Decompiler.CSharp
return expr.WithILInstruction(block);
}
bool IsInitializer(Block b)
Expression MakeInitializerAssignment(IMember member, List<Expression> values)
{
return b.Type == BlockType.ArrayInitializer || b.Type == BlockType.CollectionInitializer || b.Type == BlockType.ObjectInitializer;
var target = new IdentifierExpression(member.Name);
Expression value;
if (values.Count == 1 && !(values[0] is AssignmentExpression))
value = values[0];
else
value = new ArrayInitializerExpression(values);
return new AssignmentExpression(target, value);
}
Expression MakeInitializerElements(List<ILInstruction> values, IList<IParameter> parameters)
{
if (values.Count == 1)
return Translate(values[0]).ConvertTo(parameters[0].Type, this);
var expressions = new Expression[values.Count];
for (int i = 0; i < values.Count; i++)
expressions[i] = Translate(values[i]).ConvertTo(parameters[i].Type, this);
return new ArrayInitializerExpression(expressions);
}
readonly static ArraySpecifier[] NoSpecifiers = new ArraySpecifier[0];

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

@ -24,13 +24,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -24,13 +24,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
enum InitializerType
{
None,
Collection,
Object
}
bool DoTransform(Block body, int pos)
{
ILInstruction inst = body.Instructions[pos];
@ -38,40 +31,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -38,40 +31,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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;
var blockType = BlockType.CollectionInitializer;
// 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)))
{
&& IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, ref blockType))
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;
}
Block initBlock = new Block(blockType);
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();
var newCallVirt = (CallVirt)callVirt.Clone();
var newTarget = newCallVirt.Arguments[0];
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:
@ -91,22 +71,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -91,22 +71,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true;
}
bool MatchesStObj(ILInstruction current, ILVariable v, ref InitializerType initializerType)
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, ref BlockType blockType)
{
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);
(var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos]);
switch (kind) {
case AccessPathKind.Adder:
return target == targetVariable;
case AccessPathKind.Setter:
if (values.Count == 1 && target == targetVariable) {
blockType = BlockType.ObjectInitializer;
return true;
}
return false;
default:
return false;
}
}
bool IsMatchingTarget(ILInstruction target, ILVariable targetVariable)
@ -120,17 +99,131 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -120,17 +99,131 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
bool IsMatchingMethod(CallVirt cv, ref InitializerType type)
bool IsMatchingMethod(CallVirt cv, ref BlockType type)
{
if (cv.Method.IsAccessor && cv.Method.AccessorOwner is IProperty p && p.Setter == cv.Method) {
type = InitializerType.Object;
type = BlockType.ObjectInitializer;
return true;
} else if (cv.Method.Name.Equals("Add", StringComparison.Ordinal)) {
if (type == InitializerType.None)
type = InitializerType.Collection;
return true;
}
return false;
}
}
public enum AccessPathKind
{
Invalid,
Setter,
Adder
}
public struct AccessPathElement : IEquatable<AccessPathElement>
{
public AccessPathElement(IMember member, ILVariable index = null)
{
this.Member = member;
this.Index = index;
}
public IMember Member;
public ILVariable Index;
public override string ToString() => $"[{Member}, {Index}]";
public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath(ILInstruction instruction)
{
List<AccessPathElement> path = new List<AccessPathElement>();
ILVariable target = null;
AccessPathKind kind = AccessPathKind.Invalid;
List<ILInstruction> values = null;
while (instruction != null) {
switch (instruction) {
case CallVirt cv:
var method = cv.Method;
if (method.IsStatic) goto default;
instruction = cv.Arguments[0];
if (values == null) {
values = new List<ILInstruction>(cv.Arguments.Skip(1));
if (values.Count == 0)
goto default;
if (method.IsAccessor) {
kind = AccessPathKind.Setter;
} else {
kind = AccessPathKind.Adder;
}
}
if (method.IsAccessor) {
path.Insert(0, new AccessPathElement(method.AccessorOwner));
} else {
path.Insert(0, new AccessPathElement(method));
}
break;
case LdObj ldobj:
if (ldobj.Target is LdFlda ldflda) {
path.Insert(0, new AccessPathElement(ldflda.Field));
instruction = ldflda.Target;
break;
}
goto default;
case StObj stobj:
if (stobj.Value is Block b && (b.Type == BlockType.ObjectInitializer || b.Type == BlockType.CollectionInitializer) && stobj.Target is LdFlda ldflda2) {
path.Insert(0, new AccessPathElement(ldflda2.Field));
instruction = ldflda2.Target;
if (values == null) {
values = new List<ILInstruction>(new[] { b });
kind = AccessPathKind.Setter;
}
break;
}
goto default;
case LdLoc ldloc:
target = ldloc.Variable;
instruction = null;
break;
default:
kind = AccessPathKind.Invalid;
instruction = null;
break;
}
}
if (kind != AccessPathKind.Invalid && values.OfType<LdLoc>().Any(ld => ld.Variable == target))
kind = AccessPathKind.Invalid;
return (kind, path, values, target);
}
public override bool Equals(object obj)
{
if (obj is AccessPathElement)
return Equals((AccessPathElement)obj);
return false;
}
public override int GetHashCode()
{
int hashCode = 0;
unchecked {
if (Member != null)
hashCode += 1000000007 * Member.GetHashCode();
if (Index != null)
hashCode += 1000000009 * Index.GetHashCode();
}
return hashCode;
}
public bool Equals(AccessPathElement other)
{
return other.Member.Equals(this.Member) && other.Index == this.Index;
}
public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(AccessPathElement lhs, AccessPathElement rhs)
{
return !(lhs == rhs);
}
}
}

8
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

@ -56,11 +56,11 @@ namespace ICSharpCode.Decompiler.Tests @@ -56,11 +56,11 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public void ICSharpCode_Decompiler()
{
try {
//try {
RunWithTest("ICSharpCode.Decompiler", "ICSharpCode.Decompiler.dll", "ICSharpCode.Decompiler.Tests.dll");
} catch (CompilationFailedException) {
Assert.Ignore("Ignored because yield return is not yet implemented!");
}
//} catch (CompilationFailedException) {
// Assert.Ignore("Ignored because yield return is not yet implemented!");
//}
}
[Test]

12
ICSharpCode.Decompiler/Tests/TestCases/Correctness/InitializerTests.cs

@ -515,6 +515,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -515,6 +515,18 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
});
}
public static void ObjectInitializerWithInitializationOfDeeplyNestedObjects()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data {
a = InitializerTests.MyEnum.b,
MoreData =
{
a = InitializerTests.MyEnum.a,
MoreData = { MoreData = { MoreData = { MoreData = { MoreData = { MoreData = { a = MyEnum.b } } } } } }
}
});
}
public static void StructInitializer_DefaultConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData

Loading…
Cancel
Save