Browse Source

Implemented support for nested object/collection initializers.

pull/144/head
Daniel Grunwald 14 years ago
parent
commit
207bb984aa
  1. 210
      ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
  2. 4
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  3. 13
      ICSharpCode.Decompiler/Tests/InitializerTests.cs

210
ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs

@ -155,13 +155,10 @@ namespace ICSharpCode.Decompiler.ILAst @@ -155,13 +155,10 @@ namespace ICSharpCode.Decompiler.ILAst
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;
} else {
// Object Initializer
@ -169,46 +166,27 @@ namespace ICSharpCode.Decompiler.ILAst @@ -169,46 +166,27 @@ namespace ICSharpCode.Decompiler.ILAst
if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule()))
return false;
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 {
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;
}
}
}
if (initializer.Arguments.Count == 1)
return false; // no initializers were matched (the single argument is the newobjExpr)
initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr);
}
if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
return false;
int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections
Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1);
// Verify that we can inline 'v' into the next instruction:
if (!CanInlineInitializer(body, pos, v, initializer, inlining ?? new ILInlining(method)))
if (pos >= body.Count)
return false; // reached end of block, but there should be another instruction which consumes the initialized object
ILInlining inlining = new ILInlining(method);
// one ldloc for each initializer argument, and another ldloc for the use of the initialized object
if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1)
return false;
if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0))
return false;
ILExpression nextExpr = body[pos] as ILExpression;
if (!inlining.CanInlineInto(nextExpr, v, initializer))
return false;
expr.Arguments[0] = initializer;
@ -236,34 +214,26 @@ namespace ICSharpCode.Decompiler.ILAst @@ -236,34 +214,26 @@ namespace ICSharpCode.Decompiler.ILAst
/// 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)
static bool IsSetterInObjectInitializer(ILExpression expr)
{
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 expr.Arguments.Count == 2;
}
return false;
}
/// <summary>
/// Gets whether 'expr' represents getting a collection in an object initializer.
/// ('collectionVar = callvirtGetter(Property, v)')
/// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer.
/// </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)
static bool IsAddMethodCall(ILExpression expr)
{
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);
}
MethodReference addMethod;
List<ILExpression> args;
if (expr.Match(ILCode.Callvirt, out addMethod, out args)) {
if (addMethod.Name == "Add" && addMethod.HasThis) {
return args.Count >= 2;
}
}
return false;
@ -278,29 +248,19 @@ namespace ICSharpCode.Decompiler.ILAst @@ -278,29 +248,19 @@ namespace ICSharpCode.Decompiler.ILAst
/// 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)
/// <param name="newObjExpr">The initial value of the collection (newobj instruction)</param>
/// <returns>InitCollection instruction</returns>
ILExpression ParseCollectionInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr)
{
Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc);
ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, getCollection);
ILExpression collectionInitializer = new ILExpression(ILCode.InitCollection, null, newObjExpr);
// 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;
}
if (IsAddMethodCall(nextExpr) && nextExpr.Arguments[0].MatchLdloc(v)) {
collectionInitializer.Arguments.Add(nextExpr);
} else {
break;
}
@ -308,17 +268,89 @@ namespace ICSharpCode.Decompiler.ILAst @@ -308,17 +268,89 @@ namespace ICSharpCode.Decompiler.ILAst
return collectionInitializer;
}
static bool CanInlineInitializer(List<ILNode> body, int pos, ILVariable v, ILExpression initializer, ILInlining inlining)
/// <summary>
/// Parses an object initializer.
/// </summary>
/// <param name="body">ILAst block</param>
/// <param name="pos">
/// Input: position of the instruction assigning to 'v'.
/// Output: first position after the object initializer
/// </param>
/// <param name="v">The variable that holds the object being initialized</param>
/// <param name="newObjExpr">The newobj instruction</param>
/// <returns>InitObject instruction</returns>
ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr)
{
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))
Debug.Assert(((ILExpression)body[pos]).Code == ILCode.Stloc);
// 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.
ILExpression objectInitializer = new ILExpression(ILCode.InitObject, null, newObjExpr);
List<ILExpression> initializerStack = new List<ILExpression>();
initializerStack.Add(objectInitializer);
while (++pos < body.Count) {
ILExpression nextExpr = body[pos] as ILExpression;
if (IsSetterInObjectInitializer(nextExpr)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false))
break;
initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
} else if (IsAddMethodCall(nextExpr)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true))
break;
initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
} else {
// can't match any more initializers: end of object initializer
break;
}
}
return objectInitializer;
}
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection)
{
// Argument is of the form 'getter(getter(...(v)))'
// Unpack it into a list of getters:
List<ILExpression> getters = new List<ILExpression>();
while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.Ldfld) {
getters.Add(argument);
if (argument.Arguments.Count != 1)
return false;
argument = argument.Arguments[0];
}
// Ensure that the final argument is 'v'
if (!argument.MatchLdloc(v))
return false;
ILExpression nextExpr = body[pos] as ILExpression;
return inlining.CanInlineInto(nextExpr, v, initializer);
// Now compare the getters with those that are currently active on the initializer stack:
int i;
for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) {
ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer
ILExpression g2 = getters[getters.Count - i]; // matching getter from argument
if (g1.Operand != g2.Operand) {
// operands differ, so we abort the comparison
break;
}
}
// Remove all initializers from the stack that were not matched with one from the argument:
initializerStack.RemoveRange(i, initializerStack.Count - i);
// Now create new initializers for the remaining arguments:
for (; i <= getters.Count; i++) {
ILExpression g = getters[getters.Count - i];
MemberReference mr = (MemberReference)g.Operand;
TypeReference returnType;
if (mr is FieldReference)
returnType = TypeAnalysis.GetFieldType((FieldReference)mr);
else
returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr);
ILExpression nestedInitializer = new ILExpression(
IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject,
null, g);
// add new initializer to its parent, and push it on the stack:
initializerStack[initializerStack.Count - 1].Arguments.Add(nestedInitializer);
initializerStack.Add(nestedInitializer);
}
ILExpression lastInitializer = initializerStack[initializerStack.Count - 1];
return (lastInitializer.Code == ILCode.InitCollection) == isCollection;
}
static void ChangeFirstArgumentToInitializedObject(ILExpression initializer)
@ -326,18 +358,14 @@ namespace ICSharpCode.Decompiler.ILAst @@ -326,18 +358,14 @@ namespace ICSharpCode.Decompiler.ILAst
// 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
if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) {
// nested collection/object initializer
ILExpression getCollection = element.Arguments[0];
arg = getCollection.Arguments[0];
getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
ChangeFirstArgumentToInitializedObject(element); // handle the collection elements
} else {
arg = element.Arguments[0];
element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null);
}
Debug.Assert(arg.Code == ILCode.Ldloc);
arg.Code = ILCode.InitializedObject;
arg.Operand = null;
}
}
}

4
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -748,12 +748,12 @@ namespace ICSharpCode.Decompiler.ILAst @@ -748,12 +748,12 @@ namespace ICSharpCode.Decompiler.ILAst
return resultType;
}
static TypeReference GetFieldType(FieldReference fieldReference)
public static TypeReference GetFieldType(FieldReference fieldReference)
{
return SubstituteTypeArgs(UnpackModifiers(fieldReference.FieldType), fieldReference);
}
static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member)
public static TypeReference SubstituteTypeArgs(TypeReference type, MemberReference member)
{
if (type is TypeSpecification) {
ArrayType arrayType = type as ArrayType;

13
ICSharpCode.Decompiler/Tests/InitializerTests.cs

@ -31,6 +31,8 @@ public class InitializerTests @@ -31,6 +31,8 @@ public class InitializerTests
set;
}
public List<InitializerTests.MyEnum2> FieldList = new List<InitializerTests.MyEnum2>();
public InitializerTests.Data MoreData { get; set; }
}
// Helper methods used to ensure initializers used within expressions work correctly
@ -231,4 +233,15 @@ public class InitializerTests @@ -231,4 +233,15 @@ public class InitializerTests
}
});
}
public static void ObjectInitializerWithInitializationOfNestedObjects()
{
X(Y(), new InitializerTests.Data
{
MoreData =
{
a = InitializerTests.MyEnum.a
}
});
}
}

Loading…
Cancel
Save