From 7ea511ef325e61652f366ef0f64d2b7a97355f48 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 3 Sep 2017 01:46:56 +0200 Subject: [PATCH] Add support for C# 6 dictionary initializer. --- .../TestCases/Correctness/InitializerTests.cs | 30 ++++++- .../CSharp/ExpressionBuilder.cs | 25 ++++-- ...ransformCollectionAndObjectInitializers.cs | 88 ++++++++++++++----- 3 files changed, 111 insertions(+), 32 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs index 4d0d980ce..29824ad1b 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/InitializerTests.cs @@ -84,6 +84,13 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } set { } } + + public InitializerTests.Data this[int i, string j] { + get { + return null; + } + set { } + } } private struct StructData @@ -531,12 +538,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness }); } -#if false static int GetInt() { return 1; } + static string GetString() + { + return "Test"; + } + +#if !LEGACY_CSC + public static void SimpleDictInitializer() + { + InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data { + MoreData = + { + a = InitializerTests.MyEnum.a, + [2] = (Data)null + } + }); + } + public static void MixedObjectAndDictInitializer() { InitializerTests.X(InitializerTests.Y(), new InitializerTests.Data { @@ -546,8 +569,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness [GetInt()] = { a = InitializerTests.MyEnum.b, FieldList = { MyEnum2.c }, - [2] = null - }, + [GetInt(), GetString()] = new Data(), + [2] = (Data)null + } } }); } diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index e960aaac9..5108ba076 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -1448,7 +1448,12 @@ namespace ICSharpCode.Decompiler.CSharp var elements = new List(block.Instructions.Count); elementsStack.Push(elements); List currentPath = null; + var indexVariables = new Dictionary(); foreach (var inst in block.Instructions.Skip(1)) { + if (inst is StLoc indexStore) { + indexVariables.Add(indexStore.Variable, indexStore.Value); + continue; + } var info = AccessPathElement.GetAccessPath(inst, initObjRR.Type); if (info.Kind == AccessPathKind.Invalid) continue; if (currentPath == null) { @@ -1463,7 +1468,7 @@ namespace ICSharpCode.Decompiler.CSharp var methodElement = currentPath[elementsStack.Count - 1]; var pathElement = currentPath[elementsStack.Count - 2]; var values = elementsStack.Pop(); - elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement.Member, values)); + elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement, values, indexVariables)); } currentPath = info.Path; } @@ -1476,9 +1481,15 @@ namespace ICSharpCode.Decompiler.CSharp elementsStack.Peek().Add(MakeInitializerElements(info.Values, ((IMethod)lastElement.Member).Parameters)); break; case AccessPathKind.Setter: - var target = new IdentifierExpression(lastElement.Member.Name) - .WithILInstruction(inst).WithRR(memberRR); - elementsStack.Peek().Add(Assignment(target, Translate(info.Values.Single()))); + if (lastElement.Indices?.Length > 0) { + var indexer = new IndexerExpression(null, lastElement.Indices.SelectArray(i => Translate(i is LdLoc ld ? indexVariables[ld.Variable] : i).Expression)) + .WithILInstruction(inst).WithRR(memberRR); + elementsStack.Peek().Add(Assignment(indexer, Translate(info.Values.Single()))); + } else { + var target = new IdentifierExpression(lastElement.Member.Name) + .WithILInstruction(inst).WithRR(memberRR); + elementsStack.Peek().Add(Assignment(target, Translate(info.Values.Single()))); + } break; } } @@ -1486,16 +1497,16 @@ namespace ICSharpCode.Decompiler.CSharp var methodElement = currentPath[elementsStack.Count - 1]; var pathElement = currentPath[elementsStack.Count - 2]; var values = elementsStack.Pop(); - elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement.Member, values)); + elementsStack.Peek().Add(MakeInitializerAssignment(methodElement.Member, pathElement, values, indexVariables)); } var oce = (ObjectCreateExpression)expr.Expression; oce.Initializer = new ArrayInitializerExpression(elements); return expr.WithILInstruction(block); } - Expression MakeInitializerAssignment(IMember method, IMember member, List values) + Expression MakeInitializerAssignment(IMember method, AccessPathElement member, List values, Dictionary indexVariables) { - var target = new IdentifierExpression(member.Name); + var target = member.Indices?.Length > 0 ? (Expression)new IndexerExpression(null, member.Indices.SelectArray(i => Translate(i is LdLoc ld ? indexVariables[ld.Variable] : i).Expression)) : new IdentifierExpression(member.Member.Name); Expression value; if (values.Count == 1 && !(values[0] is AssignmentExpression) && !(method.SymbolKind == SymbolKind.Method && method.Name == "Add")) value = values[0]; diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index ee4486396..550ec0c1d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -83,14 +83,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms context.Step("CollectionOrObjectInitializer", inst); int initializerItemsCount = 0; var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer; + var possibleIndexVariables = new Dictionary(); // 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 - && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType)) + && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType, possibleIndexVariables)) { initializerItemsCount++; - if (initializerItemsCount == 0) + } + var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index); + if (index != null) { + initializerItemsCount = index.Value - pos - 1; + } + if (initializerItemsCount <= 0) return false; ILVariable finalSlot; if (initializerBlock == null) { @@ -108,17 +114,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms var newCall = (CallInstruction)call.Clone(); var newTarget = newCall.Arguments[0]; foreach (var load in newTarget.Descendants.OfType()) - if (load is LdLoc || load is LdLoca) + if ((load is LdLoc || load is LdLoca) && load.Variable == v) load.Variable = finalSlot; initializerBlock.Instructions.Add(newCall); break; case StObj stObj: var newStObj = (StObj)stObj.Clone(); foreach (var load in newStObj.Target.Descendants.OfType()) - if (load is LdLoc || load is LdLoca) + if ((load is LdLoc || load is LdLoca) && load.Variable == v) load.Variable = finalSlot; initializerBlock.Instructions.Add(newStObj); break; + case StLoc stLoc: + var newStLoc = (StLoc)stLoc.Clone(); + initializerBlock.Instructions.Add(newStLoc); + break; } } initInst.ReplaceWith(initializerBlock); @@ -129,9 +139,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - bool IsPartOfInitializer(InstructionCollection instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType) + bool IsPartOfInitializer(InstructionCollection instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType, Dictionary possibleIndexVariables) { - (var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType); + if (instructions[pos] is StLoc stloc && stloc.Variable.Kind == VariableKind.Local && stloc.Variable.IsSingleDefinition) { + if (stloc.Value.Descendants.OfType().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) + return false; + possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value)); + return true; + } + (var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, possibleIndexVariables); switch (kind) { case AccessPathKind.Adder: return target == targetVariable; @@ -156,24 +172,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms public struct AccessPathElement : IEquatable { - public AccessPathElement(IMember member, ILVariable index = null) + public AccessPathElement(IMember member, ILInstruction[] indices = null) { this.Member = member; - this.Index = index; + this.Indices = indices; } public readonly IMember Member; - public readonly ILVariable Index; + public readonly ILInstruction[] Indices; - public override string ToString() => $"[{Member}, {Index}]"; + public override string ToString() => $"[{Member}, {Indices}]"; - public static (AccessPathKind Kind, List Path, List Values, ILVariable Target) GetAccessPath(ILInstruction instruction, IType rootType) + public static (AccessPathKind Kind, List Path, List Values, ILVariable Target) GetAccessPath(ILInstruction instruction, IType rootType, Dictionary possibleIndexVariables = null) { List path = new List(); ILVariable target = null; AccessPathKind kind = AccessPathKind.Invalid; List values = null; IMethod method; + var inst = instruction; while (instruction != null) { switch (instruction) { case CallInstruction call: @@ -181,21 +198,31 @@ namespace ICSharpCode.Decompiler.IL.Transforms method = call.Method; if (!IsMethodApplicable(method, call.Arguments, rootType)) goto default; instruction = call.Arguments[0]; + if (method.IsAccessor) { + var property = (IProperty)method.AccessorOwner; + var isGetter = property.Getter == method; + var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray(); + if (possibleIndexVariables != null) { + foreach (var index in indices.OfType()) { + if (possibleIndexVariables.TryGetValue(index.Variable, out var info)) + possibleIndexVariables[index.Variable] = (-1, info.Value); + } + } + path.Insert(0, new AccessPathElement(method.AccessorOwner, indices)); + } else { + path.Insert(0, new AccessPathElement(method)); + } if (values == null) { - values = new List(call.Arguments.Skip(1)); - if (values.Count == 0) - goto default; if (method.IsAccessor) { kind = AccessPathKind.Setter; + values = new List { call.Arguments.Last() }; } else { kind = AccessPathKind.Adder; + values = new List(call.Arguments.Skip(1)); + if (values.Count == 0) + goto default; } } - if (method.IsAccessor) { - path.Insert(0, new AccessPathElement(method.AccessorOwner)); - } else { - path.Insert(0, new AccessPathElement(method)); - } break; case LdObj ldobj: if (ldobj.Target is LdFlda ldflda) { @@ -280,15 +307,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms unchecked { if (Member != null) hashCode += 1000000007 * Member.GetHashCode(); - if (Index != null) - hashCode += 1000000009 * Index.GetHashCode(); } return hashCode; } public bool Equals(AccessPathElement other) { - return other.Member.Equals(this.Member) && other.Index == this.Index; + return other.Member.Equals(this.Member) && (other.Indices == this.Indices || other.Indices.SequenceEqual(this.Indices, ILInstructionMatchComparer.Instance)); } public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs) @@ -301,4 +326,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms return !(lhs == rhs); } } + + class ILInstructionMatchComparer : IEqualityComparer + { + public static readonly ILInstructionMatchComparer Instance = new ILInstructionMatchComparer(); + + public bool Equals(ILInstruction x, ILInstruction y) + { + if (x == y) + return true; + if (x == null || y == null) + return false; + return x.Match(y).Success; + } + + public int GetHashCode(ILInstruction obj) + { + return obj.GetHashCode(); + } + } }