Browse Source

Implemented object initializers.

pull/144/head
Daniel Grunwald 14 years ago
parent
commit
600c07388e
  1. 78
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  2. 8
      ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs
  3. 14
      ICSharpCode.Decompiler/ILAst/ILCodes.cs
  4. 1
      ICSharpCode.Decompiler/ILAst/ILInlining.cs
  5. 222
      ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
  6. 15
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  7. 91
      ICSharpCode.Decompiler/Tests/InitializerTests.cs

78
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -703,24 +703,72 @@ namespace ICSharpCode.Decompiler.Ast @@ -703,24 +703,72 @@ namespace ICSharpCode.Decompiler.Ast
return new Ast.YieldBreakStatement();
case ILCode.YieldReturn:
return new Ast.YieldStatement { Expression = arg1 };
case ILCode.InitCollection: {
ObjectCreateExpression oce = (ObjectCreateExpression)arg1;
oce.Initializer = new ArrayInitializerExpression();
case ILCode.InitObject:
case ILCode.InitCollection:
{
ArrayInitializerExpression initializer = new ArrayInitializerExpression();
for (int i = 1; i < args.Count; i++) {
ArrayInitializerExpression aie = args[i] as ArrayInitializerExpression;
if (aie != null && aie.Elements.Count == 1)
oce.Initializer.Elements.Add(aie.Elements.Single().Detach());
else
oce.Initializer.Elements.Add(args[i]);
Match m = objectInitializerPattern.Match(args[i]);
if (m.Success) {
initializer.Elements.Add(
new NamedArgumentExpression {
Identifier = m.Get<MemberReferenceExpression>("left").Single().MemberName,
Expression = m.Get<Expression>("right").Single().Detach()
});
} else {
m = collectionInitializerPattern.Match(args[i]);
if (m.Success) {
if (m.Get("arg").Count() == 1) {
initializer.Elements.Add(m.Get<Expression>("arg").Single().Detach());
} else {
ArrayInitializerExpression argList = new ArrayInitializerExpression();
foreach (var expr in m.Get<Expression>("arg")) {
argList.Elements.Add(expr.Detach());
}
initializer.Elements.Add(argList);
}
} else {
initializer.Elements.Add(args[i]);
}
}
}
ObjectCreateExpression oce = arg1 as ObjectCreateExpression;
if (oce != null) {
oce.Initializer = initializer;
return oce;
} else {
return new AssignmentExpression(arg1, initializer);
}
return oce;
}
case ILCode.InitCollectionAddMethod: {
var collectionInit = new ArrayInitializerExpression();
collectionInit.Elements.AddRange(args);
return collectionInit;
}
default: throw new Exception("Unknown OpCode: " + byteCode.Code);
case ILCode.InitializedObject:
return new InitializedObjectExpression();
default:
throw new Exception("Unknown OpCode: " + byteCode.Code);
}
}
static readonly AstNode objectInitializerPattern = new AssignmentExpression(
new MemberReferenceExpression {
Target = new InitializedObjectExpression()
}.WithName("left"),
new AnyNode("right")
);
static readonly AstNode collectionInitializerPattern = new InvocationExpression {
Target = new MemberReferenceExpression {
Target = new InitializedObjectExpression(),
MemberName = "Add"
},
Arguments = { new Repeat(new AnyNode("arg")) }
};
sealed class InitializedObjectExpression : IdentifierExpression
{
public InitializedObjectExpression() : base("__initialized_object__") {}
protected override bool DoMatch(AstNode other, Match match)
{
return other is InitializedObjectExpression;
}
}

8
ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs

@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.ILAst
TransformDecimalCtorToConstant,
SimplifyLdObjAndStObj,
TransformArrayInitializers,
TransformCollectionInitializers,
TransformObjectInitializers,
MakeAssignmentExpression,
IntroducePostIncrement,
InlineVariables2,
@ -119,10 +119,10 @@ namespace ICSharpCode.Decompiler.ILAst @@ -119,10 +119,10 @@ namespace ICSharpCode.Decompiler.ILAst
modified |= block.RunOptimization(SimplifyLdObjAndStObj);
if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return;
modified |= block.RunOptimization(Initializers.TransformArrayInitializers);
modified |= block.RunOptimization(TransformArrayInitializers);
if (abortBeforeStep == ILAstOptimizationStep.TransformCollectionInitializers) return;
modified |= block.RunOptimization(Initializers.TransformCollectionInitializers);
if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return;
modified |= block.RunOptimization(TransformObjectInitializers);
if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return;
modified |= block.RunOptimization(MakeAssignmentExpression);

14
ICSharpCode.Decompiler/ILAst/ILCodes.cs

@ -238,8 +238,18 @@ namespace ICSharpCode.Decompiler.ILAst @@ -238,8 +238,18 @@ namespace ICSharpCode.Decompiler.ILAst
LogicOr,
NullCoalescing,
InitArray, // Array Initializer
InitCollection, // Collection Initializer: first arg is newobj, remaining args are InitCollectionAddMethod method calls
InitCollectionAddMethod,
// new Class { Prop = 1, Collection = { { 2, 3 }, {4, 5} }}
// is represented as:
// InitObject(newobj Class,
// CallSetter(Prop, InitializedObject, 1),
// InitCollection(CallGetter(Collection, InitializedObject))),
// Call(Add, InitializedObject, 2, 3),
// Call(Add, InitializedObject, 4, 5)))
InitObject, // Object initializer: first arg is newobj, remaining args are the initializing statements
InitCollection, // Collection initializer: first arg is newobj, remaining args are the initializing statements
InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction)
TernaryOp, // ?:
LoopOrSwitchBreak,
LoopContinue,

1
ICSharpCode.Decompiler/ILAst/ILInlining.cs

@ -197,6 +197,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -197,6 +197,7 @@ namespace ICSharpCode.Decompiler.ILAst
{
switch (inlinedExpression.Code) {
case ILCode.InitArray:
case ILCode.InitObject:
case ILCode.InitCollection:
case ILCode.DefaultValue:
return true;

222
ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs

@ -3,8 +3,8 @@ @@ -3,8 +3,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Mono.Cecil;
namespace ICSharpCode.Decompiler.ILAst
@ -12,9 +12,10 @@ namespace ICSharpCode.Decompiler.ILAst @@ -12,9 +12,10 @@ namespace ICSharpCode.Decompiler.ILAst
/// <summary>
/// IL AST transformation that introduces array, object and collection initializers.
/// </summary>
public class Initializers
partial class ILAstOptimizer
{
public static bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
#region Array Initializers
bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos)
{
ILVariable v, v2, v3;
ILExpression newarrExpr;
@ -138,50 +139,201 @@ namespace ICSharpCode.Decompiler.ILAst @@ -138,50 +139,201 @@ namespace ICSharpCode.Decompiler.ILAst
return false;
}
}
#endregion
public static bool TransformCollectionInitializers(List<ILNode> body, ILExpression expr, int pos)
/// <summary>
/// Handles both object and collection initializers.
/// </summary>
bool TransformObjectInitializers(List<ILNode> body, ILExpression expr, int pos)
{
ILVariable v, v2;
Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
ILVariable v;
ILExpression newObjExpr;
MethodReference ctor;
List<ILExpression> ctorArgs;
if (expr.Match(ILCode.Stloc, out v, out newObjExpr) &&
newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs))
{
TypeDefinition td = ctor.DeclaringType.Resolve();
if (td == null || !td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections"))
// v = newObj(ctor, ctorArgs)
if (!(expr.Match(ILCode.Stloc, out v, out newObjExpr) && newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)))
return false;
int originalPos = pos;
ILInlining inlining = null;
ILExpression initializer;
if (IsCollectionType(ctor.DeclaringType)) {
// Collection Initializer
initializer = ParseCollectionInitializer(body, ref pos, v, newObjExpr);
if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
return false;
// This is a collection: we can convert Add() calls into a collection initializer
ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, newObjExpr);
bool anyAdded = false;
while(pos + 1 < body.Count) {
ILExpression nextExpr = body[pos + 1] as ILExpression;
MethodReference addMethod;
List<ILExpression> args;
if (nextExpr.Match(ILCode.Callvirt, out addMethod, out args) &&
addMethod.Name == "Add" &&
addMethod.HasThis &&
args.Count >= 2 &&
args[0].Match(ILCode.Ldloc, out v2) &&
v == v2)
{
nextExpr.Code = ILCode.InitCollectionAddMethod;
nextExpr.Arguments.RemoveAt(0);
collectionInitializer.Arguments.Add(nextExpr);
body.RemoveAt(pos + 1);
anyAdded = true;
} else {
// Object Initializer
initializer = new ILExpression(ILCode.InitObject, null, newObjExpr);
pos++;
while (pos < body.Count) {
ILExpression nextExpr = body[pos] as ILExpression;
if (IsSetterInObjectInitializer(nextExpr, v)) {
initializer.Arguments.Add(nextExpr);
pos++;
} else {
break;
ILVariable collectionVar;
ILExpression getCollection;
if (IsCollectionGetterInObjectInitializer(nextExpr, v, out collectionVar, out getCollection)) {
int posBeforeCollectionInitializer = pos;
ILExpression collectionInitializer = ParseCollectionInitializer(body, ref pos, collectionVar, getCollection);
// Validate that the 'collectionVar' is not read (except in the initializers)
if (inlining == null) {
// instantiate ILInlining only once - we don't change the method while analysing the object initializer
inlining = new ILInlining(method);
}
if (inlining.numLdloc.GetOrDefault(collectionVar) == collectionInitializer.Arguments.Count
&& inlining.numStloc.GetOrDefault(collectionVar) == 1
&& inlining.numLdloca.GetOrDefault(collectionVar) == 0)
{
initializer.Arguments.Add(collectionInitializer);
// no need to increment 'pos' here: ParseCollectionInitializer already did that
} else {
// Consider the object initializer to have ended in front of the 'collection getter' instruction.
pos = posBeforeCollectionInitializer;
break;
}
} else {
// can't match any more initializers: end of object initializer
break;
}
}
}
// ensure we added at least one additional arg to the collection initializer:
if (anyAdded) {
expr.Arguments[0] = collectionInitializer;
return true;
if (initializer.Arguments.Count == 1)
return false; // no initializers were matched (the single argument is the newobjExpr)
}
// Verify that we can inline 'v' into the next instruction:
if (!CanInlineInitializer(body, pos, v, initializer, inlining ?? new ILInlining(method)))
return false;
expr.Arguments[0] = initializer;
// remove all the instructions that were pulled into the initializer
body.RemoveRange(originalPos + 1, pos - originalPos - 1);
// now that we know that it's an object initializer, change all the first arguments to 'InitializedObject'
ChangeFirstArgumentToInitializedObject(initializer);
return true;
}
/// <summary>
/// Gets whether the type supports collection initializers.
/// </summary>
static bool IsCollectionType(TypeReference tr)
{
if (tr == null)
return false;
TypeDefinition td = tr.Resolve();
return td != null && td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections");
}
/// <summary>
/// Gets whether 'expr' represents a setter in an object initializer.
/// ('CallvirtSetter(Property, v, value)')
/// </summary>
/// <param name="expr">The expression to test</param>
/// <param name="v">The variable that contains the object being initialized</param>
static bool IsSetterInObjectInitializer(ILExpression expr, ILVariable v)
{
if (expr == null)
return false;
if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.Stfld) {
if (expr.Arguments.Count == 2) {
return expr.Arguments[0].MatchLdloc(v);
}
}
return false;
}
/// <summary>
/// Gets whether 'expr' represents getting a collection in an object initializer.
/// ('collectionVar = callvirtGetter(Property, v)')
/// </summary>
/// <param name="expr">The expression to test</param>
/// <param name="v">The variable that contains the object being initialized</param>
static bool IsCollectionGetterInObjectInitializer(ILExpression expr, ILVariable v, out ILVariable collectionVar, out ILExpression init)
{
if (expr.Match(ILCode.Stloc, out collectionVar, out init)) {
if (init.Code == ILCode.Ldfld || init.Code == ILCode.CallvirtGetter) {
if (init.Arguments.Count == 1 && init.Arguments[0].MatchLdloc(v)) {
MemberReference mr = (MemberReference)init.Operand;
return IsCollectionType(mr.DeclaringType);
}
}
}
return false;
}
/// <summary>
/// Parses a collection initializer.
/// </summary>
/// <param name="body">ILAst block</param>
/// <param name="pos">
/// Input: position of the instruction assigning to 'v'.
/// Output: first position after the collection initializer
/// </param>
/// <param name="v">The variable that holds the collection</param>
/// <param name="getCollection">The initial value of the collection (newobj instruction)</param>
/// <returns>InitCollection instruction; or null if parsing failed</returns>
ILExpression ParseCollectionInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression getCollection)
{
Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc);
ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, getCollection);
// Take care not to modify any existing ILExpressions in here.
// We just construct new ones around the old ones, any modifications must wait until the whole
// object/collection initializer was analyzed.
while (++pos < body.Count) {
ILExpression nextExpr = body[pos] as ILExpression;
MethodReference addMethod;
List<ILExpression> args;
if (nextExpr.Match(ILCode.Callvirt, out addMethod, out args)) {
if (addMethod.Name == "Add" && addMethod.HasThis) {
if (args.Count >= 2 && args[0].MatchLdloc(v)) {
collectionInitializer.Arguments.Add(nextExpr);
} else {
break;
}
} else {
break;
}
} else {
break;
}
}
return collectionInitializer;
}
static bool CanInlineInitializer(List<ILNode> body, int pos, ILVariable v, ILExpression initializer, ILInlining inlining)
{
if (pos >= body.Count)
return false; // reached end of block, but there should be another instruction which consumes the initialized object
// one ldloc for each initializer argument except for the newobj, and another ldloc for the use of the initialized object
if (inlining.numLdloc.GetOrDefault(v) != initializer.Arguments.Count)
return false;
if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0))
return false;
ILExpression nextExpr = body[pos] as ILExpression;
return inlining.CanInlineInto(nextExpr, v, initializer);
}
static void ChangeFirstArgumentToInitializedObject(ILExpression initializer)
{
// Go through all elements in the initializer (so skip the newobj-instr. at the start)
for (int i = 1; i < initializer.Arguments.Count; i++) {
ILExpression element = initializer.Arguments[i];
ILExpression arg;
if (element.Code == ILCode.InitCollection) {
// nested collection initializer
ILExpression getCollection = element.Arguments[0];
arg = getCollection.Arguments[0];
ChangeFirstArgumentToInitializedObject(element); // handle the collection elements
} else {
arg = element.Arguments[0];
}
Debug.Assert(arg.Code == ILCode.Ldloc);
arg.Code = ILCode.InitializedObject;
arg.Operand = null;
}
}
}
}

15
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -335,18 +335,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -335,18 +335,13 @@ namespace ICSharpCode.Decompiler.ILAst
}
return ctor.DeclaringType;
}
case ILCode.InitObject:
case ILCode.InitCollection:
return InferTypeForExpression(expr.Arguments[0], expectedType);
case ILCode.InitCollectionAddMethod:
{
MethodReference addMethod = (MethodReference)expr.Operand;
if (forceInferChildren) {
for (int i = 0; i < addMethod.Parameters.Count; i++) {
InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(addMethod.Parameters[i].ParameterType, addMethod));
}
}
return addMethod.DeclaringType;
}
case ILCode.InitializedObject:
// expectedType should always be known due to the parent method call / property setter
Debug.Assert(expectedType != null);
return expectedType;
#endregion
#region Load/Store Fields
case ILCode.Ldfld:

91
ICSharpCode.Decompiler/Tests/InitializerTests.cs

@ -17,6 +17,21 @@ public class InitializerTests @@ -17,6 +17,21 @@ public class InitializerTests
c,
d
}
class Data
{
public InitializerTests.MyEnum a
{
get;
set;
}
public List<InitializerTests.MyEnum2> PropertyList
{
get;
set;
}
public List<InitializerTests.MyEnum2> FieldList = new List<InitializerTests.MyEnum2>();
}
// Helper methods used to ensure initializers used within expressions work correctly
static void X(object a, object b)
@ -140,4 +155,80 @@ public class InitializerTests @@ -140,4 +155,80 @@ public class InitializerTests
{ InitializerTests.MyEnum.b, InitializerTests.MyEnum2.d }
});
}
public static void NotACollectionInitializer()
{
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
X(Y(), list);
}
public static void ObjectInitializer()
{
X(Y(), new Data
{
a = InitializerTests.MyEnum.a
});
}
public static void NotAObjectInitializer()
{
Data data = new InitializerTests.Data();
data.a = InitializerTests.MyEnum.a;
X(Y(), data);
}
public static void ObjectInitializerAssignCollectionToField()
{
X(Y(), new InitializerTests.Data
{
a = InitializerTests.MyEnum.a,
FieldList = new List<InitializerTests.MyEnum2>
{
InitializerTests.MyEnum2.c,
InitializerTests.MyEnum2.d
}
});
}
public static void ObjectInitializerAddToCollectionInField()
{
X(Y(), new InitializerTests.Data
{
a = InitializerTests.MyEnum.a,
FieldList =
{
InitializerTests.MyEnum2.c,
InitializerTests.MyEnum2.d
}
});
}
public static void ObjectInitializerAssignCollectionToProperty()
{
X(Y(), new InitializerTests.Data
{
a = InitializerTests.MyEnum.a,
PropertyList = new List<InitializerTests.MyEnum2>
{
InitializerTests.MyEnum2.c,
InitializerTests.MyEnum2.d
}
});
}
public static void ObjectInitializerAddToCollectionInProperty()
{
X(Y(), new InitializerTests.Data
{
a = InitializerTests.MyEnum.a,
PropertyList =
{
InitializerTests.MyEnum2.c,
InitializerTests.MyEnum2.d
}
});
}
}

Loading…
Cancel
Save