diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index f14b1077e..546cd2ea7 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -92,6 +92,8 @@ + + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs index 013111e78..1f73795e2 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs @@ -719,5 +719,91 @@ namespace LocalFunctions } } } + + public int Issue1798_NestedCapture2() + { + return Method(); +#if CS80 + static int Method() +#else + int Method() +#endif + { + int t0 = 0; + return ZZZ_0(); + int ZZZ_0() + { + t0 = 0; + int t2 = t0; + return ((Func)delegate { + t0 = 0; + t2 = 0; + return ZZZ_1(); + })(); + } + int ZZZ_1() + { + t0 = 0; + int t1 = t0; +#if !OPT + Func func = delegate { +#else + return ((Func)delegate { +#endif + t0 = 0; + t1 = 0; + return 0; +#if !OPT + }; + return func(); +#else + })(); +#endif + } + } + } + + public int Issue1798_NestedCapture2b() + { + return Method(); +#if CS80 + static int Method() +#else + int Method() +#endif + { + int t0 = 0; + return ZZZ_0() + ZZZ_1(); + int ZZZ_0() + { + t0 = 0; + int t2 = t0; + return ((Func)delegate { + t0 = 0; + t2 = 0; + return ZZZ_1(); + })(); + } + int ZZZ_1() + { + t0 = 0; + int t1 = t0; +#if !OPT + Func func = delegate { +#else + return ((Func)delegate { +#endif + t0 = 0; + t1 = 0; + return 0; +#if !OPT + }; + return func(); +#else + })(); +#endif + } + } + } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs new file mode 100644 index 000000000..fb7860794 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.Expected.cs @@ -0,0 +1,168 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly +{ + public class DisplayClass + { + public Program thisField; + public int field1; + public string field2; + } + + public class NestedDisplayClass + { + public DisplayClass field3; + public int field1; + public string field2; + } + + public class Program + { + public int Rand() + { + throw new NotImplementedException(); + } + + public void Test1() + { + int field1 = 42; + string field2 = "Hello World!"; + Console.WriteLine("{0} {1}", field1, field2); + } + + public void Test2() + { + DisplayClass displayClass = new DisplayClass { + field1 = 42, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.GetHashCode()); + } + + public void Test3() + { + DisplayClass displayClass = new DisplayClass { + field1 = 42, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass); + } + + public void Test4() + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = 42, + field2 = "Hello World!" + }; + int field1 = 4711; + string field2 = "ILSpy"; + DisplayClass field3; + if (displayClass.field1 > 100) { + field3 = displayClass; + } else { + field3 = null; + } + Console.WriteLine("{0} {1}", displayClass, field3); + } + + public void Test5() + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = 42, + field2 = "Hello World!" + }; + int field1 = 4711; + string field2 = "ILSpy"; + DisplayClass field3; + if (displayClass.field1 > 100) { + field3 = displayClass; + } else { + field3 = null; + } + Console.WriteLine("{0} {1}", field2 + field1, field3); + } + + public void Issue1898(int i) + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = i + }; + int field1 = default(int); + string field2 = default(string); + DisplayClass field3 = default(DisplayClass); + while (true) { + switch (Rand()) { + case 1: + field1 = Rand(); + continue; + case 2: + field2 = Rand().ToString(); + continue; + case 3: + field3 = displayClass; + continue; + } + Console.WriteLine(field1); + Console.WriteLine(field2); + Console.WriteLine(field3); + } + } + + public void Test6(int i) + { + int field1 = i; + string field2 = "Hello World!"; + if (i < 0) { + i = -i; + } + Console.WriteLine("{0} {1}", field1, field2); + } + + public void Test6b(int i) + { + int num = i; + int field1 = num; + string field2 = "Hello World!"; + if (num < 0) { + num = -num; + } + Console.WriteLine("{0} {1}", field1, field2); + } + + public void Test7(int i) + { + int field1 = i; + string field2 = "Hello World!"; + Console.WriteLine("{0} {1} {2}", field1++, field2, i); + } + + public void Test8(int i) + { + int field1 = i; + string field2 = "Hello World!"; + i = 42; + Console.WriteLine("{0} {1}", field1, field2); + } + + public void Test8b(int i) + { + int num = i; + int field1 = num; + string field2 = "Hello World!"; + num = 42; + Console.WriteLine("{0} {1}", field1, field2); + } + +// public void Test9() +// { +// Program thisField = this; +// int field1 = 1; +// string field2 = "Hello World!"; +// thisField = new Program(); +// Console.WriteLine("{0} {1}", this, thisField); +// } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.cs b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.cs new file mode 100644 index 000000000..0fb451750 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.cs @@ -0,0 +1,184 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Ugly +{ + public class DisplayClass + { + public Program thisField; + public int field1; + public string field2; + } + + public class NestedDisplayClass + { + public DisplayClass field3; + public int field1; + public string field2; + } + + public class Program + { + public int Rand() + { + throw new NotImplementedException(); + } + + public void Test1() + { + DisplayClass displayClass = new DisplayClass { + field1 = 42, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.field2); + } + + public void Test2() + { + DisplayClass displayClass = new DisplayClass { + field1 = 42, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.GetHashCode()); + } + + public void Test3() + { + DisplayClass displayClass = new DisplayClass { + field1 = 42, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass); + } + + public void Test4() + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = 42, + field2 = "Hello World!" + }; + NestedDisplayClass nested = new NestedDisplayClass { + field1 = 4711, + field2 = "ILSpy" + }; + if (displayClass.field1 > 100) { + nested.field3 = displayClass; + } else { + nested.field3 = null; + } + Console.WriteLine("{0} {1}", displayClass, nested.field3); + } + + public void Test5() + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = 42, + field2 = "Hello World!" + }; + NestedDisplayClass nested = new NestedDisplayClass { + field1 = 4711, + field2 = "ILSpy" + }; + if (displayClass.field1 > 100) { + nested.field3 = displayClass; + } else { + nested.field3 = null; + } + Console.WriteLine("{0} {1}", nested.field2 + nested.field1, nested.field3); + } + + public void Issue1898(int i) + { + DisplayClass displayClass = new DisplayClass { + thisField = this, + field1 = i + }; + NestedDisplayClass nested = new NestedDisplayClass(); + while (true) { + switch (Rand()) { + case 1: + nested.field1 = Rand(); + break; + case 2: + nested.field2 = Rand().ToString(); + break; + case 3: + nested.field3 = displayClass; + break; + default: + Console.WriteLine(nested.field1); + Console.WriteLine(nested.field2); + Console.WriteLine(nested.field3); + break; + } + } + } + + public void Test6(int i) + { + DisplayClass displayClass = new DisplayClass { + field1 = i, + field2 = "Hello World!" + }; + if (i < 0) { + i = -i; + } + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.field2); + } + + public void Test6b(int i) + { + int num = i; + DisplayClass displayClass = new DisplayClass { + field1 = num, + field2 = "Hello World!" + }; + if (num < 0) { + num = -num; + } + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.field2); + } + + public void Test7(int i) + { + DisplayClass displayClass = new DisplayClass { + field1 = i, + field2 = "Hello World!" + }; + Console.WriteLine("{0} {1} {2}", displayClass.field1++, displayClass.field2, i); + } + + public void Test8(int i) + { + DisplayClass displayClass = new DisplayClass { + field1 = i, + field2 = "Hello World!" + }; + i = 42; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.field2); + } + + public void Test8b(int i) + { + int num = i; + DisplayClass displayClass = new DisplayClass { + field1 = num, + field2 = "Hello World!" + }; + num = 42; + Console.WriteLine("{0} {1}", displayClass.field1, displayClass.field2); + } + +// public void Test9() +// { +// DisplayClass displayClass = new DisplayClass { +// thisField = this, +// field1 = 1, +// field2 = "Hello World!" +// }; +// displayClass.thisField = new Program(); +// Console.WriteLine("{0} {1}", this, displayClass.thisField); +// } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.opt.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.opt.roslyn.il new file mode 100644 index 000000000..c2ec15ad8 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.opt.roslyn.il @@ -0,0 +1,509 @@ + + + + +// Metadata version: v4.0.30319 +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly AggressiveScalarReplacementOfAggregates +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 02 00 00 00 00 00 ) + + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module AggressiveScalarReplacementOfAggregates.dll +.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass + extends [mscorlib]System.Object +{ + .field public class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program thisField + .field public int32 field1 + .field public string field2 + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method DisplayClass::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass + extends [mscorlib]System.Object +{ + .field public class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass field3 + .field public int32 field1 + .field public string field2 + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method NestedDisplayClass::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program + extends [mscorlib]System.Object +{ + .method public hidebysig instance int32 + Rand() cil managed + { + // Code size 6 (0x6) + .maxstack 8 + IL_0000: newobj instance void [mscorlib]System.NotImplementedException::.ctor() + IL_0005: throw + } // end of method Program::Rand + + .method public hidebysig instance void + Test1() cil managed + { + // Code size 53 (0x35) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldc.i4.s 42 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldstr "{0} {1}" + IL_001e: ldloc.0 + IL_001f: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0024: box [mscorlib]System.Int32 + IL_0029: ldloc.0 + IL_002a: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_002f: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0034: ret + } // end of method Program::Test1 + + .method public hidebysig instance void + Test2() cil managed + { + // Code size 58 (0x3a) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldc.i4.s 42 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldstr "{0} {1}" + IL_001e: ldloc.0 + IL_001f: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0024: box [mscorlib]System.Int32 + IL_0029: ldloc.0 + IL_002a: callvirt instance int32 [mscorlib]System.Object::GetHashCode() + IL_002f: box [mscorlib]System.Int32 + IL_0034: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0039: ret + } // end of method Program::Test2 + + .method public hidebysig instance void + Test3() cil managed + { + // Code size 48 (0x30) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldc.i4.s 42 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldstr "{0} {1}" + IL_001e: ldloc.0 + IL_001f: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0024: box [mscorlib]System.Int32 + IL_0029: ldloc.0 + IL_002a: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_002f: ret + } // end of method Program::Test3 + + .method public hidebysig instance void + Test4() cil managed + { + // Code size 104 (0x68) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.0 + IL_0007: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000c: dup + IL_000d: ldc.i4.s 42 + IL_000f: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0014: dup + IL_0015: ldstr "Hello World!" + IL_001a: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_001f: stloc.0 + IL_0020: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_0025: dup + IL_0026: ldc.i4 0x1267 + IL_002b: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0030: dup + IL_0031: ldstr "ILSpy" + IL_0036: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_003b: stloc.1 + IL_003c: ldloc.0 + IL_003d: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0042: ldc.i4.s 100 + IL_0044: ble.s IL_004f + + IL_0046: ldloc.1 + IL_0047: ldloc.0 + IL_0048: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_004d: br.s IL_0056 + + IL_004f: ldloc.1 + IL_0050: ldnull + IL_0051: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0056: ldstr "{0} {1}" + IL_005b: ldloc.0 + IL_005c: ldloc.1 + IL_005d: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0062: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0067: ret + } // end of method Program::Test4 + + .method public hidebysig instance void + Test5() cil managed + { + // Code size 125 (0x7d) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.0 + IL_0007: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000c: dup + IL_000d: ldc.i4.s 42 + IL_000f: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0014: dup + IL_0015: ldstr "Hello World!" + IL_001a: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_001f: stloc.0 + IL_0020: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_0025: dup + IL_0026: ldc.i4 0x1267 + IL_002b: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0030: dup + IL_0031: ldstr "ILSpy" + IL_0036: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_003b: stloc.1 + IL_003c: ldloc.0 + IL_003d: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0042: ldc.i4.s 100 + IL_0044: ble.s IL_004f + + IL_0046: ldloc.1 + IL_0047: ldloc.0 + IL_0048: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_004d: br.s IL_0056 + + IL_004f: ldloc.1 + IL_0050: ldnull + IL_0051: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0056: ldstr "{0} {1}" + IL_005b: ldloc.1 + IL_005c: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_0061: ldloc.1 + IL_0062: ldflda int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0067: call instance string [mscorlib]System.Int32::ToString() + IL_006c: call string [mscorlib]System.String::Concat(string, + string) + IL_0071: ldloc.1 + IL_0072: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0077: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_007c: ret + } // end of method Program::Test5 + + .method public hidebysig instance void + Issue1898(int32 i) cil managed + { + // Code size 135 (0x87) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1, + int32 V_2, + int32 V_3) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.0 + IL_0007: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000c: dup + IL_000d: ldarg.1 + IL_000e: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0013: stloc.0 + IL_0014: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_0019: stloc.1 + IL_001a: ldarg.0 + IL_001b: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_0020: stloc.2 + IL_0021: ldloc.2 + IL_0022: ldc.i4.1 + IL_0023: sub + IL_0024: switch ( + IL_0037, + IL_0045, + IL_005b) + IL_0035: br.s IL_0064 + + IL_0037: ldloc.1 + IL_0038: ldarg.0 + IL_0039: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_003e: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0043: br.s IL_001a + + IL_0045: ldloc.1 + IL_0046: ldarg.0 + IL_0047: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_004c: stloc.3 + IL_004d: ldloca.s V_3 + IL_004f: call instance string [mscorlib]System.Int32::ToString() + IL_0054: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_0059: br.s IL_001a + + IL_005b: ldloc.1 + IL_005c: ldloc.0 + IL_005d: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0062: br.s IL_001a + + IL_0064: ldloc.1 + IL_0065: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_006a: call void [mscorlib]System.Console::WriteLine(int32) + IL_006f: ldloc.1 + IL_0070: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_0075: call void [mscorlib]System.Console::WriteLine(string) + IL_007a: ldloc.1 + IL_007b: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0080: call void [mscorlib]System.Console::WriteLine(object) + IL_0085: br.s IL_001a + } // end of method Program::Issue1898 + + .method public hidebysig instance void + Test6(int32 i) cil managed + { + // Code size 60 (0x3c) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.1 + IL_0007: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000c: dup + IL_000d: ldstr "Hello World!" + IL_0012: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0017: stloc.0 + IL_0018: ldarg.1 + IL_0019: ldc.i4.0 + IL_001a: bge.s IL_0020 + + IL_001c: ldarg.1 + IL_001d: neg + IL_001e: starg.s i + IL_0020: ldstr "{0} {1}" + IL_0025: ldloc.0 + IL_0026: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_002b: box [mscorlib]System.Int32 + IL_0030: ldloc.0 + IL_0031: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0036: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_003b: ret + } // end of method Program::Test6 + + .method public hidebysig instance void + Test6b(int32 i) cil managed + { + // Code size 61 (0x3d) + .maxstack 3 + .locals init (int32 V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0007: dup + IL_0008: ldloc.0 + IL_0009: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000e: dup + IL_000f: ldstr "Hello World!" + IL_0014: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0019: stloc.1 + IL_001a: ldloc.0 + IL_001b: ldc.i4.0 + IL_001c: bge.s IL_0021 + + IL_001e: ldloc.0 + IL_001f: neg + IL_0020: stloc.0 + IL_0021: ldstr "{0} {1}" + IL_0026: ldloc.1 + IL_0027: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_002c: box [mscorlib]System.Int32 + IL_0031: ldloc.1 + IL_0032: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0037: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_003c: ret + } // end of method Program::Test6b + + .method public hidebysig instance void + Test7(int32 i) cil managed + { + // Code size 69 (0x45) + .maxstack 4 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + int32 V_1) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.1 + IL_0007: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000c: dup + IL_000d: ldstr "Hello World!" + IL_0012: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0017: stloc.0 + IL_0018: ldstr "{0} {1} {2}" + IL_001d: ldloc.0 + IL_001e: dup + IL_001f: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0024: stloc.1 + IL_0025: ldloc.1 + IL_0026: ldc.i4.1 + IL_0027: add + IL_0028: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_002d: ldloc.1 + IL_002e: box [mscorlib]System.Int32 + IL_0033: ldloc.0 + IL_0034: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0039: ldarg.1 + IL_003a: box [mscorlib]System.Int32 + IL_003f: call void [mscorlib]System.Console::WriteLine(string, + object, + object, + object) + IL_0044: ret + } // end of method Program::Test7 + + .method public hidebysig instance void + Test8(int32 i) cil managed + { + // Code size 56 (0x38) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0005: dup + IL_0006: ldarg.1 + IL_0007: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000c: dup + IL_000d: ldstr "Hello World!" + IL_0012: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0017: stloc.0 + IL_0018: ldc.i4.s 42 + IL_001a: starg.s i + IL_001c: ldstr "{0} {1}" + IL_0021: ldloc.0 + IL_0022: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0027: box [mscorlib]System.Int32 + IL_002c: ldloc.0 + IL_002d: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0032: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0037: ret + } // end of method Program::Test8 + + .method public hidebysig instance void + Test8b(int32 i) cil managed + { + // Code size 57 (0x39) + .maxstack 3 + .locals init (int32 V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_1) + IL_0000: ldarg.1 + IL_0001: stloc.0 + IL_0002: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0007: dup + IL_0008: ldloc.0 + IL_0009: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000e: dup + IL_000f: ldstr "Hello World!" + IL_0014: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0019: stloc.1 + IL_001a: ldc.i4.s 42 + IL_001c: stloc.0 + IL_001d: ldstr "{0} {1}" + IL_0022: ldloc.1 + IL_0023: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0028: box [mscorlib]System.Int32 + IL_002d: ldloc.1 + IL_002e: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0033: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0038: ret + } // end of method Program::Test8b + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: ret + } // end of method Program::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.roslyn.il new file mode 100644 index 000000000..a51b839f8 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Ugly/AggressiveScalarReplacementOfAggregates.roslyn.il @@ -0,0 +1,577 @@ + + + + +// Metadata version: v4.0.30319 +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly AggressiveScalarReplacementOfAggregates +{ + .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) + .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx + 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. + + // --- The following custom attribute is added automatically, do not uncomment ------- + // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 ) + + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module AggressiveScalarReplacementOfAggregates.dll +.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 ) +.imagebase 0x10000000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00000001 // ILONLY + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass + extends [mscorlib]System.Object +{ + .field public class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program thisField + .field public int32 field1 + .field public string field2 + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method DisplayClass::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass + extends [mscorlib]System.Object +{ + .field public class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass field3 + .field public int32 field1 + .field public string field2 + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method NestedDisplayClass::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program + extends [mscorlib]System.Object +{ + .method public hidebysig instance int32 + Rand() cil managed + { + // Code size 7 (0x7) + .maxstack 8 + IL_0000: nop + IL_0001: newobj instance void [mscorlib]System.NotImplementedException::.ctor() + IL_0006: throw + } // end of method Program::Rand + + .method public hidebysig instance void + Test1() cil managed + { + // Code size 55 (0x37) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldc.i4.s 42 + IL_0009: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000e: dup + IL_000f: ldstr "Hello World!" + IL_0014: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0019: stloc.0 + IL_001a: ldstr "{0} {1}" + IL_001f: ldloc.0 + IL_0020: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0025: box [mscorlib]System.Int32 + IL_002a: ldloc.0 + IL_002b: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0030: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0035: nop + IL_0036: ret + } // end of method Program::Test1 + + .method public hidebysig instance void + Test2() cil managed + { + // Code size 60 (0x3c) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldc.i4.s 42 + IL_0009: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000e: dup + IL_000f: ldstr "Hello World!" + IL_0014: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0019: stloc.0 + IL_001a: ldstr "{0} {1}" + IL_001f: ldloc.0 + IL_0020: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0025: box [mscorlib]System.Int32 + IL_002a: ldloc.0 + IL_002b: callvirt instance int32 [mscorlib]System.Object::GetHashCode() + IL_0030: box [mscorlib]System.Int32 + IL_0035: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_003a: nop + IL_003b: ret + } // end of method Program::Test2 + + .method public hidebysig instance void + Test3() cil managed + { + // Code size 50 (0x32) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldc.i4.s 42 + IL_0009: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000e: dup + IL_000f: ldstr "Hello World!" + IL_0014: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0019: stloc.0 + IL_001a: ldstr "{0} {1}" + IL_001f: ldloc.0 + IL_0020: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0025: box [mscorlib]System.Int32 + IL_002a: ldloc.0 + IL_002b: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0030: nop + IL_0031: ret + } // end of method Program::Test3 + + .method public hidebysig instance void + Test4() cil managed + { + // Code size 114 (0x72) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1, + bool V_2) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.0 + IL_0008: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000d: dup + IL_000e: ldc.i4.s 42 + IL_0010: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0015: dup + IL_0016: ldstr "Hello World!" + IL_001b: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0020: stloc.0 + IL_0021: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_0026: dup + IL_0027: ldc.i4 0x1267 + IL_002c: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0031: dup + IL_0032: ldstr "ILSpy" + IL_0037: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_003c: stloc.1 + IL_003d: ldloc.0 + IL_003e: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0043: ldc.i4.s 100 + IL_0045: cgt + IL_0047: stloc.2 + IL_0048: ldloc.2 + IL_0049: brfalse.s IL_0056 + + IL_004b: nop + IL_004c: ldloc.1 + IL_004d: ldloc.0 + IL_004e: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0053: nop + IL_0054: br.s IL_005f + + IL_0056: nop + IL_0057: ldloc.1 + IL_0058: ldnull + IL_0059: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_005e: nop + IL_005f: ldstr "{0} {1}" + IL_0064: ldloc.0 + IL_0065: ldloc.1 + IL_0066: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_006b: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0070: nop + IL_0071: ret + } // end of method Program::Test4 + + .method public hidebysig instance void + Test5() cil managed + { + // Code size 135 (0x87) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1, + bool V_2) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.0 + IL_0008: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000d: dup + IL_000e: ldc.i4.s 42 + IL_0010: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0015: dup + IL_0016: ldstr "Hello World!" + IL_001b: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0020: stloc.0 + IL_0021: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_0026: dup + IL_0027: ldc.i4 0x1267 + IL_002c: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0031: dup + IL_0032: ldstr "ILSpy" + IL_0037: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_003c: stloc.1 + IL_003d: ldloc.0 + IL_003e: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0043: ldc.i4.s 100 + IL_0045: cgt + IL_0047: stloc.2 + IL_0048: ldloc.2 + IL_0049: brfalse.s IL_0056 + + IL_004b: nop + IL_004c: ldloc.1 + IL_004d: ldloc.0 + IL_004e: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0053: nop + IL_0054: br.s IL_005f + + IL_0056: nop + IL_0057: ldloc.1 + IL_0058: ldnull + IL_0059: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_005e: nop + IL_005f: ldstr "{0} {1}" + IL_0064: ldloc.1 + IL_0065: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_006a: ldloc.1 + IL_006b: ldflda int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0070: call instance string [mscorlib]System.Int32::ToString() + IL_0075: call string [mscorlib]System.String::Concat(string, + string) + IL_007a: ldloc.1 + IL_007b: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0080: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0085: nop + IL_0086: ret + } // end of method Program::Test5 + + .method public hidebysig instance void + Issue1898(int32 i) cil managed + { + // Code size 151 (0x97) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass V_1, + int32 V_2, + int32 V_3, + int32 V_4, + bool V_5) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.0 + IL_0008: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::thisField + IL_000d: dup + IL_000e: ldarg.1 + IL_000f: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0014: stloc.0 + IL_0015: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::.ctor() + IL_001a: stloc.1 + IL_001b: br.s IL_0092 + + IL_001d: nop + IL_001e: ldarg.0 + IL_001f: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_0024: stloc.3 + IL_0025: ldloc.3 + IL_0026: stloc.2 + IL_0027: ldloc.2 + IL_0028: ldc.i4.1 + IL_0029: sub + IL_002a: switch ( + IL_003d, + IL_004b, + IL_0062) + IL_003b: br.s IL_006b + + IL_003d: ldloc.1 + IL_003e: ldarg.0 + IL_003f: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_0044: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0049: br.s IL_0091 + + IL_004b: ldloc.1 + IL_004c: ldarg.0 + IL_004d: call instance int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program::Rand() + IL_0052: stloc.s V_4 + IL_0054: ldloca.s V_4 + IL_0056: call instance string [mscorlib]System.Int32::ToString() + IL_005b: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_0060: br.s IL_0091 + + IL_0062: ldloc.1 + IL_0063: ldloc.0 + IL_0064: stfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0069: br.s IL_0091 + + IL_006b: ldloc.1 + IL_006c: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field1 + IL_0071: call void [mscorlib]System.Console::WriteLine(int32) + IL_0076: nop + IL_0077: ldloc.1 + IL_0078: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field2 + IL_007d: call void [mscorlib]System.Console::WriteLine(string) + IL_0082: nop + IL_0083: ldloc.1 + IL_0084: ldfld class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass ICSharpCode.Decompiler.Tests.TestCases.Ugly.NestedDisplayClass::field3 + IL_0089: call void [mscorlib]System.Console::WriteLine(object) + IL_008e: nop + IL_008f: br.s IL_0091 + + IL_0091: nop + IL_0092: ldc.i4.1 + IL_0093: stloc.s V_5 + IL_0095: br.s IL_001d + } // end of method Program::Issue1898 + + .method public hidebysig instance void + Test6(int32 i) cil managed + { + // Code size 68 (0x44) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + bool V_1) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.1 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldarg.1 + IL_001a: ldc.i4.0 + IL_001b: clt + IL_001d: stloc.1 + IL_001e: ldloc.1 + IL_001f: brfalse.s IL_0027 + + IL_0021: nop + IL_0022: ldarg.1 + IL_0023: neg + IL_0024: starg.s i + IL_0026: nop + IL_0027: ldstr "{0} {1}" + IL_002c: ldloc.0 + IL_002d: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0032: box [mscorlib]System.Int32 + IL_0037: ldloc.0 + IL_0038: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_003d: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0042: nop + IL_0043: ret + } // end of method Program::Test6 + + .method public hidebysig instance void + Test6b(int32 i) cil managed + { + // Code size 69 (0x45) + .maxstack 3 + .locals init (int32 V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_1, + bool V_2) + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: stloc.0 + IL_0003: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0008: dup + IL_0009: ldloc.0 + IL_000a: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000f: dup + IL_0010: ldstr "Hello World!" + IL_0015: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_001a: stloc.1 + IL_001b: ldloc.0 + IL_001c: ldc.i4.0 + IL_001d: clt + IL_001f: stloc.2 + IL_0020: ldloc.2 + IL_0021: brfalse.s IL_0028 + + IL_0023: nop + IL_0024: ldloc.0 + IL_0025: neg + IL_0026: stloc.0 + IL_0027: nop + IL_0028: ldstr "{0} {1}" + IL_002d: ldloc.1 + IL_002e: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0033: box [mscorlib]System.Int32 + IL_0038: ldloc.1 + IL_0039: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_003e: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0043: nop + IL_0044: ret + } // end of method Program::Test6b + + .method public hidebysig instance void + Test7(int32 i) cil managed + { + // Code size 71 (0x47) + .maxstack 4 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0, + int32 V_1) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.1 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldstr "{0} {1} {2}" + IL_001e: ldloc.0 + IL_001f: dup + IL_0020: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0025: stloc.1 + IL_0026: ldloc.1 + IL_0027: ldc.i4.1 + IL_0028: add + IL_0029: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_002e: ldloc.1 + IL_002f: box [mscorlib]System.Int32 + IL_0034: ldloc.0 + IL_0035: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_003a: ldarg.1 + IL_003b: box [mscorlib]System.Int32 + IL_0040: call void [mscorlib]System.Console::WriteLine(string, + object, + object, + object) + IL_0045: nop + IL_0046: ret + } // end of method Program::Test7 + + .method public hidebysig instance void + Test8(int32 i) cil managed + { + // Code size 58 (0x3a) + .maxstack 3 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_0) + IL_0000: nop + IL_0001: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0006: dup + IL_0007: ldarg.1 + IL_0008: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000d: dup + IL_000e: ldstr "Hello World!" + IL_0013: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0018: stloc.0 + IL_0019: ldc.i4.s 42 + IL_001b: starg.s i + IL_001d: ldstr "{0} {1}" + IL_0022: ldloc.0 + IL_0023: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0028: box [mscorlib]System.Int32 + IL_002d: ldloc.0 + IL_002e: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0033: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0038: nop + IL_0039: ret + } // end of method Program::Test8 + + .method public hidebysig instance void + Test8b(int32 i) cil managed + { + // Code size 59 (0x3b) + .maxstack 3 + .locals init (int32 V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass V_1) + IL_0000: nop + IL_0001: ldarg.1 + IL_0002: stloc.0 + IL_0003: newobj instance void ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::.ctor() + IL_0008: dup + IL_0009: ldloc.0 + IL_000a: stfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_000f: dup + IL_0010: ldstr "Hello World!" + IL_0015: stfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_001a: stloc.1 + IL_001b: ldc.i4.s 42 + IL_001d: stloc.0 + IL_001e: ldstr "{0} {1}" + IL_0023: ldloc.1 + IL_0024: ldfld int32 ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field1 + IL_0029: box [mscorlib]System.Int32 + IL_002e: ldloc.1 + IL_002f: ldfld string ICSharpCode.Decompiler.Tests.TestCases.Ugly.DisplayClass::field2 + IL_0034: call void [mscorlib]System.Console::WriteLine(string, + object, + object) + IL_0039: nop + IL_003a: ret + } // end of method Program::Test8b + + .method public hidebysig specialname rtspecialname + instance void .ctor() cil managed + { + // Code size 8 (0x8) + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void [mscorlib]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method Program::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Ugly.Program + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs index b69e33bae..db5f8c4da 100644 --- a/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/UglyTestRunner.cs @@ -105,6 +105,14 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp1)); } + [Test] + public void AggressiveScalarReplacementOfAggregates([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings { + AggressiveScalarReplacementOfAggregates = true + }); + } + void RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, DecompilerSettings decompilerSettings = null) { Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, decompilerSettings); diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 68a3daf8e..9eeec424c 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -200,6 +200,7 @@ namespace ICSharpCode.Decompiler.CSharp int skipCount = localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters; ide.TypeArguments.AddRange(method.TypeArguments.Skip(skipCount).Select(expressionBuilder.ConvertType)); } + ide.AddAnnotation(localFunction); target = ide.WithoutILInstruction() .WithRR(ToMethodGroup(method, localFunction)); } else { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs index e531b23d0..2aad6a16d 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs @@ -21,7 +21,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Syntax; -using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.Semantics; @@ -257,7 +256,16 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (node is IdentifierExpression identExpr) { var rr = identExpr.GetResolveResult() as ILVariableResolveResult; if (rr != null && VariableNeedsDeclaration(rr.Variable.Kind)) { - var variable = rr.Variable; + FindInsertionPointForVariable(rr.Variable); + } else if (identExpr.Annotation() is ILFunction localFunction && localFunction.Kind == ILFunctionKind.LocalFunction) { + foreach (var v in localFunction.CapturedVariables) { + if (VariableNeedsDeclaration(v.Kind)) + FindInsertionPointForVariable(v); + } + } + + void FindInsertionPointForVariable(ILVariable variable) + { InsertionPoint newPoint; int startIndex = scopeTracking.Count - 1; if (variable.CaptureScope != null && startIndex > 0 && variable.CaptureScope != scopeTracking[startIndex].Scope) { @@ -287,7 +295,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } else { v = new VariableToDeclare(variable, variable.HasInitialValue, newPoint, identExpr, sourceOrder: variableDict.Count); - variableDict.Add(rr.Variable, v); + variableDict.Add(variable, v); } } } diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 8dd738273..3e8349bd2 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1347,6 +1347,24 @@ namespace ICSharpCode.Decompiler } } + bool aggressiveScalarReplacementOfAggregates = false; + + [Category("DecompilerSettings.Other")] + [Description("DecompilerSettings.AggressiveScalarReplacementOfAggregates")] + // TODO : Remove once https://github.com/icsharpcode/ILSpy/issues/2032 is fixed. +#if !DEBUG + [Browsable(false)] +#endif + public bool AggressiveScalarReplacementOfAggregates { + get { return aggressiveScalarReplacementOfAggregates; } + set { + if (aggressiveScalarReplacementOfAggregates != value) { + aggressiveScalarReplacementOfAggregates = value; + OnPropertyChanged(); + } + } + } + CSharpFormattingOptions csharpFormattingOptions; [Browsable(false)] diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs index 60423625c..77b6dbae5 100644 --- a/ICSharpCode.Decompiler/IL/ILVariable.cs +++ b/ICSharpCode.Decompiler/IL/ILVariable.cs @@ -337,6 +337,17 @@ namespace ICSharpCode.Decompiler.IL } } + /// + /// Gets whether the variable is dead - unused. + /// + public bool IsDead { + get { + return StoreCount == (HasInitialValue ? 1 : 0) + && LoadCount == 0 + && AddressCount == 0; + } + } + /// /// The field which was converted to a local variable. /// Set when the variable is from a 'yield return' or 'async' state machine. diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index 5bb1a2dae..87ba58072 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.IL.Transforms; +using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL { @@ -131,6 +132,45 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(call.Arguments[0].MatchLdLoc(v)); } break; + case BlockKind.ArrayInitializer: + var final = finalInstruction as LdLoc; + Debug.Assert(final != null && final.Variable.IsSingleDefinition && final.Variable.Kind == VariableKind.InitializerTarget); + IType type = null; + Debug.Assert(Instructions[0].MatchStLoc(final.Variable, out var init) && init.MatchNewArr(out type)); + for (int i = 1; i < Instructions.Count; i++) { + Debug.Assert(Instructions[i].MatchStObj(out ILInstruction target, out _, out var t) && type != null && type.Equals(t)); + Debug.Assert(target.MatchLdElema(out t, out ILInstruction array) && type.Equals(t)); + Debug.Assert(array.MatchLdLoc(out ILVariable v) && v == final.Variable); + } + break; + case BlockKind.CollectionInitializer: + case BlockKind.ObjectInitializer: + var final2 = finalInstruction as LdLoc; + Debug.Assert(final2 != null); + var initVar2 = final2.Variable; + Debug.Assert(initVar2.StoreCount == 1 && initVar2.Kind == VariableKind.InitializerTarget); + IType type2 = null; + bool condition = Instructions[0].MatchStLoc(final2.Variable, out var init2); + Debug.Assert(condition); + Debug.Assert(init2 is NewObj || init2 is DefaultValue || (init2 is Block named && named.Kind == BlockKind.CallWithNamedArgs)); + switch (init2) { + case NewObj newObj: + type2 = newObj.Method.DeclaringType; + break; + case DefaultValue defaultValue: + type2 = defaultValue.Type; + break; + case Block callWithNamedArgs when callWithNamedArgs.Kind == BlockKind.CallWithNamedArgs: + type2 = ((CallInstruction)callWithNamedArgs.FinalInstruction).Method.ReturnType; + break; + default: + Debug.Assert(false); + break; + } + for (int i = 1; i < Instructions.Count; i++) { + Debug.Assert(Instructions[i] is StLoc || AccessPathElement.GetAccessPath(Instructions[i], type2).Kind != IL.Transforms.AccessPathKind.Invalid); + } + break; } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index ab3caa5cf..7291484b0 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -73,6 +73,12 @@ namespace ICSharpCode.Decompiler.IL /// public BlockContainer DeclarationScope { get; internal set; } + /// + /// Gets the set of captured variables by this ILFunction. + /// + /// This is populated by the step. + public HashSet CapturedVariables { get; } = new HashSet(); + /// /// List of warnings of ILReader. /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs b/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs index c3c53092f..f27d962a7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILVariableCollection.cs @@ -102,8 +102,10 @@ namespace ICSharpCode.Decompiler.IL { for (int i = 0; i < list.Count;) { var v = list[i]; - int deadStoreCount = v.HasInitialValue ? 1 : 0; - if (v.StoreCount == deadStoreCount && v.LoadCount == 0 && v.AddressCount == 0 && v.Kind != VariableKind.DisplayClassLocal) { + // Note: we cannot remove display-class locals from the collection, + // even if they are unused - which is always the case, if TDCU succeeds, + // because they are necessary for PDB generation to produce correct results. + if (v.IsDead && v.Kind != VariableKind.DisplayClassLocal) { RemoveAt(i); } else { i++; diff --git a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs index 5d68b4131..b5dc0423f 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs @@ -53,9 +53,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (instWithVar.Variable.Kind == VariableKind.Local) { instWithVar.Variable.Kind = VariableKind.DisplayClassLocal; } - var displayClassTypeDef = instWithVar.Variable.Type.GetDefinition(); if (instWithVar.Variable.IsSingleDefinition && instWithVar.Variable.StoreInstructions.SingleOrDefault() is StLoc store) { - if (store.Value is NewObj newObj) { + if (store.Value is NewObj) { instWithVar.Variable.CaptureScope = BlockContainer.FindClosestContainer(store); } } @@ -141,6 +140,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (LocalFunctionDecompiler.IsLocalFunctionMethod(targetMethod, context)) return null; target = value.Arguments[0]; + if (!ValidateDelegateTarget(target)) + return null; var handle = (MethodDefinitionHandle)targetMethod.MetadataToken; if (activeMethods.Contains(handle)) { this.context.Function.Warnings.Add(" Found self-referencing delegate construction. Abort transformation to avoid stack overflow."); @@ -169,7 +170,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms var nestedContext = new ILTransformContext(context, function); function.RunTransforms(CSharpDecompiler.GetILTransforms().TakeWhile(t => !(t is DelegateConstruction)).Concat(GetTransforms()), nestedContext); nestedContext.Step("DelegateConstruction (ReplaceDelegateTargetVisitor)", function); - function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(v => v.Index == -1 && v.Kind == VariableKind.Parameter))); + function.AcceptVisitor(new ReplaceDelegateTargetVisitor(target, function.Variables.SingleOrDefault(VariableKindExtensions.IsThis))); // handle nested lambdas nestedContext.StepStartGroup("DelegateConstruction (nested lambdas)", function); ((IILTransform)this).Run(function, nestedContext); @@ -180,6 +181,37 @@ namespace ICSharpCode.Decompiler.IL.Transforms return function; } + private static bool ValidateDelegateTarget(ILInstruction inst) + { + switch (inst) { + case LdNull _: + return true; + case LdLoc ldloc: + return ldloc.Variable.IsSingleDefinition; + case LdObj ldobj: + // TODO : should make sure that the display-class 'this' is unused, + // if the delegate target is ldobj(ldsflda field). + if (ldobj.Target is LdsFlda) + return true; + // TODO : ldfld chains must be validated more thoroughly, i.e., we should make sure + // that the value of the field is never changed. + ILInstruction target = ldobj; + while (target is LdObj || target is LdFlda) { + if (target is LdObj o) { + target = o.Target; + continue; + } + if (target is LdFlda f) { + target = f.Target; + continue; + } + } + return target is LdLoc; + default: + return false; + } + } + private IEnumerable GetTransforms() { yield return new CombineExitsTransform(); @@ -206,7 +238,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms child.AcceptVisitor(this); } } - + + protected internal override void VisitILFunction(ILFunction function) + { + if (function == thisVariable?.Function) { + ILVariable v = null; + switch (target) { + case LdLoc l: + v = l.Variable; + break; + case LdObj lo: + ILInstruction inner = lo.Target; + while (inner is LdFlda ldf) { + inner = ldf.Target; + } + if (inner is LdLoc l2) + v = l2.Variable; + break; + } + if (v != null) + function.CapturedVariables.Add(v); + } + base.VisitILFunction(function); + } + protected internal override void VisitLdLoc(LdLoc inst) { if (inst.Variable == thisVariable) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs index 073fe8f8f..1f59d6df5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/LocalFunctionDecompiler.cs @@ -46,6 +46,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms public List UseSites; public IMethod Method; public ILFunction Definition; + /// + /// Used to store all synthesized call-site arguments grouped by the parameter index. + /// We use a dictionary instead of a simple array, because -1 is used for the this parameter + /// and there might be many non-synthesized arguments in between. + /// + public Dictionary> LocalFunctionArguments; } /// @@ -78,69 +84,89 @@ namespace ICSharpCode.Decompiler.IL.Transforms this.context = context; this.resolveContext = new SimpleTypeResolveContext(function.Method); var localFunctions = new Dictionary(); - var cancellationToken = context.CancellationToken; // Find all local functions declared inside this method, including nested local functions or local functions declared in lambdas. FindUseSites(function, context, localFunctions); - foreach (var (_, info) in localFunctions) { - cancellationToken.ThrowIfCancellationRequested(); + ReplaceReferencesToDisplayClassThis(localFunctions.Values); + DetermineCaptureAndDeclarationScopes(localFunctions.Values); + PropagateClosureParameterArguments(localFunctions); + TransformUseSites(localFunctions.Values); + } + + private void ReplaceReferencesToDisplayClassThis(Dictionary.ValueCollection localFunctions) + { + foreach (var info in localFunctions) { + var localFunction = info.Definition; + if (localFunction.Method.IsStatic) + continue; + var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); + if (thisVar == null) + continue; + var compatibleArgument = FindCompatibleArgument(info, info.UseSites.SelectArray(u => u.Arguments[0]), ignoreStructure: true); + Debug.Assert(compatibleArgument != null); + context.Step($"Replace 'this' with {compatibleArgument}", localFunction); + localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, thisVar)); + DetermineCaptureAndDeclarationScope(info, -1, compatibleArgument); + } + } + + private void DetermineCaptureAndDeclarationScopes(Dictionary.ValueCollection localFunctions) + { + foreach (var info in localFunctions) { + context.CancellationToken.ThrowIfCancellationRequested(); if (info.Definition == null) { - function.Warnings.Add($"Could not decode local function '{info.Method}'"); + context.Function.Warnings.Add($"Could not decode local function '{info.Method}'"); continue; } - context.StepStartGroup($"Transform " + info.Definition.Name, info.Definition); + context.StepStartGroup($"Determine and move to declaration scope of " + info.Definition.Name, info.Definition); try { var localFunction = info.Definition; - if (!localFunction.Method.IsStatic) { - var thisVar = localFunction.Variables.SingleOrDefault(VariableKindExtensions.IsThis); - var target = info.UseSites.Where(us => us.Arguments[0].MatchLdLoc(out _)).FirstOrDefault()?.Arguments[0]; - if (target == null) { - target = info.UseSites[0].Arguments[0]; - if (target.MatchLdFld(out var target1, out var field) && thisVar.Type.Equals(field.Type) && field.Type.Kind == TypeKind.Class && TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition())) { - var variable = function.Descendants.OfType().SelectMany(f => f.Variables).Where(v => !v.IsThis() && TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type)).OnlyOrDefault(); - if (variable != null) { - target = new LdLoc(variable); - HandleArgument(localFunction, 1, 0, target); - } - } - } - context.Step($"Replace 'this' with {target}", localFunction); - localFunction.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(target, thisVar)); - } foreach (var useSite in info.UseSites) { - DetermineCaptureAndDeclarationScope(localFunction, useSite); + DetermineCaptureAndDeclarationScope(info, useSite); - if (function.Method.IsConstructor && localFunction.DeclarationScope == null) { + if (context.Function.Method.IsConstructor && localFunction.DeclarationScope == null) { localFunction.DeclarationScope = BlockContainer.FindClosestContainer(useSite); } } if (localFunction.DeclarationScope == null) { - localFunction.DeclarationScope = (BlockContainer)function.Body; + localFunction.DeclarationScope = (BlockContainer)context.Function.Body; } ILFunction declaringFunction = GetDeclaringFunction(localFunction); - if (declaringFunction != function) { - function.LocalFunctions.Remove(localFunction); + if (declaringFunction != context.Function) { + context.Step($"Move {localFunction.Name} from {context.Function.Name} to {declaringFunction.Name}", localFunction); + context.Function.LocalFunctions.Remove(localFunction); declaringFunction.LocalFunctions.Add(localFunction); } if (TryValidateSkipCount(info, out int skipCount) && skipCount != localFunction.ReducedMethod.NumberOfCompilerGeneratedTypeParameters) { Debug.Assert(false); - function.Warnings.Add($"Could not decode local function '{info.Method}'"); - if (declaringFunction != function) { + context.Function.Warnings.Add($"Could not decode local function '{info.Method}'"); + if (declaringFunction != context.Function) { declaringFunction.LocalFunctions.Remove(localFunction); } - continue; } + } finally { + context.StepEndGroup(keepIfEmpty: true); + } + } + } + private void TransformUseSites(Dictionary.ValueCollection localFunctions) + { + foreach (var info in localFunctions) { + context.CancellationToken.ThrowIfCancellationRequested(); + if (info.Definition == null) continue; + context.StepStartGroup($"TransformUseSites of " + info.Definition.Name, info.Definition); + try { foreach (var useSite in info.UseSites) { - context.Step($"Transform use site at IL_{useSite.StartILOffset:x4}", useSite); + context.Step($"Transform use-site at IL_{useSite.StartILOffset:x4}", useSite); if (useSite.OpCode == OpCode.NewObj) { - TransformToLocalFunctionReference(localFunction, useSite); + TransformToLocalFunctionReference(info.Definition, useSite); } else { - TransformToLocalFunctionInvocation(localFunction.ReducedMethod, useSite); + TransformToLocalFunctionInvocation(info.Definition.ReducedMethod, useSite); } } } finally { @@ -149,6 +175,106 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + private void PropagateClosureParameterArguments(Dictionary localFunctions) + { + foreach (var localFunction in context.Function.Descendants.OfType()) { + if (localFunction.Kind != ILFunctionKind.LocalFunction) + continue; + context.CancellationToken.ThrowIfCancellationRequested(); + var token = (MethodDefinitionHandle)localFunction.Method.MetadataToken; + var info = localFunctions[token]; + + foreach (var useSite in info.UseSites) { + if (useSite is NewObj) { + AddAsArgument(-1, useSite.Arguments[0]); + } else { + int firstArgumentIndex; + if (info.Method.IsStatic) { + firstArgumentIndex = 0; + } else { + firstArgumentIndex = 1; + AddAsArgument(-1, useSite.Arguments[0]); + } + for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { + AddAsArgument(i - firstArgumentIndex, useSite.Arguments[i]); + } + } + } + + context.StepStartGroup($"PropagateClosureParameterArguments of " + info.Definition.Name, info.Definition); + try { + foreach (var (index, arguments) in info.LocalFunctionArguments) { + var targetVariable = info.Definition.Variables.SingleOrDefault(p => p.Kind == VariableKind.Parameter && p.Index == index); + if (targetVariable == null) + continue; + var compatibleArgument = FindCompatibleArgument(info, arguments); + Debug.Assert(compatibleArgument != null); + context.Step($"Replace '{targetVariable}' with '{compatibleArgument}'", info.Definition); + info.Definition.AcceptVisitor(new DelegateConstruction.ReplaceDelegateTargetVisitor(compatibleArgument, targetVariable)); + } + } finally { + context.StepEndGroup(keepIfEmpty: true); + } + + void AddAsArgument(int index, ILInstruction argument) + { + switch (argument) { + case LdLoc _: + case LdLoca _: + case LdFlda _: + case LdObj _: + if (index >= 0 && !IsClosureParameter(info.Method.Parameters[index], resolveContext)) + return; + break; + default: + if (index >= 0 && IsClosureParameter(info.Method.Parameters[index], resolveContext)) + info.Definition.Warnings.Add("Could not transform parameter " + index + ": unsupported argument pattern"); + return; + } + + if (!info.LocalFunctionArguments.TryGetValue(index, out var arguments)) { + arguments = new List(); + info.LocalFunctionArguments.Add(index, arguments); + } + arguments.Add(argument); + } + } + } + + private ILInstruction FindCompatibleArgument(LocalFunctionInfo info, IList arguments, bool ignoreStructure = false) + { + foreach (var arg in arguments) { + if (arg is IInstructionWithVariableOperand ld2 && (ignoreStructure || info.Definition.IsDescendantOf(ld2.Variable.Function))) + return arg; + var v = ResolveAncestorScopeReference(arg); + if (v != null) + return new LdLoc(v); + } + return null; + } + + private ILVariable ResolveAncestorScopeReference(ILInstruction inst) + { + if (!inst.MatchLdFld(out var target, out var field)) + return null; + if (field.Type.Kind != TypeKind.Class) + return null; + if (!(TransformDisplayClassUsage.IsPotentialClosure(context, field.Type.GetDefinition()) || context.Function.Method.DeclaringType.Equals(field.Type))) + return null; + foreach (var v in context.Function.Descendants.OfType().SelectMany(f => f.Variables)) { + if (v.Kind != VariableKind.Local && v.Kind != VariableKind.DisplayClassLocal && v.Kind != VariableKind.StackSlot) { + if (!(v.Kind == VariableKind.Parameter && v.Index == -1)) + continue; + if (v.Type.Equals(field.Type)) + return v; + } + if (!(TransformDisplayClassUsage.IsClosure(context, v, out var varType, out _) && varType.Equals(field.Type))) + continue; + return v; + } + return null; + } + private ILFunction GetDeclaringFunction(ILFunction localFunction) { if (localFunction.DeclarationScope == null) @@ -217,6 +343,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms context.StepStartGroup($"Read local function '{targetMethod.Name}'", inst); info = new LocalFunctionInfo() { UseSites = new List() { inst }, + LocalFunctionArguments = new Dictionary>(), Method = (IMethod)targetMethod.MemberDefinition, }; var rootFunction = context.Function; @@ -347,7 +474,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst; } - LocalFunctionMethod ReduceToLocalFunction(IMethod method, int skipCount) + LocalFunctionMethod ReduceToLocalFunction(IMethod method, int typeParametersToRemove) { int parametersToRemove = 0; for (int i = method.Parameters.Count - 1; i >= 0; i--) { @@ -355,12 +482,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms break; parametersToRemove++; } - return new LocalFunctionMethod(method, parametersToRemove, skipCount); + return new LocalFunctionMethod(method, parametersToRemove, typeParametersToRemove); } static void TransformToLocalFunctionReference(ILFunction function, CallInstruction useSite) { - useSite.Arguments[0].ReplaceWith(new LdNull().WithILRange(useSite.Arguments[0])); + ILInstruction target = useSite.Arguments[0]; + target.ReplaceWith(new LdNull().WithILRange(target)); + if (target is IInstructionWithVariableOperand withVar && withVar.Variable.Kind == VariableKind.Local) { + withVar.Variable.Kind = VariableKind.DisplayClassLocal; + } var fnptr = (IInstructionWithMethodOperand)useSite.Arguments[1]; var specializeMethod = function.ReducedMethod.Specialize(fnptr.Method.Substitution); var replacement = new LdFtn(specializeMethod).WithILRange((ILInstruction)fnptr); @@ -396,41 +527,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms useSite.ReplaceWith(replacement); } - void DetermineCaptureAndDeclarationScope(ILFunction function, CallInstruction useSite) + void DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, CallInstruction useSite) { - int firstArgumentIndex = function.Method.IsStatic ? 0 : 1; + int firstArgumentIndex = info.Definition.Method.IsStatic ? 0 : 1; for (int i = useSite.Arguments.Count - 1; i >= firstArgumentIndex; i--) { - if (!HandleArgument(function, firstArgumentIndex, i, useSite.Arguments[i])) + if (!DetermineCaptureAndDeclarationScope(info, i - firstArgumentIndex, useSite.Arguments[i])) break; } if (firstArgumentIndex > 0) { - HandleArgument(function, firstArgumentIndex, 0, useSite.Arguments[0]); + DetermineCaptureAndDeclarationScope(info, -1, useSite.Arguments[0]); } } - bool HandleArgument(ILFunction function, int firstArgumentIndex, int i, ILInstruction arg) + bool DetermineCaptureAndDeclarationScope(LocalFunctionInfo info, int parameterIndex, ILInstruction arg) { + ILFunction function = info.Definition; ILVariable closureVar; - if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) - return false; + if (parameterIndex >= 0) { + if (!(parameterIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[parameterIndex], resolveContext))) + return false; + } + if (!(arg.MatchLdLoc(out closureVar) || arg.MatchLdLoca(out closureVar))) { + closureVar = ResolveAncestorScopeReference(arg); + if (closureVar == null) + return false; + } if (closureVar.Kind == VariableKind.NamedArgument) return false; - if (!TransformDisplayClassUsage.IsClosure(context, closureVar, out _, out var initializer)) + var initializer = GetClosureInitializer(closureVar); + if (initializer == null) return false; - if (i - firstArgumentIndex >= 0) { - Debug.Assert(i - firstArgumentIndex < function.Method.Parameters.Count && IsClosureParameter(function.Method.Parameters[i - firstArgumentIndex], resolveContext)); - } // determine the capture scope of closureVar and the declaration scope of the function var additionalScope = BlockContainer.FindClosestContainer(initializer); if (closureVar.CaptureScope == null) closureVar.CaptureScope = additionalScope; - else - closureVar.CaptureScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + else { + BlockContainer combinedScope = FindCommonAncestorInstruction(closureVar.CaptureScope, additionalScope); + Debug.Assert(combinedScope != null); + closureVar.CaptureScope = combinedScope; + } + if (closureVar.Kind == VariableKind.Local) { + closureVar.Kind = VariableKind.DisplayClassLocal; + } if (function.DeclarationScope == null) function.DeclarationScope = closureVar.CaptureScope; else if (!IsInNestedLocalFunction(function.DeclarationScope, closureVar.CaptureScope.Ancestors.OfType().First())) function.DeclarationScope = FindCommonAncestorInstruction(function.DeclarationScope, closureVar.CaptureScope); return true; + + ILInstruction GetClosureInitializer(ILVariable variable) + { + var type = UnwrapByRef(variable.Type).GetDefinition(); + if (type == null) + return null; + if (variable.Kind == VariableKind.Parameter) + return null; + if (type.Kind == TypeKind.Struct) + return GetStatement(variable.AddressInstructions.OrderBy(i => i.StartILOffset).First()); + else + return (StLoc)variable.StoreInstructions[0]; + } } bool IsInNestedLocalFunction(BlockContainer declarationScope, ILFunction function) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index 894194b43..ee619d6a8 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -60,10 +60,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms // for the base ctor call) or a compiler-generated delegate method, which might be used in a query expression. return false; } - // Do not try to transform display class usages or delegate construction. + // Do not try to transform delegate construction. // DelegateConstruction transform cannot deal with this. - if (TransformDisplayClassUsage.IsSimpleDisplayClass(newObjInst.Method.DeclaringType)) - return false; if (DelegateConstruction.IsDelegateConstruction(newObjInst) || TransformDisplayClassUsage.IsPotentialClosure(context, newObjInst)) return false; // Cannot build a collection/object initializer attached to an AnonymousTypeCreateExpression:s @@ -260,7 +258,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms public override string ToString() => $"[{Member}, {Indices}]"; public static (AccessPathKind Kind, List Path, List Values, ILVariable Target) GetAccessPath( - ILInstruction instruction, IType rootType, DecompilerSettings settings, + ILInstruction instruction, IType rootType, DecompilerSettings settings = null, CSharpTypeResolveContext resolveContext = null, Dictionary possibleIndexVariables = null) { @@ -282,7 +280,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!CanBeUsedInInitializer(property, resolveContext, kind, path)) goto default; var isGetter = method.Equals(property?.Getter); var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray(); - if (indices.Length > 0 && !settings.DictionaryInitializers) goto default; + if (indices.Length > 0 && settings?.DictionaryInitializers == false) goto default; if (possibleIndexVariables != null) { // Mark all index variables as used foreach (var index in indices.OfType()) { @@ -373,7 +371,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!"Add".Equals(method.Name, StringComparison.Ordinal) || arguments.Count == 0) return false; if (method.IsExtensionMethod) - return settings.ExtensionMethodsInCollectionInitializers + return settings?.ExtensionMethodsInCollectionInitializers != false && CSharp.Transforms.IntroduceExtensionMethods.CanTransformToExtensionMethodCall(method, resolveContext, ignoreTypeArguments: true); var targetType = GetReturnTypeFromInstruction(arguments[0]) ?? rootType; if (targetType == null) diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs index 5592a588d..3f11ee01d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformDisplayClassUsage.cs @@ -20,6 +20,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection.Metadata; +using ICSharpCode.Decompiler.Disassembler; +using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; @@ -28,135 +31,471 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Transforms closure fields to local variables. /// - /// This is a post-processing step of , and . + /// This is a post-processing step of , + /// and . + /// + /// In general we can perform SROA (scalar replacement of aggregates) on any variable that + /// satisfies the following conditions: + /// 1) It is initialized by an empty/default constructor call. + /// 2) The variable is never passed to another method. + /// 3) The variable is never the target of an invocation. + /// + /// Note that 2) and 3) apply because declarations and uses of lambdas and local functions + /// are already transformed by the time this transform is applied. /// class TransformDisplayClassUsage : ILVisitor, IILTransform { + class VariableToDeclare + { + private readonly DisplayClass container; + private readonly IField field; + private ILVariable declaredVariable; + + public string Name => field.Name; + + public bool CanPropagate { get; private set; } + + public HashSet Initializers { get; } = new HashSet(); + + public VariableToDeclare(DisplayClass container, IField field, ILVariable declaredVariable = null) + { + this.container = container; + this.field = field; + this.declaredVariable = declaredVariable; + + Debug.Assert(declaredVariable == null || declaredVariable.StateMachineField == field); + } + + public void Propagate(ILVariable variable) + { + Debug.Assert(declaredVariable == null || (variable == null && declaredVariable.StateMachineField == null)); + this.declaredVariable = variable; + this.CanPropagate = variable != null; + } + + public ILVariable GetOrDeclare() + { + if (declaredVariable != null) + return declaredVariable; + declaredVariable = container.Variable.Function.RegisterVariable(VariableKind.Local, field.Type, field.Name); + declaredVariable.HasInitialValue = true; + declaredVariable.CaptureScope = container.CaptureScope; + return declaredVariable; + } + } + + [DebuggerDisplay("[DisplayClass {Variable} : {Type}]")] class DisplayClass { - public bool IsMono; - public ILInstruction Initializer; - public ILVariable Variable; - public ITypeDefinition Definition; - public Dictionary Variables; + public readonly ILVariable Variable; + public readonly ITypeDefinition Type; + public readonly Dictionary VariablesToDeclare; public BlockContainer CaptureScope; - public ILFunction DeclaringFunction; + public ILInstruction Initializer; + + public DisplayClass(ILVariable variable, ITypeDefinition type) + { + Variable = variable; + Type = type; + VariablesToDeclare = new Dictionary(); + } } ILTransformContext context; + ITypeResolveContext decompilationContext; readonly Dictionary displayClasses = new Dictionary(); - readonly List instructionsToRemove = new List(); - readonly MultiDictionary fieldAssignmentsWithVariableValue = new MultiDictionary(); - public void Run(ILFunction function, ILTransformContext context) + void IILTransform.Run(ILFunction function, ILTransformContext context) { + if (this.context != null) + throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); try { - if (this.context != null) - throw new InvalidOperationException("Reentrancy in " + nameof(TransformDisplayClassUsage)); this.context = context; - var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); - // Traverse nested functions in post-order: - // Inner functions are transformed before outer functions - foreach (var f in function.Descendants.OfType()) { - foreach (var v in f.Variables.ToArray()) { - if (context.Settings.YieldReturn && HandleMonoStateMachine(function, v, decompilationContext, f)) - continue; - if ((context.Settings.AnonymousMethods || context.Settings.ExpressionTrees) && IsClosure(context, v, out ITypeDefinition closureType, out var inst)) { - if (!CanRemoveAllReferencesTo(context, v)) - continue; - if (inst is StObj || inst is StLoc) - instructionsToRemove.Add(inst); - AddOrUpdateDisplayClass(f, v, closureType, inst, localFunctionClosureParameter: false); - continue; + this.decompilationContext = new SimpleTypeResolveContext(context.Function.Method); + AnalyzeFunction(function); + Transform(function); + } finally { + ClearState(); + } + } + + void ClearState() + { + displayClasses.Clear(); + this.decompilationContext = null; + this.context = null; + } + + void AnalyzeFunction(ILFunction function) + { + void VisitFunction(ILFunction f) + { + foreach (var v in f.Variables.ToArray()) { + var result = AnalyzeVariable(v); + if (result == null || displayClasses.ContainsKey(result.Variable)) + continue; + context.Step($"Detected display-class {result.Variable}", result.Initializer ?? f.Body); + displayClasses.Add(result.Variable, result); + } + } + + void VisitChildren(ILInstruction inst) + { + foreach (var child in inst.Children) { + Visit(child); + } + } + + void Visit(ILInstruction inst) + { + switch (inst) { + case ILFunction f: + VisitFunction(f); + VisitChildren(inst); + break; + default: + VisitChildren(inst); + break; + } + } + + Visit(function); + + foreach (var (v, displayClass) in displayClasses.ToArray()) { + if (!ValidateDisplayClassUses(v, displayClass)) + displayClasses.Remove(v); + } + + foreach (var displayClass in displayClasses.Values) { + foreach (var v in displayClass.VariablesToDeclare.Values) { + if (v.CanPropagate) { + var variableToPropagate = v.GetOrDeclare(); + if (variableToPropagate.Kind != VariableKind.Parameter && !displayClasses.ContainsKey(variableToPropagate)) + v.Propagate(null); + } + } + } + } + + bool ValidateDisplayClassUses(ILVariable v, DisplayClass displayClass) + { + Debug.Assert(v == displayClass.Variable); + foreach (var ldloc in v.LoadInstructions) { + if (!ValidateUse(displayClass, ldloc)) + return false; + } + foreach (var ldloca in v.AddressInstructions) { + if (!ValidateUse(displayClass, ldloca)) + return false; + } + return true; + + bool ValidateUse(DisplayClass container, ILInstruction use) + { + IField field; + switch (use.Parent) { + case LdFlda ldflda when ldflda.MatchLdFlda(out var target, out field) && target == use: + var keyField = (IField)field.MemberDefinition; + if (!container.VariablesToDeclare.TryGetValue(keyField, out VariableToDeclare variable) || variable == null) { + variable = AddVariable(container, null, field); } - if (context.Settings.LocalFunctions && f.Kind == ILFunctionKind.LocalFunction && v.Kind == VariableKind.Parameter && v.Index > -1 && f.Method.Parameters[v.Index.Value] is IParameter p && LocalFunctionDecompiler.IsClosureParameter(p, decompilationContext)) { - AddOrUpdateDisplayClass(f, v, ((ByReferenceType)p.Type).ElementType.GetDefinition(), f.Body, localFunctionClosureParameter: true); - continue; + container.VariablesToDeclare[keyField] = variable; + return true; + case StObj stobj when stobj.MatchStObj(out var target, out ILInstruction value, out _) && value == use: + if (target.MatchLdFlda(out var load, out field) && load.MatchLdLocRef(out var otherVariable) && displayClasses.TryGetValue(otherVariable, out var otherDisplayClass)) { + if (otherDisplayClass.VariablesToDeclare.TryGetValue((IField)field.MemberDefinition, out var declaredVar)) + return declaredVar.CanPropagate; } - AnalyzeUseSites(v); + return false; + default: + return false; + } + } + } + + private DisplayClass AnalyzeVariable(ILVariable v) + { + switch (v.Kind) { + case VariableKind.Parameter: + if (context.Settings.YieldReturn && v.Function.StateMachineCompiledWithMono && v.IsThis()) + return HandleMonoStateMachine(v.Function, v); + return null; + case VariableKind.StackSlot: + case VariableKind.Local: + case VariableKind.DisplayClassLocal: + return DetectDisplayClass(v); + case VariableKind.InitializerTarget: + return DetectDisplayClassInitializer(v); + default: + return null; + } + } + + DisplayClass DetectDisplayClass(ILVariable v) + { + ITypeDefinition definition; + if (v.Kind != VariableKind.StackSlot) { + definition = v.Type.GetDefinition(); + } else if (v.StoreInstructions.Count > 0 && v.StoreInstructions[0] is StLoc stloc) { + definition = stloc.Value.InferType(context.TypeSystem).GetDefinition(); + } else { + definition = null; + } + if (!ValidateDisplayClassDefinition(definition)) + return null; + DisplayClass result; + switch (definition.Kind) { + case TypeKind.Class: + if (!v.IsSingleDefinition) + return null; + if (!(v.StoreInstructions.SingleOrDefault() is StLoc stloc)) + return null; + if (stloc.Value is NewObj newObj && ValidateConstructor(newObj.Method)) { + result = new DisplayClass(v, definition) { + CaptureScope = v.CaptureScope, + Initializer = stloc + }; + } else { + return null; } + + HandleInitBlock(stloc.Parent as Block, stloc.ChildIndex + 1, result, result.Variable); + break; + case TypeKind.Struct: + if (!v.HasInitialValue) + return null; + if (v.StoreCount != 1) + return null; + Debug.Assert(v.StoreInstructions.Count == 0); + result = new DisplayClass(v, definition) { CaptureScope = v.CaptureScope }; + HandleInitBlock(FindDisplayStructInitBlock(v), 0, result, result.Variable); + break; + default: + return null; + } + + if (IsMonoNestedCaptureScope(definition)) { + result.CaptureScope = null; + } + return result; + } + + void HandleInitBlock(Block initBlock, int startIndex, DisplayClass result, ILVariable targetVariable) + { + if (initBlock == null) + return; + for (int i = startIndex; i < initBlock.Instructions.Count; i++) { + var init = initBlock.Instructions[i]; + if (!init.MatchStFld(out var target, out var field, out _)) + break; + if (!target.MatchLdLocRef(targetVariable)) + break; + if (result.VariablesToDeclare.ContainsKey((IField)field.MemberDefinition)) + break; + var variable = AddVariable(result, (StObj)init, field); + result.VariablesToDeclare[(IField)field.MemberDefinition] = variable; + } + } + + private Block FindDisplayStructInitBlock(ILVariable v) + { + var root = v.Function.Body; + return Visit(root)?.Ancestors.OfType().FirstOrDefault(); + + // Try to find a common ancestor of all uses of the variable v. + ILInstruction Visit(ILInstruction inst) + { + switch (inst) { + case LdLoc l when l.Variable == v: + return l; + case StLoc s when s.Variable == v: + return s; + case LdLoca la when la.Variable == v: + return la; + default: + return VisitChildren(inst); } - VisitILFunction(function); - if (instructionsToRemove.Count > 0) { - context.Step($"Remove instructions", function); - foreach (var store in instructionsToRemove) { - if (store.Parent is Block containingBlock) - containingBlock.Instructions.Remove(store); + } + + ILInstruction VisitChildren(ILInstruction inst) + { + // Visit all children of the instruction + ILInstruction result = null; + foreach (var child in inst.Children) { + var newResult = Visit(child); + // As soon as there is a second use of v in this sub-tree, + // we can skip all other children and just return this node. + if (result == null) { + result = newResult; + } else if (newResult != null) { + return inst; } } - foreach (var f in TreeTraversal.PostOrder(function, f => f.LocalFunctions)) - RemoveDeadVariableInit.ResetHasInitialValueFlag(f, context); - } finally { - instructionsToRemove.Clear(); - displayClasses.Clear(); - fieldAssignmentsWithVariableValue.Clear(); - this.context = null; + // returns null, if v is not used in this sub-tree; + // returns a descendant of inst, if it is the only use of v in this sub-tree. + return result; } } - private bool CanRemoveAllReferencesTo(ILTransformContext context, ILVariable v) + DisplayClass DetectDisplayClassInitializer(ILVariable v) + { + if (v.StoreInstructions.Count != 1 || !(v.StoreInstructions[0] is StLoc store && store.Parent is Block initializerBlock && initializerBlock.Kind == BlockKind.ObjectInitializer)) + return null; + if (!(store.Value is NewObj newObj)) + return null; + var definition = newObj.Method.DeclaringType.GetDefinition(); + if (!ValidateDisplayClassDefinition(definition)) + return null; + if (!ValidateConstructor(newObj.Method)) + return null; + if (!initializerBlock.Parent.MatchStLoc(out var referenceVariable)) + return null; + if (!referenceVariable.IsSingleDefinition) + return null; + if (!(referenceVariable.StoreInstructions.SingleOrDefault() is StLoc)) + return null; + var result = new DisplayClass(referenceVariable, definition) { + CaptureScope = referenceVariable.CaptureScope, + Initializer = initializerBlock.Parent + }; + HandleInitBlock(initializerBlock, 1, result, v); + return result; + } + + private bool ValidateDisplayClassDefinition(ITypeDefinition definition) { - foreach (var use in v.LoadInstructions) { - // we only accept stloc, stobj/ldobj and ld(s)flda instructions, - // as these are required by all patterns this transform understands. - if (!(use.Parent is StLoc || use.Parent is LdFlda || use.Parent is LdsFlda || use.Parent is StObj || use.Parent is LdObj)) { + if (definition == null) + return false; + if (definition.ParentModule.PEFile != context.PEFile) + return false; + if (!context.Settings.AggressiveScalarReplacementOfAggregates) { + if (definition.DeclaringTypeDefinition == null) return false; - } - if (use.Parent.MatchStLoc(out var targetVar) && !IsClosure(context, targetVar, out _, out _)) { + if (!IsPotentialClosure(context, definition)) return false; - } } return true; } - private void AnalyzeUseSites(ILVariable v) + private bool ValidateConstructor(IMethod method) { - foreach (var use in v.LoadInstructions) { - if (!(use.Parent?.Parent is Block)) - continue; - if (use.Parent.MatchStFld(out _, out var f, out var value) && value == use) { - fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent); - } - if (use.Parent.MatchStsFld(out f, out value) && value == use) { - fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent); - } + try { + if (method.Parameters.Count != 0) + return false; + var handle = (MethodDefinitionHandle)method.MetadataToken; + var module = (MetadataModule)method.ParentModule; + var file = module.PEFile; + if (handle.IsNil || file != context.PEFile) + return false; + var def = file.Metadata.GetMethodDefinition(handle); + if (def.RelativeVirtualAddress == 0) + return false; + var body = file.Reader.GetMethodBody(def.RelativeVirtualAddress); + if (!body.LocalSignature.IsNil || body.ExceptionRegions.Length != 0) + return false; + var reader = body.GetILReader(); + if (reader.Length < 7) + return false; + // IL_0000: ldarg.0 + // IL_0001: call instance void [mscorlib]System.Object::.ctor() + // IL_0006: ret + if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Ldarg_0) + return false; + if (DecodeOpCodeSkipNop(ref reader) != ILOpCode.Call) + return false; + var baseCtorHandle = MetadataTokenHelpers.EntityHandleOrNil(reader.ReadInt32()); + if (baseCtorHandle.IsNil) + return false; + var objectCtor = module.ResolveMethod(baseCtorHandle, new TypeSystem.GenericContext()); + if (!objectCtor.DeclaringType.IsKnownType(KnownTypeCode.Object)) + return false; + if (!objectCtor.IsConstructor || objectCtor.Parameters.Count != 0) + return false; + return DecodeOpCodeSkipNop(ref reader) == ILOpCode.Ret; + } catch (BadImageFormatException) { + return false; } - foreach (var use in v.AddressInstructions) { - if (!(use.Parent?.Parent is Block)) - continue; - if (use.Parent.MatchStFld(out _, out var f, out var value) && value == use) { - fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent); - } - if (use.Parent.MatchStsFld(out f, out value) && value == use) { - fieldAssignmentsWithVariableValue.Add(f, (StObj)use.Parent); - } + } + + ILOpCode DecodeOpCodeSkipNop(ref BlobReader reader) + { + ILOpCode code; + do { + code = reader.DecodeOpCode(); + } while (code == ILOpCode.Nop); + return code; + } + + VariableToDeclare AddVariable(DisplayClass result, StObj statement, IField field) + { + VariableToDeclare variable = new VariableToDeclare(result, field); + if (statement != null) { + variable.Propagate(ResolveVariableToPropagate(statement.Value, field.Type)); + variable.Initializers.Add(statement); } + return variable; } - private void AddOrUpdateDisplayClass(ILFunction f, ILVariable v, ITypeDefinition closureType, ILInstruction inst, bool localFunctionClosureParameter) + /// + /// Resolves references to variables that can be propagated. + /// If a value does not match either LdLoc or a LdObj LdLdFlda* LdLoc chain, null is returned. + /// The if any of the variables/fields in the chain cannot be propagated, null is returned. + /// + ILVariable ResolveVariableToPropagate(ILInstruction value, IType expectedType = null) { - var displayClass = displayClasses.Values.FirstOrDefault(c => c.Definition == closureType); - // TODO : figure out whether it is a mono compiled closure, without relying on the type name - bool isMono = f.StateMachineCompiledWithMono || closureType.Name.Contains("AnonStorey"); - if (displayClass == null) { - displayClasses.Add(v, new DisplayClass { - IsMono = isMono, - Initializer = inst, - Variable = v, - Definition = closureType, - Variables = new Dictionary(), - CaptureScope = (isMono && IsMonoNestedCaptureScope(closureType)) || localFunctionClosureParameter ? null : v.CaptureScope, - DeclaringFunction = localFunctionClosureParameter ? f.DeclarationScope.Ancestors.OfType().First() : f - }); - } else { - if (displayClass.CaptureScope == null && !localFunctionClosureParameter) - displayClass.CaptureScope = isMono && IsMonoNestedCaptureScope(closureType) ? null : v.CaptureScope; - if (displayClass.CaptureScope != null && !localFunctionClosureParameter) { - displayClass.DeclaringFunction = displayClass.CaptureScope.Ancestors.OfType().First(); - } - displayClass.Variable = v; - displayClass.Initializer = inst; - displayClasses.Add(v, displayClass); + ILVariable v; + switch (value) { + case LdLoc load when load.Variable.StateMachineField == null: + v = load.Variable; + if (v.Kind == VariableKind.Parameter) { + if (v.LoadCount != 1 && !v.IsThis()) { + // If the variable is a parameter and it is used elsewhere, we cannot propagate it. + // "dc.field = v; dc.field.mutate(); use(v);" cannot turn to "v.mutate(); use(v)" + return null; + } + } else { + // Non-parameter propagation will later be checked, and will only be allowed for display classes + if (v.Type.IsReferenceType != true) { + // don't allow propagation for display structs (as used with local functions) + return null; + } + } + if (!v.IsSingleDefinition) { + // "dc.field = v; v = 42; use(dc.field)" cannot turn to "v = 42; use(v);" + return null; + } + if (!(expectedType == null || v.Kind == VariableKind.StackSlot || v.Type.Equals(expectedType))) + return null; + return v; + case LdObj ldfld: + DisplayClass currentDisplayClass = null; + foreach (var item in ldfld.Target.Descendants) { + if (IsDisplayClassLoad(item, out v)) { + if (!displayClasses.TryGetValue(v, out currentDisplayClass)) + return null; + } + if (currentDisplayClass == null) + return null; + if (item is LdFlda ldf && currentDisplayClass.VariablesToDeclare.TryGetValue((IField)ldf.Field.MemberDefinition, out var vd)) { + if (!vd.CanPropagate) + return null; + if (!displayClasses.TryGetValue(vd.GetOrDeclare(), out currentDisplayClass)) + return null; + } + } + return currentDisplayClass.Variable; + default: + return null; + } + } + + private void Transform(ILFunction function) + { + VisitILFunction(function); + context.Step($"ResetHasInitialValueFlag", function); + foreach (var f in TreeTraversal.PostOrder(function, f => f.LocalFunctions)) { + RemoveDeadVariableInit.ResetHasInitialValueFlag(f, context); + f.CapturedVariables.RemoveWhere(v => v.IsDead); } } @@ -190,6 +529,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms bool IsMonoNestedCaptureScope(ITypeDefinition closureType) { + if (!closureType.Name.Contains("AnonStorey")) + return false; var decompilationContext = new SimpleTypeResolveContext(context.Function.Method); return closureType.Fields.Any(f => IsPotentialClosure(decompilationContext.CurrentTypeDefinition, f.ReturnType.GetDefinition())); } @@ -198,45 +539,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// mcs likes to optimize closures in yield state machines away by moving the captured variables' fields into the state machine type, /// We construct a that spans the whole method body. /// - bool HandleMonoStateMachine(ILFunction currentFunction, ILVariable thisVariable, SimpleTypeResolveContext decompilationContext, ILFunction nestedFunction) + DisplayClass HandleMonoStateMachine(ILFunction function, ILVariable thisVariable) { - if (!(nestedFunction.StateMachineCompiledWithMono && thisVariable.IsThis())) - return false; + if (!(function.StateMachineCompiledWithMono && thisVariable.IsThis())) + return null; // Special case for Mono-compiled yield state machines ITypeDefinition closureType = thisVariable.Type.GetDefinition(); if (!(closureType != decompilationContext.CurrentTypeDefinition && IsPotentialClosure(decompilationContext.CurrentTypeDefinition, closureType, allowTypeImplementingInterfaces: true))) - return false; + return null; - var displayClass = new DisplayClass { - IsMono = true, - Initializer = nestedFunction.Body, - Variable = thisVariable, - Definition = thisVariable.Type.GetDefinition(), - Variables = new Dictionary(), - CaptureScope = (BlockContainer)nestedFunction.Body - }; - displayClasses.Add(thisVariable, displayClass); - foreach (var stateMachineVariable in nestedFunction.Variables) { - if (stateMachineVariable.StateMachineField == null || displayClass.Variables.ContainsKey(stateMachineVariable.StateMachineField)) + var displayClass = new DisplayClass(thisVariable, thisVariable.Type.GetDefinition()); + displayClass.CaptureScope = (BlockContainer)function.Body; + foreach (var stateMachineVariable in function.Variables) { + if (stateMachineVariable.StateMachineField == null || displayClass.VariablesToDeclare.ContainsKey(stateMachineVariable.StateMachineField)) continue; - displayClass.Variables.Add(stateMachineVariable.StateMachineField, stateMachineVariable); + VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, stateMachineVariable.StateMachineField, stateMachineVariable); + displayClass.VariablesToDeclare.Add(stateMachineVariable.StateMachineField, variableToDeclare); } - if (!currentFunction.Method.IsStatic && FindThisField(out var thisField)) { - var thisVar = currentFunction.Variables + if (!function.Method.IsStatic && FindThisField(out var thisField)) { + var thisVar = function.Variables .FirstOrDefault(t => t.IsThis() && t.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition); if (thisVar == null) { thisVar = new ILVariable(VariableKind.Parameter, decompilationContext.CurrentTypeDefinition, -1) { Name = "this" }; - currentFunction.Variables.Add(thisVar); + function.Variables.Add(thisVar); } - displayClass.Variables.Add(thisField, thisVar); + VariableToDeclare variableToDeclare = new VariableToDeclare(displayClass, thisField, thisVar); + displayClass.VariablesToDeclare.Add(thisField, variableToDeclare); } - return true; + return displayClass; bool FindThisField(out IField foundField) { foundField = null; - foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.Variables.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) { + foreach (var field in closureType.GetFields(f2 => !f2.IsStatic && !displayClass.VariablesToDeclare.ContainsKey(f2) && f2.Type.GetDefinition() == decompilationContext.CurrentTypeDefinition)) { thisField = field; return true; } @@ -244,15 +580,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } - internal static bool IsSimpleDisplayClass(IType type) - { - if (!type.HasGeneratedName() || (!type.Name.Contains("DisplayClass") && !type.Name.Contains("AnonStorey"))) - return false; - if (type.DirectBaseTypes.Any(t => !t.IsKnownType(KnownTypeCode.Object))) - return false; - return true; - } - internal static bool IsPotentialClosure(ILTransformContext context, NewObj inst) { var decompilationContext = new SimpleTypeResolveContext(context.Function.Ancestors.OfType().Last().Method); @@ -292,63 +619,87 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } - ILFunction currentFunction; + readonly Stack currentFunctions = new Stack(); protected internal override void VisitILFunction(ILFunction function) { - var oldFunction = this.currentFunction; + context.StepStartGroup("Visit " + function.Name); try { - this.currentFunction = function; + this.currentFunctions.Push(function); base.VisitILFunction(function); } finally { - this.currentFunction = oldFunction; + this.currentFunctions.Pop(); + context.StepEndGroup(keepIfEmpty: true); } } protected override void Default(ILInstruction inst) { - foreach (var child in inst.Children) { + ILInstruction next; + for (var child = inst.Children.FirstOrDefault(); child != null; child = next) { + next = child.GetNextSibling(); child.AcceptVisitor(this); } } protected internal override void VisitStLoc(StLoc inst) { - base.VisitStLoc(inst); - - if (inst.Parent is Block && inst.Variable.IsSingleDefinition) { - if (inst.Variable.Kind == VariableKind.Local && inst.Variable.LoadCount == 0 && inst.Value is StLoc) { - context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); - inst.ReplaceWith(inst.Value); + DisplayClass displayClass; + if (inst.Parent is Block parentBlock && inst.Variable.IsSingleDefinition) { + if ((inst.Variable.Kind == VariableKind.Local || inst.Variable.Kind == VariableKind.StackSlot) && inst.Variable.LoadCount == 0) { + // traverse pre-order, so that we do not have to deal with more special cases afterwards + base.VisitStLoc(inst); + if (inst.Value is StLoc || inst.Value is CompoundAssignmentInstruction) { + context.Step($"Remove unused variable assignment {inst.Variable.Name}", inst); + inst.ReplaceWith(inst.Value); + } return; } - if (inst.Value.MatchLdLoc(out var displayClassVariable) && displayClasses.TryGetValue(displayClassVariable, out var displayClass)) { - context.Step($"Found copy-assignment of display-class variable {displayClassVariable.Name}", inst); - displayClasses.Add(inst.Variable, displayClass); - instructionsToRemove.Add(inst); + if (displayClasses.TryGetValue(inst.Variable, out displayClass) && displayClass.Initializer == inst) { + // inline contents of object initializer block + if (inst.Value is Block initBlock && initBlock.Kind == BlockKind.ObjectInitializer) { + context.Step($"Remove initializer of {inst.Variable.Name}", inst); + for (int i = 1; i < initBlock.Instructions.Count; i++) { + var stobj = (StObj)initBlock.Instructions[i]; + var variable = displayClass.VariablesToDeclare[(IField)((LdFlda)stobj.Target).Field.MemberDefinition]; + parentBlock.Instructions.Insert(inst.ChildIndex + i, new StLoc(variable.GetOrDeclare(), stobj.Value).WithILRange(stobj)); + } + } + context.Step($"Remove initializer of {inst.Variable.Name}", inst); + parentBlock.Instructions.Remove(inst); return; } + if (inst.Value is LdLoc || inst.Value is LdObj) { + // in some cases (e.g. if inlining fails), there can be a reference to a display class in a stack slot, + // in that case it is necessary to resolve the reference and iff it can be propagated, replace all loads + // of the single-definition. + var referencedDisplayClass = ResolveVariableToPropagate(inst.Value); + if (referencedDisplayClass != null && displayClasses.TryGetValue(referencedDisplayClass, out _)) { + context.Step($"Propagate reference to {referencedDisplayClass.Name} in {inst.Variable}", inst); + foreach (var ld in inst.Variable.LoadInstructions.ToArray()) { + ld.ReplaceWith(new LdLoc(referencedDisplayClass).WithILRange(ld)); + } + parentBlock.Instructions.Remove(inst); + return; + } + } } + base.VisitStLoc(inst); } protected internal override void VisitStObj(StObj inst) { - inst.Value.AcceptVisitor(this); - if (inst.Parent is Block) { - if (IsParameterAssignment(inst, out var displayClass, out var field, out var parameter)) { - context.Step($"Detected parameter assignment {parameter.Name}", inst); - displayClass.Variables.Add((IField)field.MemberDefinition, parameter); - instructionsToRemove.Add(inst); - return; - } - if (IsDisplayClassAssignment(inst, out displayClass, out field, out var variable)) { - context.Step($"Detected display-class assignment {variable.Name}", inst); - displayClass.Variables.Add((IField)field.MemberDefinition, variable); - instructionsToRemove.Add(inst); - return; + if (IsDisplayClassFieldAccess(inst.Target, out var v, out var displayClass, out var field)) { + VariableToDeclare vd = displayClass.VariablesToDeclare[(IField)field.MemberDefinition]; + if (vd.CanPropagate && vd.Initializers.Contains(inst)) { + if (inst.Parent is Block containingBlock) { + context.Step($"Remove initializer of {v.Name}.{vd.Name} due to propagation", inst); + containingBlock.Instructions.Remove(inst); + return; + } } } - inst.Target.AcceptVisitor(this); + base.VisitStObj(inst); EarlyExpressionTransforms.StObjToStLoc(inst, context); } @@ -366,36 +717,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } - private bool IsDisplayClassAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable variable) - { - variable = null; - if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field)) - return false; - if (!(inst.Value.MatchLdLoc(out var v) && displayClasses.ContainsKey(v))) - return false; - if (displayClassVar.Function != currentFunction) - return false; - variable = v; - return true; - } - - private bool IsParameterAssignment(StObj inst, out DisplayClass displayClass, out IField field, out ILVariable parameter) - { - parameter = null; - if (!IsDisplayClassFieldAccess(inst.Target, out var displayClassVar, out displayClass, out field)) - return false; - if (fieldAssignmentsWithVariableValue[field].Count != 1) - return false; - if (!(inst.Value.MatchLdLoc(out var v) && v.Kind == VariableKind.Parameter && v.Function == currentFunction && v.Type.Equals(field.Type))) - return false; - if (displayClass.Variables.ContainsKey((IField)field.MemberDefinition)) - return false; - if (displayClassVar.Function != currentFunction) - return false; - parameter = v; - return true; - } - private bool IsDisplayClassFieldAccess(ILInstruction inst, out ILVariable displayClassVar, out DisplayClass displayClass, out IField field) { displayClass = null; @@ -414,23 +735,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms // Get display class info if (!IsDisplayClassFieldAccess(inst, out _, out DisplayClass displayClass, out IField field)) return; - // We want the specialized version, so that display-class type parameters are - // substituted with the type parameters from the use-site. - var fieldType = field.Type; - // However, use the unspecialized member definition to make reference comparisons in dictionary possible. - field = (IField)field.MemberDefinition; - if (!displayClass.Variables.TryGetValue(field, out var v)) { - context.Step($"Introduce captured variable for {field.FullName}", inst); - // Introduce a fresh variable for the display class field. - Debug.Assert(displayClass.Definition == field.DeclaringTypeDefinition); - v = displayClass.DeclaringFunction.RegisterVariable(VariableKind.Local, fieldType, field.Name); - v.HasInitialValue = true; - v.CaptureScope = displayClass.CaptureScope; - inst.ReplaceWith(new LdLoca(v).WithILRange(inst)); - displayClass.Variables.Add(field, v); - } else { - context.Step($"Reuse captured variable {v.Name} for {field.FullName}", inst); - inst.ReplaceWith(new LdLoca(v).WithILRange(inst)); + var keyField = (IField)field.MemberDefinition; + var v = displayClass.VariablesToDeclare[keyField]; + context.Step($"Replace {field.Name} with captured variable {v.Name}", inst); + ILVariable variable = v.GetOrDeclare(); + inst.ReplaceWith(new LdLoca(variable).WithILRange(inst)); + // add captured variable to all descendant functions from the declaring function to this use-site function + foreach (var f in currentFunctions) { + if (f == variable.Function) + break; + f.CapturedVariables.Add(variable); } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs index 2ed286a3b..14d847ba9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformExpressionTrees.cs @@ -1229,6 +1229,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (ldloc.Variable.CaptureScope == null) { ldloc.Variable.CaptureScope = BlockContainer.FindClosestContainer(context); + var f = ldloc.Variable.CaptureScope.Ancestors.OfType().FirstOrDefault(); + if (f != null) { + f.CapturedVariables.Add(ldloc.Variable); + } } return ldloc; } else { diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 3f8e88ef2..50e640b22 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -576,6 +576,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Aggressively perform Scalar Replacement Of Aggregates (SROA). + /// + public static string DecompilerSettings_AggressiveScalarReplacementOfAggregates { + get { + return ResourceManager.GetString("DecompilerSettings.AggressiveScalarReplacementOfAggregates", resourceCulture); + } + } + /// /// Looks up a localized string similar to Allow extension 'Add' methods in collection initializer expressions. ///