Browse Source

Merge pull request #927 from icsharpcode/initializer_pretty_fix

Initializer
pull/946/head
Daniel Grunwald 8 years ago committed by GitHub
parent
commit
f2be6c6ddb
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 484
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs
  4. 1522
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.il
  5. 1281
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.il
  6. 1314
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.roslyn.il
  7. 1533
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.roslyn.il
  8. 9
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  9. 13
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  10. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  11. 4
      ICSharpCode.Decompiler/IL/ILReader.cs
  12. 33
      ICSharpCode.Decompiler/IL/Instructions/DefaultValue.cs
  13. 27
      ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs
  14. 1
      ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
  15. 138
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
  16. 7
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -66,6 +66,7 @@
<Compile Include="TestCases\Correctness\Using.cs" /> <Compile Include="TestCases\Correctness\Using.cs" />
<Compile Include="TestCases\ILPretty\Issue379.cs" /> <Compile Include="TestCases\ILPretty\Issue379.cs" />
<Compile Include="TestCases\Pretty\FixProxyCalls.cs" /> <Compile Include="TestCases\Pretty\FixProxyCalls.cs" />
<Compile Include="TestCases\Pretty\InitializerTests.cs" />
<None Include="TestCases\ILPretty\Issue646.cs" /> <None Include="TestCases\ILPretty\Issue646.cs" />
<Compile Include="TestCases\Pretty\Async.cs" /> <Compile Include="TestCases\Pretty\Async.cs" />
<Compile Include="TestCases\Pretty\CheckedUnchecked.cs" /> <Compile Include="TestCases\Pretty\CheckedUnchecked.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -188,6 +188,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions, asmOptions: AssemblerOptions.UseOwnDisassembler); Run(cscOptions: cscOptions, asmOptions: AssemblerOptions.UseOwnDisassembler);
} }
[Test]
public void InitializerTests([ValueSource("defaultOptions")] CompilerOptions cscOptions)
{
Run(cscOptions: cscOptions);
}
[Test] [Test]
public void FixProxyCalls([Values(CompilerOptions.None, CompilerOptions.Optimize, CompilerOptions.UseRoslyn)] CompilerOptions cscOptions) public void FixProxyCalls([Values(CompilerOptions.None, CompilerOptions.Optimize, CompilerOptions.UseRoslyn)] CompilerOptions cscOptions)
{ {

484
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs

@ -0,0 +1,484 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class InitializerTests
{
#region Types and helpers
public class C
{
public int Z;
public S Y;
public List<S> L;
}
public struct S
{
public int A;
public int B;
public S(int a)
{
this.A = a;
this.B = 0;
}
}
private enum MyEnum
{
a = 0,
b = 1
}
private enum MyEnum2
{
c = 0,
d = 1
}
private class Data
{
public List<MyEnum2> FieldList = new List<MyEnum2>();
public MyEnum a {
get;
set;
}
public MyEnum b {
get;
set;
}
public List<MyEnum2> PropertyList {
get;
set;
}
public Data MoreData {
get;
set;
}
public StructData NestedStruct {
get;
set;
}
public Data this[int i] {
get {
return null;
}
set {
}
}
public Data this[int i, string j] {
get {
return null;
}
set {
}
}
public event EventHandler TestEvent;
}
private struct StructData
{
public int Field;
public int Property {
get;
set;
}
public Data MoreData {
get;
set;
}
public StructData(int initialValue)
{
this = default(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)
{
}
private static object Y()
{
return null;
}
public static void TestCall(int a, Thread thread)
{
}
public static C TestCall(int a, C c)
{
return c;
}
#endregion
public C Test1()
{
C c = new C();
c.L = new List<S>();
c.L.Add(new S(1));
return c;
}
public C Test1Alternative()
{
return InitializerTests.TestCall(1, new C {
L = new List<S> {
new S(1)
}
});
}
public C Test2()
{
C c = new C();
c.Z = 1;
c.Z = 2;
return c;
}
public C Test3()
{
C c = new C();
c.Y = new S(1);
c.Y.A = 2;
return c;
}
public C Test3b()
{
return InitializerTests.TestCall(0, new C {
Z = 1,
Y = {
A = 2
}
});
}
public C Test4()
{
C c = new C();
c.Y.A = 1;
c.Z = 2;
c.Y.B = 3;
return c;
}
public static void CollectionInitializerList()
{
InitializerTests.X(InitializerTests.Y(), new List<int> {
1,
2,
3
});
}
public static object RecursiveCollectionInitializer()
{
List<object> list = new List<object>();
list.Add(list);
return list;
}
public static void CollectionInitializerDictionary()
{
InitializerTests.X(InitializerTests.Y(), new Dictionary<string, int> {
{
"First",
1
},
{
"Second",
2
},
{
"Third",
3
}
});
}
public static void CollectionInitializerDictionaryWithEnumTypes()
{
InitializerTests.X(InitializerTests.Y(), new Dictionary<MyEnum, MyEnum2> {
{
MyEnum.a,
MyEnum2.c
},
{
MyEnum.b,
MyEnum2.d
}
});
}
public static void NotACollectionInitializer()
{
List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
InitializerTests.X(InitializerTests.Y(), list);
}
public static void ObjectInitializer()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.a
});
}
public static void NotAnObjectInitializer()
{
Data data = new Data();
data.a = MyEnum.a;
InitializerTests.X(InitializerTests.Y(), data);
}
public static void NotAnObjectInitializerWithEvent()
{
Data data = new Data();
data.TestEvent += delegate(object sender, EventArgs e) {
Console.WriteLine();
};
InitializerTests.X(InitializerTests.Y(), data);
}
public static void ObjectInitializerAssignCollectionToField()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.a,
FieldList = new List<MyEnum2> {
MyEnum2.c,
MyEnum2.d
}
});
}
public static void ObjectInitializerAddToCollectionInField()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.a,
FieldList = {
MyEnum2.c,
MyEnum2.d
}
});
}
public static void ObjectInitializerAssignCollectionToProperty()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.a,
PropertyList = new List<MyEnum2> {
MyEnum2.c,
MyEnum2.d
}
});
}
public static void ObjectInitializerAddToCollectionInProperty()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.a,
PropertyList = {
MyEnum2.c,
MyEnum2.d
}
});
}
public static void ObjectInitializerWithInitializationOfNestedObjects()
{
InitializerTests.X(InitializerTests.Y(), new Data {
MoreData = {
a = MyEnum.a,
MoreData = {
a = MyEnum.b
}
}
});
}
private static int GetInt()
{
return 1;
}
private static string GetString()
{
return "Test";
}
#if !LEGACY_CSC
public static void SimpleDictInitializer()
{
InitializerTests.X(InitializerTests.Y(), new Data {
MoreData = {
a = MyEnum.a,
[2] = null
}
});
}
public static void MixedObjectAndDictInitializer()
{
InitializerTests.X(InitializerTests.Y(), new Data {
MoreData = {
a = MyEnum.a,
[InitializerTests.GetInt()] = {
a = MyEnum.b,
FieldList = {
MyEnum2.c
},
[InitializerTests.GetInt(), InitializerTests.GetString()] = new Data(),
[2] = null
}
}
});
}
#endif
public static void ObjectInitializerWithInitializationOfDeeplyNestedObjects()
{
InitializerTests.X(InitializerTests.Y(), new Data {
a = MyEnum.b,
MoreData = {
a = MyEnum.a,
MoreData = {
MoreData = {
MoreData = {
MoreData = {
MoreData = {
MoreData = {
a = MyEnum.b
}
}
}
}
}
}
}
});
}
public static void CollectionInitializerInsideObjectInitializers()
{
InitializerTests.X(InitializerTests.Y(), new Data {
MoreData = new Data {
a = MyEnum.a,
b = MyEnum.b,
PropertyList = {
MyEnum2.c
}
}
});
}
public static void NotAStructInitializer_DefaultConstructor()
{
StructData structData = default(StructData);
structData.Field = 1;
structData.Property = 2;
InitializerTests.X(InitializerTests.Y(), structData);
}
public static void StructInitializer_DefaultConstructor()
{
InitializerTests.X(InitializerTests.Y(), new StructData {
Field = 1,
Property = 2
});
}
public static void NotAStructInitializer_ExplicitConstructor()
{
StructData structData = new StructData(0);
structData.Field = 1;
structData.Property = 2;
InitializerTests.X(InitializerTests.Y(), structData);
}
public static void StructInitializer_ExplicitConstructor()
{
InitializerTests.X(InitializerTests.Y(), new StructData(0) {
Field = 1,
Property = 2
});
}
public static void StructInitializerWithInitializationOfNestedObjects()
{
InitializerTests.X(InitializerTests.Y(), new StructData {
MoreData = {
a = MyEnum.a,
FieldList = {
MyEnum2.c,
MyEnum2.d
}
}
});
}
public static void StructInitializerWithinObjectInitializer()
{
InitializerTests.X(InitializerTests.Y(), new Data {
NestedStruct = new StructData(2) {
Field = 1,
Property = 2
}
});
}
public static void Bug270_NestedInitialisers()
{
NumberFormatInfo[] source = null;
InitializerTests.TestCall(0, new Thread(InitializerTests.Bug270_NestedInitialisers) {
Priority = ThreadPriority.BelowNormal,
CurrentCulture = new CultureInfo(0) {
DateTimeFormat = new DateTimeFormatInfo {
ShortDatePattern = "ddmmyy"
},
NumberFormat = (from format in source
where format.CurrencySymbol == "$"
select format).First()
}
});
}
}
}

1522
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.il

File diff suppressed because it is too large Load Diff

1281
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.il

File diff suppressed because it is too large Load Diff

1314
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.opt.roslyn.il

File diff suppressed because it is too large Load Diff

1533
ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.roslyn.il

File diff suppressed because it is too large Load Diff

9
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -1440,11 +1440,10 @@ namespace ICSharpCode.Decompiler.CSharp
if (currentPath == null) { if (currentPath == null) {
currentPath = info.Path; currentPath = info.Path;
} else { } else {
int firstDifferenceIndex = Math.Min(currentPath.Count, info.Path.Count); int minLen = Math.Min(currentPath.Count, info.Path.Count);
int index = 0; int firstDifferenceIndex = 0;
while (index < firstDifferenceIndex && info.Path[index] == currentPath[index]) while (firstDifferenceIndex < minLen && info.Path[firstDifferenceIndex] == currentPath[firstDifferenceIndex])
index++; firstDifferenceIndex++;
firstDifferenceIndex = index;
while (elementsStack.Count - 1 > firstDifferenceIndex) { while (elementsStack.Count - 1 > firstDifferenceIndex) {
var methodElement = currentPath[elementsStack.Count - 1]; var methodElement = currentPath[elementsStack.Count - 1];
var pathElement = currentPath[elementsStack.Count - 2]; var pathElement = currentPath[elementsStack.Count - 2];

13
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -90,6 +90,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
Space(policy.SpaceBeforeBracketComma); Space(policy.SpaceBeforeBracketComma);
// TODO: Comma policy has changed. // TODO: Comma policy has changed.
writer.WriteToken(Roles.Comma, ","); writer.WriteToken(Roles.Comma, ",");
isAfterSpace = false;
Space(!noSpaceAfterComma && policy.SpaceAfterBracketComma); Space(!noSpaceAfterComma && policy.SpaceAfterBracketComma);
// TODO: Comma policy has changed. // TODO: Comma policy has changed.
} }
@ -194,6 +195,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
#region Write tokens #region Write tokens
protected bool isAtStartOfLine = true; protected bool isAtStartOfLine = true;
protected bool isAfterSpace;
/// <summary> /// <summary>
/// Writes a keyword, and all specials up to /// Writes a keyword, and all specials up to
@ -207,18 +209,21 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
writer.WriteKeyword(tokenRole, token); writer.WriteKeyword(tokenRole, token);
isAtStartOfLine = false; isAtStartOfLine = false;
isAfterSpace = false;
} }
protected virtual void WriteIdentifier(Identifier identifier) protected virtual void WriteIdentifier(Identifier identifier)
{ {
writer.WriteIdentifier(identifier); writer.WriteIdentifier(identifier);
isAtStartOfLine = false; isAtStartOfLine = false;
isAfterSpace = false;
} }
protected virtual void WriteIdentifier(string identifier) protected virtual void WriteIdentifier(string identifier)
{ {
AstType.Create(identifier).AcceptVisitor(this); AstType.Create(identifier).AcceptVisitor(this);
isAtStartOfLine = false; isAtStartOfLine = false;
isAfterSpace = false;
} }
protected virtual void WriteToken(TokenRole tokenRole) protected virtual void WriteToken(TokenRole tokenRole)
@ -230,6 +235,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
writer.WriteToken(tokenRole, token); writer.WriteToken(tokenRole, token);
isAtStartOfLine = false; isAtStartOfLine = false;
isAfterSpace = false;
} }
protected virtual void LPar() protected virtual void LPar()
@ -260,8 +266,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary> /// </summary>
protected virtual void Space(bool addSpace = true) protected virtual void Space(bool addSpace = true)
{ {
if (addSpace) { if (addSpace && !isAfterSpace) {
writer.Space(); writer.Space();
isAfterSpace = true;
} }
} }
@ -269,6 +276,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
writer.NewLine(); writer.NewLine();
isAtStartOfLine = true; isAtStartOfLine = true;
isAfterSpace = false;
} }
protected virtual void OpenBrace(BraceStyle style) protected virtual void OpenBrace(BraceStyle style)
@ -278,7 +286,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case BraceStyle.EndOfLine: case BraceStyle.EndOfLine:
case BraceStyle.BannerStyle: case BraceStyle.BannerStyle:
if (!isAtStartOfLine) if (!isAtStartOfLine)
writer.Space(); Space();
writer.WriteToken(Roles.LBrace, "{"); writer.WriteToken(Roles.LBrace, "{");
break; break;
case BraceStyle.EndOfLineWithoutSpace: case BraceStyle.EndOfLineWithoutSpace:
@ -937,6 +945,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
StartNode(primitiveExpression); StartNode(primitiveExpression);
writer.WritePrimitiveValue(primitiveExpression.Value, primitiveExpression.UnsafeLiteralValue); writer.WritePrimitiveValue(primitiveExpression.Value, primitiveExpression.UnsafeLiteralValue);
isAfterSpace = false;
EndNode(primitiveExpression); EndNode(primitiveExpression);
} }
#endregion #endregion

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -276,6 +276,7 @@
<Compile Include="IL\ILAstWritingOptions.cs" /> <Compile Include="IL\ILAstWritingOptions.cs" />
<Compile Include="IL\SequencePoint.cs" /> <Compile Include="IL\SequencePoint.cs" />
<Compile Include="IL\Instructions\CallIndirect.cs" /> <Compile Include="IL\Instructions\CallIndirect.cs" />
<Compile Include="IL\Instructions\DefaultValue.cs" />
<Compile Include="IL\Transforms\EarlyExpressionTransforms.cs" /> <Compile Include="IL\Transforms\EarlyExpressionTransforms.cs" />
<Compile Include="IL\Transforms\ProxyCallReplacer.cs" /> <Compile Include="IL\Transforms\ProxyCallReplacer.cs" />
<Compile Include="IL\Instructions\StringToInt.cs" /> <Compile Include="IL\Instructions\StringToInt.cs" />

4
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1011,7 +1011,9 @@ namespace ICSharpCode.Decompiler.IL
ILInstruction InitObj(ILInstruction target, IType type) ILInstruction InitObj(ILInstruction target, IType type)
{ {
return new StObj(target, new DefaultValue(type), type); var value = new DefaultValue(type);
value.ILStackWasEmpty = currentStack.IsEmpty;
return new StObj(target, value, type);
} }
private ILInstruction DecodeConstrainedCall() private ILInstruction DecodeConstrainedCall()

33
ICSharpCode.Decompiler/IL/Instructions/DefaultValue.cs

@ -0,0 +1,33 @@
// Copyright (c) 2017 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
namespace ICSharpCode.Decompiler.IL
{
partial class DefaultValue
{
/// <summary>
/// Gets whether the IL stack was empty at the point of this instruction.
/// (not counting the argument of the instruction itself)
/// </summary>
public bool ILStackWasEmpty;
}
}

27
ICSharpCode.Decompiler/IL/Transforms/AssignVariableNames.cs

@ -196,6 +196,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
proposedName = proposedNameForLoads[0]; proposedName = proposedNameForLoads[0];
} }
} }
if (string.IsNullOrEmpty(proposedName) && variable.Kind == VariableKind.StackSlot) {
var proposedNameForStoresFromNewObj = variable.StoreInstructions.OfType<StLoc>()
.Select(expr => GetNameByType(GuessType(variable.Type, expr.Value, context)))
.Except(currentFieldNames).ToList();
if (proposedNameForStoresFromNewObj.Count == 1) {
proposedName = proposedNameForStoresFromNewObj[0];
}
}
if (string.IsNullOrEmpty(proposedName)) { if (string.IsNullOrEmpty(proposedName)) {
proposedName = GetNameByType(variable.Type); proposedName = GetNameByType(variable.Type);
} }
@ -349,6 +357,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return char.ToLower(name[0]) + name.Substring(1); return char.ToLower(name[0]) + name.Substring(1);
} }
internal static IType GuessType(IType variableType, ILInstruction inst, ILTransformContext context)
{
if (!variableType.IsKnownType(KnownTypeCode.Object))
return variableType;
switch (inst) {
case NewObj newObj:
return newObj.Method.DeclaringType;
case Call call:
return call.Method.ReturnType;
case CallVirt callVirt:
return callVirt.Method.ReturnType;
case CallIndirect calli:
return calli.ReturnType;
default:
return context.TypeSystem.Compilation.FindType(inst.ResultType.ToKnownTypeCode());
}
}
internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext, ILVariable existingVariable = null) internal static string GenerateForeachVariableName(ILFunction function, ILInstruction valueContext, ILVariable existingVariable = null)
{ {
if (function == null) if (function == null)

1
ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs

@ -104,6 +104,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var newObj = new NewObj(inst.Method); var newObj = new NewObj(inst.Method);
newObj.ILRange = inst.ILRange; newObj.ILRange = inst.ILRange;
newObj.Arguments.AddRange(inst.Arguments.Skip(1)); newObj.Arguments.AddRange(inst.Arguments.Skip(1));
newObj.ILStackWasEmpty = inst.ILStackWasEmpty;
var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType); var expr = new StObj(inst.Arguments[0], newObj, inst.Method.DeclaringType);
inst.ReplaceWith(expr); inst.ReplaceWith(expr);
return expr; return expr;

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

@ -20,6 +20,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
@ -45,7 +46,6 @@ 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) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)) { if (inst.MatchStLoc(out var v, out var initInst) && (v.Kind == VariableKind.Local || v.Kind == VariableKind.StackSlot)) {
Block initializerBlock = null;
IType instType; IType instType;
switch (initInst) { switch (initInst) {
case NewObj newObjInst: case NewObj newObjInst:
@ -65,49 +65,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms
instType = newObjInst.Method.DeclaringType; instType = newObjInst.Method.DeclaringType;
break; break;
case DefaultValue defaultVal: case DefaultValue defaultVal:
if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor) {
// on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots),
// unless we are in a constructor (where inlining object initializers might be critical
// for the base ctor call)
return false;
}
instType = defaultVal.Type; instType = defaultVal.Type;
break; break;
case Block existingInitializer:
if (existingInitializer.Type == BlockType.CollectionInitializer || existingInitializer.Type == BlockType.ObjectInitializer) {
initializerBlock = existingInitializer;
var value = ((StLoc)initializerBlock.Instructions[0]).Value;
if (value is NewObj no)
instType = no.Method.DeclaringType;
else
instType = ((DefaultValue)value).Type;
break;
}
return false;
default: default:
return false; return false;
} }
int initializerItemsCount = 0; int initializerItemsCount = 0;
var blockType = initializerBlock?.Type ?? BlockType.CollectionInitializer; var blockType = BlockType.CollectionInitializer;
var possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>(); possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>();
currentPath = new List<AccessPathElement>();
isCollection = false;
pathStack = new Stack<HashSet<AccessPathElement>>();
pathStack.Push(new HashSet<AccessPathElement>());
// 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
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary 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 while (pos + initializerItemsCount + 1 < body.Instructions.Count
&& IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType, possibleIndexVariables)) { && IsPartOfInitializer(body.Instructions, pos + initializerItemsCount + 1, v, instType, ref blockType)) {
initializerItemsCount++; initializerItemsCount++;
} }
// Do not convert the statements into an initializer if there's an incompatible usage of the initializer variable
// directly after the possible initializer.
if (IsMethodCallOnVariable(body.Instructions[pos + initializerItemsCount + 1], v))
return false;
// Calculate the correct number of statements inside the initializer:
// All index variables that were used in the initializer have Index set to -1.
// We fetch the first unused variable from the list and remove all instructions after its
// first usage (i.e. the init store) from the initializer.
var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index); var index = possibleIndexVariables.Where(info => info.Value.Index > -1).Min(info => (int?)info.Value.Index);
if (index != null) { if (index != null) {
initializerItemsCount = index.Value - pos - 1; initializerItemsCount = index.Value - pos - 1;
} }
// The initializer would be empty, there's nothing to do here.
if (initializerItemsCount <= 0) if (initializerItemsCount <= 0)
return false; return false;
context.Step("CollectionOrObjectInitializer", inst); context.Step("CollectionOrObjectInitializer", inst);
ILVariable finalSlot; // Create a new block and final slot (initializer target variable)
if (initializerBlock == null) { var initializerBlock = new Block(blockType);
initializerBlock = new Block(blockType); ILVariable finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type);
finalSlot = context.Function.RegisterVariable(VariableKind.InitializerTarget, v.Type); initializerBlock.FinalInstruction = new LdLoc(finalSlot);
initializerBlock.FinalInstruction = new LdLoc(finalSlot); initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone()));
initializerBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); // Move all instructions to the initializer block.
} 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:
@ -139,20 +145,67 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType, Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables) bool IsMethodCallOnVariable(ILInstruction inst, ILVariable variable)
{
if (inst.MatchLdLocRef(variable))
return true;
if (inst is CallInstruction call && call.Arguments.Count > 0 && !call.Method.IsStatic)
return IsMethodCallOnVariable(call.Arguments[0], variable);
if (inst.MatchLdFld(out var target, out _) || inst.MatchStFld(out target, out _, out _) || inst.MatchLdFlda(out target, out _))
return IsMethodCallOnVariable(target, variable);
return false;
}
Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables;
List<AccessPathElement> currentPath;
bool isCollection;
Stack<HashSet<AccessPathElement>> pathStack;
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockType blockType)
{ {
// Include any stores to local variables that are single-assigned and do not reference the initializer-variable
// in the list of possible index variables.
// Index variables are used to implement dictionary initializers.
if (instructions[pos] is StLoc stloc && stloc.Variable.Kind == VariableKind.Local && stloc.Variable.IsSingleDefinition) { if (instructions[pos] is StLoc stloc && stloc.Variable.Kind == VariableKind.Local && stloc.Variable.IsSingleDefinition) {
if (stloc.Value.Descendants.OfType<IInstructionWithVariableOperand>().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) if (stloc.Value.Descendants.OfType<IInstructionWithVariableOperand>().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca)))
return false; return false;
possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value)); possibleIndexVariables.Add(stloc.Variable, (stloc.ChildIndex, stloc.Value));
return true; return true;
} }
(var kind, var path, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, possibleIndexVariables); (var kind, var newPath, var values, var targetVariable) = AccessPathElement.GetAccessPath(instructions[pos], rootType, possibleIndexVariables);
if (kind == AccessPathKind.Invalid || target != targetVariable)
return false;
// Treat last element separately:
// Can either be an Add method call or property setter.
var lastElement = newPath.Last();
newPath.RemoveLast();
// Compare new path with current path:
int minLen = Math.Min(currentPath.Count, newPath.Count);
int firstDifferenceIndex = 0;
while (firstDifferenceIndex < minLen && newPath[firstDifferenceIndex] == currentPath[firstDifferenceIndex])
firstDifferenceIndex++;
while (currentPath.Count > firstDifferenceIndex) {
isCollection = false;
currentPath.RemoveAt(currentPath.Count - 1);
pathStack.Pop();
}
while (currentPath.Count < newPath.Count) {
AccessPathElement newElement = newPath[currentPath.Count];
currentPath.Add(newElement);
if (isCollection || !pathStack.Peek().Add(newElement))
return false;
pathStack.Push(new HashSet<AccessPathElement>());
}
switch (kind) { switch (kind) {
case AccessPathKind.Adder: case AccessPathKind.Adder:
return target == targetVariable; isCollection = true;
if (pathStack.Peek().Count != 0)
return false;
return true;
case AccessPathKind.Setter: case AccessPathKind.Setter:
if (values.Count == 1 && target == targetVariable) { if (isCollection || !pathStack.Peek().Add(lastElement))
return false;
if (values.Count == 1) {
blockType = BlockType.ObjectInitializer; blockType = BlockType.ObjectInitializer;
return true; return true;
} }
@ -183,7 +236,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public override string ToString() => $"[{Member}, {Indices}]"; public override string ToString() => $"[{Member}, {Indices}]";
public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath(ILInstruction instruction, IType rootType, Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = null) public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath(
ILInstruction instruction, IType rootType, Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = null)
{ {
List<AccessPathElement> path = new List<AccessPathElement>(); List<AccessPathElement> path = new List<AccessPathElement>();
ILVariable target = null; ILVariable target = null;
@ -203,6 +257,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
var isGetter = property?.Getter == method; var isGetter = property?.Getter == method;
var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray(); var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray();
if (possibleIndexVariables != null) { if (possibleIndexVariables != null) {
// Mark all index variables as used
foreach (var index in indices.OfType<IInstructionWithVariableOperand>()) { foreach (var index in indices.OfType<IInstructionWithVariableOperand>()) {
if (possibleIndexVariables.TryGetValue(index.Variable, out var info)) if (possibleIndexVariables.TryGetValue(index.Variable, out var info))
possibleIndexVariables[index.Variable] = (-1, info.Value); possibleIndexVariables[index.Variable] = (-1, info.Value);
@ -224,17 +279,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
break; break;
case LdObj ldobj: case LdObj ldobj: {
if (ldobj.Target is LdFlda ldflda) { if (ldobj.Target is LdFlda ldflda) {
path.Insert(0, new AccessPathElement(ldflda.Field)); path.Insert(0, new AccessPathElement(ldflda.Field));
instruction = ldflda.Target; instruction = ldflda.Target;
break; break;
} }
goto default; goto default;
case StObj stobj: }
if (stobj.Target is LdFlda ldflda2) { case StObj stobj: {
path.Insert(0, new AccessPathElement(ldflda2.Field)); if (stobj.Target is LdFlda ldflda) {
instruction = ldflda2.Target; path.Insert(0, new AccessPathElement(ldflda.Field));
instruction = ldflda.Target;
if (values == null) { if (values == null) {
values = new List<ILInstruction>(new[] { stobj.Value }); values = new List<ILInstruction>(new[] { stobj.Value });
kind = AccessPathKind.Setter; kind = AccessPathKind.Setter;
@ -242,6 +298,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
break; break;
} }
goto default; goto default;
}
case LdLoc ldloc: case LdLoc ldloc:
target = ldloc.Variable; target = ldloc.Variable;
instruction = null; instruction = null;
@ -250,6 +307,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
target = ldloca.Variable; target = ldloca.Variable;
instruction = null; instruction = null;
break; break;
case LdFlda ldflda:
path.Insert(0, new AccessPathElement(ldflda.Field));
instruction = ldflda.Target;
break;
default: default:
kind = AccessPathKind.Invalid; kind = AccessPathKind.Invalid;
instruction = null; instruction = null;
@ -313,7 +374,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public bool Equals(AccessPathElement other) public bool Equals(AccessPathElement other)
{ {
return other.Member.Equals(this.Member) && (other.Indices == this.Indices || other.Indices.SequenceEqual(this.Indices, ILInstructionMatchComparer.Instance)); 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) public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs)
@ -337,12 +399,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
if (x == null || y == null) if (x == null || y == null)
return false; return false;
return x.Match(y).Success; return SemanticHelper.IsPure(x.Flags)
&& SemanticHelper.IsPure(y.Flags)
&& x.Match(y).Success;
} }
public int GetHashCode(ILInstruction obj) public int GetHashCode(ILInstruction obj)
{ {
return obj.GetHashCode(); throw new NotSupportedException();
} }
} }
} }

7
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -190,5 +190,12 @@ namespace ICSharpCode.Decompiler.Util
return maxElement; return maxElement;
} }
} }
public static void RemoveLast<T>(this IList<T> list)
{
if (list == null)
throw new ArgumentNullException(nameof(list));
list.RemoveAt(list.Count - 1);
}
} }
} }

Loading…
Cancel
Save