Browse Source

TransformCollectionAndObjectInitializers: Extend existing initializer if possible; recognize calls to Add only if target implements IEnumerable

pull/734/merge
Siegfried Pammer 8 years ago
parent
commit
2b92a93e2f
  1. 70
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

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

@ -43,22 +43,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
ILInstruction inst = body.Instructions[pos]; ILInstruction inst = body.Instructions[pos];
// Match stloc(v, newobj) // Match stloc(v, newobj)
if (inst.MatchStLoc(out var v, out var initInst)) { if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)) {
Block initializerBlock = null;
switch (initInst) { switch (initInst) {
case NewObj newObjInst: case NewObj newObjInst:
if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst)) if (DelegateConstruction.IsDelegateConstruction(newObjInst) || DelegateConstruction.IsPotentialClosure(context, newObjInst))
return false; return false;
if (newObjInst.Method.DeclaringType.Kind != TypeKind.Struct && v.Kind != VariableKind.StackSlot)
return false;
break; break;
case DefaultValue defaultVal: case DefaultValue defaultVal:
break; break;
case Block existingInitializer:
if (existingInitializer.Type == BlockType.CollectionInitializer || existingInitializer.Type == BlockType.ObjectInitializer) {
initializerBlock = existingInitializer;
break;
}
return false;
default: default:
return false; return false;
} }
context.Step("CollectionOrObjectInitializer", inst); context.Step("CollectionOrObjectInitializer", inst);
int initializerItemsCount = 0; int initializerItemsCount = 0;
var blockType = BlockType.CollectionInitializer; var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer;
// Detect initializer type by scanning the following statements // Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument // 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 a setter we're dealing with an object initializer
@ -68,10 +73,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
initializerItemsCount++; initializerItemsCount++;
if (initializerItemsCount == 0) if (initializerItemsCount == 0)
return false; return false;
Block initBlock = new Block(blockType); ILVariable finalSlot;
var finalSlot = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type); if (initializerBlock == null) {
initBlock.FinalInstruction = new LdLoc(finalSlot); initializerBlock = new Block(blockType);
initBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); finalSlot = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type);
initializerBlock.FinalInstruction = new LdLoc(finalSlot);
initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
} else {
finalSlot = ((LdLoc)initializerBlock.FinalInstruction).Variable;
}
for (int i = 1; i <= initializerItemsCount; i++) { for (int i = 1; i <= initializerItemsCount; i++) {
switch (body.Instructions[i + pos]) { switch (body.Instructions[i + pos]) {
case CallInstruction call: case CallInstruction call:
@ -81,19 +91,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>()) foreach (var load in newTarget.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca) if (load is LdLoc || load is LdLoca)
load.Variable = finalSlot; load.Variable = finalSlot;
initBlock.Instructions.Add(newCall); initializerBlock.Instructions.Add(newCall);
break; break;
case StObj stObj: case StObj stObj:
var newStObj = (StObj)stObj.Clone(); var newStObj = (StObj)stObj.Clone();
foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>()) foreach (var load in newStObj.Target.Descendants.OfType<IInstructionWithVariableOperand>())
if (load is LdLoc || load is LdLoca) if (load is LdLoc || load is LdLoca)
load.Variable = finalSlot; load.Variable = finalSlot;
initBlock.Instructions.Add(newStObj); initializerBlock.Instructions.Add(newStObj);
break; break;
} }
} }
initInst.ReplaceWith(initBlock); initInst.ReplaceWith(initializerBlock);
for (int i = 0; i < initializerItemsCount; i++) for (int i = 0; i < initializerItemsCount; i++)
body.Instructions.RemoveAt(pos + 1); body.Instructions.RemoveAt(pos + 1);
ILInlining.InlineIfPossible(body, ref pos, context); ILInlining.InlineIfPossible(body, ref pos, context);
@ -151,7 +160,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case CallInstruction call: case CallInstruction call:
if (!(call is CallVirt || call is Call)) goto default; if (!(call is CallVirt || call is Call)) goto default;
method = call.Method; method = call.Method;
if (!IsMethodApplicable(method)) goto default; if (!IsMethodApplicable(method, call.Arguments)) goto default;
instruction = call.Arguments[0]; instruction = call.Arguments[0];
if (values == null) { if (values == null) {
values = new List<ILInstruction>(call.Arguments.Skip(1)); values = new List<ILInstruction>(call.Arguments.Skip(1));
@ -206,13 +215,42 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return (kind, path, values, target); return (kind, path, values, target);
} }
static bool IsMethodApplicable(IMethod method) static bool IsMethodApplicable(IMethod method, IList<ILInstruction> arguments)
{ {
if (method.IsStatic) if (!method.IsExtensionMethod && method.IsStatic)
return false; return false;
if (method.IsAccessor) if (method.IsAccessor)
return true; return true;
return "Add".Equals(method.Name, StringComparison.Ordinal); if (!"Add".Equals(method.Name, StringComparison.Ordinal) || arguments.Count == 0)
return false;
var targetType = GetReturnTypeFromInstruction(arguments[0]);
if (targetType == null)
return false;
return targetType.GetAllBaseTypes().Any(i => i.IsKnownType(KnownTypeCode.IEnumerable) || i.IsKnownType(KnownTypeCode.IEnumerableOfT));
}
static IType GetReturnTypeFromInstruction(ILInstruction instruction)
{
// this switch must match the one in GetAccessPath
switch (instruction) {
case CallInstruction call:
if (!(call is CallVirt || call is Call)) goto default;
return call.Method.ReturnType;
case LdObj ldobj:
if (ldobj.Target is LdFlda ldflda)
return ldflda.Field.ReturnType;
goto default;
case StObj stobj:
if (stobj.Target is LdFlda ldflda2)
return ldflda2.Field.ReturnType;
goto default;
case LdLoc ldloc:
return ldloc.Variable.Type;
case LdLoca ldloca:
return ldloca.Variable.Type;
default:
return null;
}
} }
public override bool Equals(object obj) public override bool Equals(object obj)

Loading…
Cancel
Save