Browse Source

TransformCollectionAndObjectInitializers: Support struct initializers, add more unit tests

pull/734/merge
Siegfried Pammer 8 years ago
parent
commit
e0437896be
  1. 20
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  2. 68
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  3. 18
      ICSharpCode.Decompiler/Tests/TestCases/Correctness/InitializerTests.cs

20
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1422,9 +1422,24 @@ namespace ICSharpCode.Decompiler.CSharp
{
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)
if (stloc == null || final == null || stloc.Variable != final.Variable)
throw new ArgumentException("given Block is invalid!");
var initObjRR = new InitializedObjectResolveResult(newObjInst.Method.DeclaringType);
InitializedObjectResolveResult initObjRR;
TranslatedExpression expr;
switch (stloc.Value) {
case NewObj newObjInst:
initObjRR = new InitializedObjectResolveResult(newObjInst.Method.DeclaringType);
expr = HandleCallInstruction(newObjInst);
break;
case DefaultValue defaultVal:
initObjRR = new InitializedObjectResolveResult(defaultVal.Type);
expr = new ObjectCreateExpression(ConvertType(defaultVal.Type))
.WithILInstruction(defaultVal)
.WithRR(new TypeResolveResult(defaultVal.Type));
break;
default:
throw new ArgumentException("given Block is invalid!");
}
var elementsStack = new Stack<List<Expression>>();
var elements = new List<Expression>(block.Instructions.Count);
elementsStack.Push(elements);
@ -1467,7 +1482,6 @@ namespace ICSharpCode.Decompiler.CSharp
var values = elementsStack.Pop();
elementsStack.Peek().Add(MakeInitializerAssignment(pathElement.Member, values));
}
var expr = HandleCallInstruction(newObjInst);
var oce = (ObjectCreateExpression)expr.Expression;
oce.Initializer = new ArrayInitializerExpression(elements);
return expr.WithILInstruction(block);

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

@ -28,7 +28,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
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) {
if (inst.MatchStLoc(out var v, out var initInst)) {
switch (initInst) {
case NewObj newObjInst:
case DefaultValue defaultVal:
break;
default:
return false;
}
context.Step("CollectionOrObjectInitializer", inst);
int initializerItemsCount = 0;
var blockType = BlockType.CollectionInitializer;
@ -47,17 +54,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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 = (CallVirt)callVirt.Clone();
var newTarget = newCallVirt.Arguments[0];
foreach (var load in newTarget.Descendants.OfType<LdLoc>())
load.Variable = finalSlot;
initBlock.Instructions.Add(newCallVirt);
case CallInstruction call:
if (!(call is CallVirt || call is Call)) continue;
var newCall = (CallInstruction)call.Clone();
var newTarget = newCall.Arguments[0];
foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca)
load.Variable = finalSlot;
initBlock.Instructions.Add(newCall);
break;
case StObj stObj:
var newStObj = (StObj)stObj.Clone();
foreach (var load in newStObj.Target.Descendants.OfType<LdLoc>())
load.Variable = finalSlot;
foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca)
load.Variable = finalSlot;
initBlock.Instructions.Add(newStObj);
break;
}
@ -87,28 +97,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
}
}
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 BlockType type)
{
if (cv.Method.IsAccessor && cv.Method.AccessorOwner is IProperty p && p.Setter == cv.Method) {
type = BlockType.ObjectInitializer;
return true;
} else if (cv.Method.Name.Equals("Add", StringComparison.Ordinal)) {
return true;
}
return false;
}
}
public enum AccessPathKind
@ -137,14 +125,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILVariable target = null;
AccessPathKind kind = AccessPathKind.Invalid;
List<ILInstruction> values = null;
IMethod method;
while (instruction != null) {
switch (instruction) {
case CallVirt cv:
var method = cv.Method;
case CallInstruction call:
if (!(call is CallVirt || call is Call)) goto default;
method = call.Method;
if (method.IsStatic) goto default;
instruction = cv.Arguments[0];
instruction = call.Arguments[0];
if (values == null) {
values = new List<ILInstruction>(cv.Arguments.Skip(1));
values = new List<ILInstruction>(call.Arguments.Skip(1));
if (values.Count == 0)
goto default;
if (method.IsAccessor) {
@ -167,11 +157,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
goto default;
case StObj stobj:
if (stobj.Value is Block b && (b.Type == BlockType.ObjectInitializer || b.Type == BlockType.CollectionInitializer) && stobj.Target is LdFlda ldflda2) {
if (stobj.Target is LdFlda ldflda2) {
path.Insert(0, new AccessPathElement(ldflda2.Field));
instruction = ldflda2.Target;
if (values == null) {
values = new List<ILInstruction>(new[] { b });
values = new List<ILInstruction>(new[] { stobj.Value });
kind = AccessPathKind.Setter;
}
break;
@ -181,6 +171,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = ldloc.Variable;
instruction = null;
break;
case LdLoca ldloca:
target = ldloca.Variable;
instruction = null;
break;
default:
kind = AccessPathKind.Invalid;
instruction = null;

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

@ -445,7 +445,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
});
}
public static void NotAObjectInitializer()
public static void NotAnObjectInitializer()
{
InitializerTests.Data data = new InitializerTests.Data();
data.a = InitializerTests.MyEnum.a;
@ -527,6 +527,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
});
}
public static void NotAStructInitializer_DefaultConstructor()
{
InitializerTests.StructData data = new InitializerTests.StructData();
data.Field = 1;
data.Property = 2;
InitializerTests.X(InitializerTests.Y(), data);
}
public static void StructInitializer_DefaultConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData
@ -536,6 +544,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
});
}
public static void NotAStructInitializer_ExplicitConstructor()
{
InitializerTests.StructData data = new InitializerTests.StructData(0);
data.Field = 1;
data.Property = 2;
InitializerTests.X(InitializerTests.Y(), data);
}
public static void StructInitializer_ExplicitConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0)

Loading…
Cancel
Save