diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index c12b5fbe7..49a2317ff 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -107,6 +107,7 @@ namespace ICSharpCode.Decompiler.CSharp // opportunities belong in this category. new ExpressionTransforms(), new TransformArrayInitializers(), + new TransformCollectionAndObjectInitializers(), new ILInlining() ) } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index d502ff0f3..fb796c0fa 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1407,6 +1407,9 @@ namespace ICSharpCode.Decompiler.CSharp switch (block.Type) { case BlockType.ArrayInitializer: return TranslateArrayInitializer(block); + case BlockType.CollectionInitializer: + case BlockType.ObjectInitializer: + return TranslateObjectAndCollectionInitializer(block); case BlockType.CompoundOperator: return TranslateCompoundOperator(block); default: @@ -1414,6 +1417,143 @@ namespace ICSharpCode.Decompiler.CSharp } } + TranslatedExpression TranslateObjectAndCollectionInitializer(Block block) + { + 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) + throw new ArgumentException("given Block is invalid!"); + Stack> elementsStack = new Stack>(); + var elements = new List(block.Instructions.Count); + elementsStack.Push(elements); + var initObjRR = new InitializedObjectResolveResult(newObjInst.Method.DeclaringType); + IMember currentMember = null; + foreach (var inst in block.Instructions) { + switch (inst) { + case CallVirt call: + if (call.Method.IsAccessor) { + if (elementsStack.Count > 1) + elementsStack.Pop(); + if (call.Arguments[0].MatchLdLoc(stloc.Variable)) { + currentMember = call.Method.AccessorOwner; + var propertyReference = new IdentifierExpression(currentMember.Name) + .WithRR(new MemberResolveResult(initObjRR, currentMember)); + var value = Translate(call.Arguments[1]).ConvertTo(currentMember.ReturnType, this); + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(call)); + } else { + IMember newMember; + switch (call.Arguments[0]) { + case CallVirt cv: + newMember = cv.Method.AccessorOwner; + break; + case LdObj lo: + if (lo.Target is LdFlda ldflda) { + newMember = ldflda.Field; + break; + } + throw new NotSupportedException(); + default: + throw new NotSupportedException(); + } + Expression propertyReference; + if (newMember != currentMember) { + currentMember = newMember; + if (elementsStack.Count > 1) { + propertyReference = new IdentifierExpression(currentMember.Name) + .WithRR(new MemberResolveResult(initObjRR, currentMember)); + + var prevItems = elementsStack.Pop(); + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems))); + } + elementsStack.Push(new List(call.Arguments.Count - 1)); + } + IMember member = call.Method.AccessorOwner; + propertyReference = new IdentifierExpression(member.Name) + .WithRR(new MemberResolveResult(initObjRR, member)); + var value = Translate(call.Arguments[1]).ConvertTo(member.ReturnType, this); + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(call)); + } + } else { + var target = call.Arguments[0]; + if (block.Type == BlockType.ObjectInitializer) { + IMember newMember; + switch (target) { + case CallVirt cv: + newMember = cv.Method.AccessorOwner; + break; + case LdObj lo: + if (lo.Target is LdFlda ldflda) { + newMember = ldflda.Field; + break; + } + throw new NotSupportedException(); + default: + throw new NotSupportedException(); + } + if (newMember != currentMember) { + currentMember = newMember; + if (elementsStack.Count > 1) { + var propertyReference = new IdentifierExpression(currentMember.Name) + .WithRR(new MemberResolveResult(initObjRR, currentMember)); + + var prevItems = elementsStack.Pop(); + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems))); + } + elementsStack.Push(new List(call.Arguments.Count - 1)); + } + } + var args = new List(call.Arguments.Count - 1); + foreach (var arg in call.Arguments.Skip(1)) { + var expectedType = call.Method.Parameters[arg.ChildIndex - 1].Type; + var a = Translate(arg).ConvertTo(expectedType, this); + args.Add(a); + } + elementsStack.Peek().Add(args.Count == 1 ? args[0] : new ArrayInitializerExpression(args)); + } + break; + case StObj stObj: + if (stObj.Value is Block b && IsInitializer(b)) { + if (stObj.Target is LdFlda ldflda) { + currentMember = ldflda.Field; + var propertyReference = new IdentifierExpression(currentMember.Name) + .WithRR(new MemberResolveResult(initObjRR, currentMember)); + Expression value; + switch (block.Type) { + case BlockType.ArrayInitializer: + value = TranslateArrayInitializer(b); + break; + case BlockType.CollectionInitializer: + case BlockType.ObjectInitializer: + value = TranslateObjectAndCollectionInitializer(b); + break; + default: + throw new NotSupportedException(); + } + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, value).WithILInstruction(stObj)); + break; + } + } + throw new NotSupportedException(); + } + } + if (elementsStack.Count > 1) { + var propertyReference = new IdentifierExpression(currentMember.Name) + .WithRR(new MemberResolveResult(initObjRR, currentMember)); + + var prevItems = elementsStack.Pop(); + elementsStack.Peek().Add(new AssignmentExpression(propertyReference, new ArrayInitializerExpression(prevItems))); + } + var expr = HandleCallInstruction(newObjInst); + var oce = (ObjectCreateExpression)expr.Expression; + oce.Initializer = new ArrayInitializerExpression(elements); + return expr.WithILInstruction(block); + } + + bool IsInitializer(Block b) + { + return b.Type == BlockType.ArrayInitializer || b.Type == BlockType.CollectionInitializer || b.Type == BlockType.ObjectInitializer; + } + readonly static ArraySpecifier[] NoSpecifiers = new ArraySpecifier[0]; TranslatedExpression TranslateArrayInitializer(Block block) diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 6d6db5677..ada11e794 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -285,6 +285,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index 26fa880cc..1750b77dd 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -239,6 +239,8 @@ namespace ICSharpCode.Decompiler.IL public enum BlockType { ControlFlow, ArrayInitializer, + CollectionInitializer, + ObjectInitializer, CompoundOperator } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs index f0f18e2c2..383b83d71 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformArrayInitializers.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { /// /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray. - /// For collection and object initializers see + /// For collection and object initializers see /// public class TransformArrayInitializers : IBlockTransform { diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs new file mode 100644 index 000000000..254901fb0 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray. + /// For collection and object initializers see + /// + public class TransformCollectionAndObjectInitializers : IBlockTransform + { + BlockTransformContext context; + + void IBlockTransform.Run(Block block, BlockTransformContext context) + { + this.context = context; + for (int i = block.Instructions.Count - 1; i >= 0; i--) + { + DoTransform(block, i); + } + } + + enum InitializerType + { + None, + Collection, + Object + } + + bool DoTransform(Block body, int pos) + { + 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) { + context.Step("CollectionOrObjectInitializer", inst); + int initializerItemsCount = 0; + var initializerType = InitializerType.None; + // Detect initializer type by scanning the following statements + // 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 named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer + while (pos + initializerItemsCount + 1 < body.Instructions.Count + && (MatchesCallVirt(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType) || MatchesStObj(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType))) + { + initializerItemsCount++; + } + if (initializerItemsCount == 0) + return false; + Block initBlock; + switch (initializerType) { + case InitializerType.Collection: + initBlock = new Block(BlockType.CollectionInitializer); + break; + case InitializerType.Object: + initBlock = new Block(BlockType.ObjectInitializer); + break; + default: return false; + } + var finalSlot = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type); + initBlock.FinalInstruction = new LdLoc(finalSlot); + 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 = new CallVirt(callVirt.Method); + var newTarget = callVirt.Arguments[0].Clone(); + foreach (var load in newTarget.Descendants.OfType()) + load.Variable = finalSlot; + newCallVirt.Arguments.Add(newTarget); + newCallVirt.Arguments.AddRange(callVirt.Arguments.Skip(1).Select(a => a.Clone())); + initBlock.Instructions.Add(newCallVirt); + break; + case StObj stObj: + var newStObj = (StObj)stObj.Clone(); + foreach (var load in newStObj.Target.Descendants.OfType()) + load.Variable = finalSlot; + initBlock.Instructions.Add(newStObj); + break; + } + + } + initInst.ReplaceWith(initBlock); + for (int i = 0; i < initializerItemsCount; i++) + body.Instructions.RemoveAt(pos + 1); + ILInlining.InlineIfPossible(body, ref pos, context); + } + return true; + } + + bool MatchesStObj(ILInstruction current, ILVariable v, ref InitializerType initializerType) + { + if (current is StObj so + && so.Target is LdFlda ldfa + && ldfa.Target.MatchLdLoc(v)) + return true; + return false; + } + + bool MatchesCallVirt(ILInstruction current, ILVariable v, ref InitializerType initializerType) + { + return current is CallVirt cv + && cv.Arguments.Count >= 2 + && !cv.Arguments.Skip(1).Any(a => a.Descendants.OfType().Any(b => b.MatchLdLoc(v))) + && IsMatchingTarget(cv.Arguments[0], v) + && IsMatchingMethod(cv, ref initializerType); + } + + 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 InitializerType type) + { + if (cv.Method.IsAccessor && cv.Method.AccessorOwner is IProperty p && p.Setter == cv.Method) { + type = InitializerType.Object; + return true; + } else if (cv.Method.Name.Equals("Add", StringComparison.Ordinal)) { + if (type == InitializerType.None) + type = InitializerType.Collection; + return true; + } + return false; + } + } +}