Browse Source

Fix #249: Object Initializer not detected for value types

pull/252/head
Daniel Grunwald 14 years ago
parent
commit
3fad5cb76b
  1. 6
      ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs
  2. 4
      ICSharpCode.Decompiler/ILAst/ILCodes.cs
  3. 90
      ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs
  4. 33
      ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs
  5. 79
      ICSharpCode.Decompiler/Tests/InitializerTests.cs

6
ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs

@ -782,9 +782,15 @@ namespace ICSharpCode.Decompiler.Ast @@ -782,9 +782,15 @@ namespace ICSharpCode.Decompiler.Ast
}
}
ObjectCreateExpression oce = arg1 as ObjectCreateExpression;
DefaultValueExpression dve = arg1 as DefaultValueExpression;
if (oce != null) {
oce.Initializer = initializer;
return oce;
} else if (dve != null) {
oce = new ObjectCreateExpression(dve.Type.Detach());
oce.CopyAnnotationsFrom(dve);
oce.Initializer = initializer;
return oce;
} else {
return new AssignmentExpression(arg1, initializer);
}

4
ICSharpCode.Decompiler/ILAst/ILCodes.cs

@ -261,8 +261,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -261,8 +261,8 @@ namespace ICSharpCode.Decompiler.ILAst
// 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
InitObject, // Object initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
InitCollection, // Collection initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction)
TernaryOp, // ?:

90
ICSharpCode.Decompiler/ILAst/InitializerPeepholeTransforms.cs

@ -232,18 +232,45 @@ namespace ICSharpCode.Decompiler.ILAst @@ -232,18 +232,45 @@ namespace ICSharpCode.Decompiler.ILAst
Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
ILVariable v;
ILExpression newObjExpr;
TypeReference newObjType;
bool isValueType;
MethodReference ctor;
List<ILExpression> ctorArgs;
// v = newObj(ctor, ctorArgs)
if (!(expr.Match(ILCode.Stloc, out v, out newObjExpr) && newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)))
if (expr.Match(ILCode.Stloc, out v, out newObjExpr)) {
if (newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) {
// v = newObj(ctor, ctorArgs)
newObjType = ctor.DeclaringType;
isValueType = false;
} else if (newObjExpr.Match(ILCode.DefaultValue, out newObjType)) {
// v = defaultvalue(type)
isValueType = true;
} else {
return false;
}
} else if (expr.Match(ILCode.Call, out ctor, out ctorArgs)) {
// call(SomeStruct::.ctor, ldloca(v), remainingArgs)
if (ctorArgs.Count > 0 && ctorArgs[0].Match(ILCode.Ldloca, out v)) {
isValueType = true;
newObjType = ctor.DeclaringType;
ctorArgs = new List<ILExpression>(ctorArgs);
ctorArgs.RemoveAt(0);
newObjExpr = new ILExpression(ILCode.Newobj, ctor, ctorArgs);
} else {
return false;
}
} else {
return false;
}
if (newObjType.IsValueType != isValueType)
return false;
int originalPos = pos;
// don't use object initializer syntax for closures
if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, ctor.DeclaringType.ResolveWithinSameModule()))
if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, newObjType.ResolveWithinSameModule()))
return false;
ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(ctor.DeclaringType));
ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(newObjType), isValueType);
if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
return false;
@ -256,16 +283,36 @@ namespace ICSharpCode.Decompiler.ILAst @@ -256,16 +283,36 @@ namespace ICSharpCode.Decompiler.ILAst
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;
if (isValueType) {
// one ldloc for the use of the initialized object
if (inlining.numLdloc.GetOrDefault(v) != 1)
return false;
// one ldloca for each initializer argument, and also for the ctor call (if it exists)
if (inlining.numLdloca.GetOrDefault(v) != totalElementCount + (expr.Code == ILCode.Call ? 1 : 0))
return false;
// one stloc for the initial store (if no ctor call was used)
if (inlining.numStloc.GetOrDefault(v) != (expr.Code == ILCode.Call ? 0 : 1))
return false;
} else {
// 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;
if (expr.Code == ILCode.Stloc) {
expr.Arguments[0] = initializer;
} else {
Debug.Assert(expr.Code == ILCode.Call);
expr.Code = ILCode.Stloc;
expr.Operand = v;
expr.Arguments.Clear();
expr.Arguments.Add(initializer);
}
// remove all the instructions that were pulled into the initializer
body.RemoveRange(originalPos + 1, pos - originalPos - 1);
@ -302,7 +349,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -302,7 +349,7 @@ namespace ICSharpCode.Decompiler.ILAst
{
if (expr == null)
return false;
if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.Stfld) {
if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.CallSetter || expr.Code == ILCode.Stfld) {
return expr.Arguments.Count == 2;
}
return false;
@ -334,9 +381,8 @@ namespace ICSharpCode.Decompiler.ILAst @@ -334,9 +381,8 @@ namespace ICSharpCode.Decompiler.ILAst
/// <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, bool isCollection)
ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection, bool isValueType)
{
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.
@ -346,13 +392,13 @@ namespace ICSharpCode.Decompiler.ILAst @@ -346,13 +392,13 @@ namespace ICSharpCode.Decompiler.ILAst
while (++pos < body.Count) {
ILExpression nextExpr = body[pos] as ILExpression;
if (IsSetterInObjectInitializer(nextExpr)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false, isValueType)) {
CleanupInitializerStackAfterFailedAdjustment(initializerStack);
break;
}
initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr);
} else if (IsAddMethodCall(nextExpr)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true)) {
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true, isValueType)) {
CleanupInitializerStackAfterFailedAdjustment(initializerStack);
break;
}
@ -365,20 +411,26 @@ namespace ICSharpCode.Decompiler.ILAst @@ -365,20 +411,26 @@ namespace ICSharpCode.Decompiler.ILAst
return objectInitializer;
}
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection)
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType)
{
// 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) {
while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || 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;
if (isValueType) {
ILVariable loadedVar;
if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v))
return false;
} else {
if (!argument.MatchLdloc(v))
return false;
}
// 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++) {

33
ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs

@ -323,13 +323,7 @@ namespace ICSharpCode.Decompiler.ILAst @@ -323,13 +323,7 @@ namespace ICSharpCode.Decompiler.ILAst
if (forceInferChildren) {
for (int i = 0; i < expr.Arguments.Count; i++) {
if (i == 0 && method.HasThis) {
ILExpressionPrefix constraint = expr.GetPrefix(ILCode.Constrained);
if (constraint != null)
InferTypeForExpression(expr.Arguments[i], new ByReferenceType((TypeReference)constraint.Operand));
else if (method.DeclaringType.IsValueType)
InferTypeForExpression(expr.Arguments[i], new ByReferenceType(method.DeclaringType));
else
InferTypeForExpression(expr.Arguments[i], method.DeclaringType);
InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(method.DeclaringType, expr.GetPrefix(ILCode.Constrained)));
} else {
InferTypeForExpression(expr.Arguments[i], SubstituteTypeArgs(method.Parameters[method.HasThis ? i - 1 : i].ParameterType, method));
}
@ -361,17 +355,22 @@ namespace ICSharpCode.Decompiler.ILAst @@ -361,17 +355,22 @@ namespace ICSharpCode.Decompiler.ILAst
#endregion
#region Load/Store Fields
case ILCode.Ldfld:
if (forceInferChildren)
InferTypeForExpression(expr.Arguments[0], ((FieldReference)expr.Operand).DeclaringType);
if (forceInferChildren) {
InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
}
return GetFieldType((FieldReference)expr.Operand);
case ILCode.Ldsfld:
return GetFieldType((FieldReference)expr.Operand);
case ILCode.Ldflda:
if (forceInferChildren) {
InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
}
return new ByReferenceType(GetFieldType((FieldReference)expr.Operand));
case ILCode.Ldsflda:
return new ByReferenceType(GetFieldType((FieldReference)expr.Operand));
case ILCode.Stfld:
if (forceInferChildren) {
InferTypeForExpression(expr.Arguments[0], ((FieldReference)expr.Operand).DeclaringType);
InferTypeForExpression(expr.Arguments[0], MakeRefIfValueType(((FieldReference)expr.Operand).DeclaringType, expr.GetPrefix(ILCode.Constrained)));
InferTypeForExpression(expr.Arguments[1], GetFieldType((FieldReference)expr.Operand));
}
return GetFieldType((FieldReference)expr.Operand);
@ -796,6 +795,20 @@ namespace ICSharpCode.Decompiler.ILAst @@ -796,6 +795,20 @@ namespace ICSharpCode.Decompiler.ILAst
}
}
/// <summary>
/// Wraps 'type' in a ByReferenceType if it is a value type. If a constrained prefix is specified,
/// returns the constrained type wrapped in a ByReferenceType.
/// </summary>
TypeReference MakeRefIfValueType(TypeReference type, ILExpressionPrefix constrainedPrefix)
{
if (constrainedPrefix != null)
return new ByReferenceType((TypeReference)constrainedPrefix.Operand);
if (type.IsValueType)
return new ByReferenceType(type);
else
return type;
}
/// <summary>
/// Promotes primitive types smaller than int32 to int32.
/// </summary>

79
ICSharpCode.Decompiler/Tests/InitializerTests.cs

@ -52,8 +52,37 @@ public class InitializerTests @@ -52,8 +52,37 @@ public class InitializerTests
get;
set;
}
public InitializerTests.StructData NestedStruct
{
get;
set;
}
}
private struct StructData
{
public int Field;
public int Property
{
get;
set;
}
public InitializerTests.Data MoreData
{
get;
set;
}
public StructData(int initialValue)
{
this = default(InitializerTests.StructData);
this.Field = initialValue;
this.Property = initialValue;
}
}
// Helper methods used to ensure initializers used within expressions work correctly
private static void X(object a, object b)
{
@ -456,7 +485,53 @@ public class InitializerTests @@ -456,7 +485,53 @@ public class InitializerTests
}
});
}
public static void StructInitializer_DefaultConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData
{
Field = 1,
Property = 2
});
}
public static void StructInitializer_ExplicitConstructor()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData(0)
{
Field = 1,
Property = 2
});
}
public static void StructInitializerWithInitializationOfNestedObjects()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.StructData
{
MoreData =
{
a = InitializerTests.MyEnum.a,
FieldList =
{
InitializerTests.MyEnum2.c,
InitializerTests.MyEnum2.d
}
}
});
}
public static void StructInitializerWithinObjectInitializer()
{
InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data
{
NestedStruct = new InitializerTests.StructData(2)
{
Field = 1,
Property = 2
}
});
}
public void MultidimensionalInit()
{
int[,] expr_09 = new int[, ]

Loading…
Cancel
Save