diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 604b32919..6eb934819 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -84,6 +84,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 0e8c4ca29..279010c1c 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -109,6 +109,12 @@ namespace ICSharpCode.Decompiler.Tests Run(cscOptions: cscOptions); } + [Test] + public void Lock([ValueSource("defaultOptions")] CompilerOptions cscOptions) + { + Run(cscOptions: cscOptions); + } + [Test, Ignore("Not implemented")] public void LiftedOperators([ValueSource("defaultOptions")] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/Lock.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.cs similarity index 83% rename from ICSharpCode.Decompiler.Tests/Lock.cs rename to ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.cs index da5a59c74..dd475ceb6 100644 --- a/ICSharpCode.Decompiler.Tests/Lock.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.cs @@ -18,21 +18,22 @@ using System; -public class Lock +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { - public void LockThis() + public class Lock { - lock (this) + public void LockThis() { - Console.WriteLine(); + lock (this) { + Console.WriteLine(); + } } - } - public void LockOnType() - { - lock (typeof(Lock)) + public void LockOnType() { - Console.WriteLine(); + lock (typeof(Lock)) { + Console.WriteLine(); + } } } } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.il new file mode 100644 index 000000000..77d86c654 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.il @@ -0,0 +1,146 @@ + +// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.17929 +// Copyright (c) Microsoft Corporation. All rights reserved. + + + +// 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 '1c2baaro' +{ + .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. + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module '1c2baaro.dll' +// MVID: {5FC5B58D-AAD7-4436-A58F-6F3D75486C66} +.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 +// Image base: 0x00C60000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + extends [mscorlib]System.Object +{ + .method public hidebysig instance void + LockThis() cil managed + { + // Code size 42 (0x2a) + .maxstack 2 + .locals init (bool V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock V_1, + bool V_2) + IL_0000: nop + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + .try + { + IL_0003: ldarg.0 + IL_0004: dup + IL_0005: stloc.1 + IL_0006: ldloca.s V_0 + IL_0008: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_000d: nop + IL_000e: nop + IL_000f: call void [mscorlib]System.Console::WriteLine() + IL_0014: nop + IL_0015: nop + IL_0016: leave.s IL_0028 + + } // end .try + finally + { + IL_0018: ldloc.0 + IL_0019: ldc.i4.0 + IL_001a: ceq + IL_001c: stloc.2 + IL_001d: ldloc.2 + IL_001e: brtrue.s IL_0027 + + IL_0020: ldloc.1 + IL_0021: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_0026: nop + IL_0027: endfinally + } // end handler + IL_0028: nop + IL_0029: ret + } // end of method Lock::LockThis + + .method public hidebysig instance void + LockOnType() cil managed + { + // Code size 51 (0x33) + .maxstack 2 + .locals init (bool V_0, + class [mscorlib]System.Type V_1, + bool V_2) + IL_0000: nop + IL_0001: ldc.i4.0 + IL_0002: stloc.0 + .try + { + IL_0003: ldtoken ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + IL_0008: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_000d: dup + IL_000e: stloc.1 + IL_000f: ldloca.s V_0 + IL_0011: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_0016: nop + IL_0017: nop + IL_0018: call void [mscorlib]System.Console::WriteLine() + IL_001d: nop + IL_001e: nop + IL_001f: leave.s IL_0031 + + } // end .try + finally + { + IL_0021: ldloc.0 + IL_0022: ldc.i4.0 + IL_0023: ceq + IL_0025: stloc.2 + IL_0026: ldloc.2 + IL_0027: brtrue.s IL_0030 + + IL_0029: ldloc.1 + IL_002a: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_002f: nop + IL_0030: endfinally + } // end handler + IL_0031: nop + IL_0032: ret + } // end of method Lock::LockOnType + + .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 Lock::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** +// WARNING: Created Win32 resource file ../../../TestCases/Pretty\Lock.res diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.il new file mode 100644 index 000000000..6f30273b4 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.il @@ -0,0 +1,122 @@ + +// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.17929 +// Copyright (c) Microsoft Corporation. All rights reserved. + + + +// 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 titpuxma +{ + .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. + .permissionset reqmin + = {[mscorlib]System.Security.Permissions.SecurityPermissionAttribute = {property bool 'SkipVerification' = bool(true)}} + .hash algorithm 0x00008004 + .ver 0:0:0:0 +} +.module titpuxma.dll +// MVID: {8B90EB29-A139-4376-848E-C5D4D42952F1} +.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 +// Image base: 0x02AF0000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + extends [mscorlib]System.Object +{ + .method public hidebysig instance void + LockThis() cil managed + { + // Code size 30 (0x1e) + .maxstack 2 + .locals init (bool V_0, + class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock V_1) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + .try + { + IL_0002: ldarg.0 + IL_0003: dup + IL_0004: stloc.1 + IL_0005: ldloca.s V_0 + IL_0007: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_000c: call void [mscorlib]System.Console::WriteLine() + IL_0011: leave.s IL_001d + + } // end .try + finally + { + IL_0013: ldloc.0 + IL_0014: brfalse.s IL_001c + + IL_0016: ldloc.1 + IL_0017: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_001c: endfinally + } // end handler + IL_001d: ret + } // end of method Lock::LockThis + + .method public hidebysig instance void + LockOnType() cil managed + { + // Code size 39 (0x27) + .maxstack 2 + .locals init (bool V_0, + class [mscorlib]System.Type V_1) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + .try + { + IL_0002: ldtoken ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + IL_0007: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_000c: dup + IL_000d: stloc.1 + IL_000e: ldloca.s V_0 + IL_0010: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_0015: call void [mscorlib]System.Console::WriteLine() + IL_001a: leave.s IL_0026 + + } // end .try + finally + { + IL_001c: ldloc.0 + IL_001d: brfalse.s IL_0025 + + IL_001f: ldloc.1 + IL_0020: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_0025: endfinally + } // end handler + IL_0026: ret + } // end of method Lock::LockOnType + + .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 Lock::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** +// WARNING: Created Win32 resource file ../../../TestCases/Pretty\Lock.opt.res diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.roslyn.il new file mode 100644 index 000000000..44b40e105 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.opt.roslyn.il @@ -0,0 +1,125 @@ + +// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.17929 +// Copyright (c) Microsoft Corporation. All rights reserved. + + + +// 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 Lock +{ + .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 Lock.dll +// MVID: {5161B18E-7152-4B49-B4A8-67524BD74953} +.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 +// Image base: 0x00E10000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + extends [mscorlib]System.Object +{ + .method public hidebysig instance void + LockThis() cil managed + { + // Code size 30 (0x1e) + .maxstack 2 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock V_0, + bool V_1) + IL_0000: ldarg.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.0 + IL_0003: stloc.1 + .try + { + IL_0004: ldloc.0 + IL_0005: ldloca.s V_1 + IL_0007: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_000c: call void [mscorlib]System.Console::WriteLine() + IL_0011: leave.s IL_001d + + } // end .try + finally + { + IL_0013: ldloc.1 + IL_0014: brfalse.s IL_001c + + IL_0016: ldloc.0 + IL_0017: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_001c: endfinally + } // end handler + IL_001d: ret + } // end of method Lock::LockThis + + .method public hidebysig instance void + LockOnType() cil managed + { + // Code size 39 (0x27) + .maxstack 2 + .locals init (class [mscorlib]System.Type V_0, + bool V_1) + IL_0000: ldtoken ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_000a: stloc.0 + IL_000b: ldc.i4.0 + IL_000c: stloc.1 + .try + { + IL_000d: ldloc.0 + IL_000e: ldloca.s V_1 + IL_0010: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_0015: call void [mscorlib]System.Console::WriteLine() + IL_001a: leave.s IL_0026 + + } // end .try + finally + { + IL_001c: ldloc.1 + IL_001d: brfalse.s IL_0025 + + IL_001f: ldloc.0 + IL_0020: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_0025: endfinally + } // end handler + IL_0026: ret + } // end of method Lock::LockOnType + + .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 Lock::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.roslyn.il b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.roslyn.il new file mode 100644 index 000000000..8010d6325 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Lock.roslyn.il @@ -0,0 +1,138 @@ + +// Microsoft (R) .NET Framework IL Disassembler. Version 4.0.30319.17929 +// Copyright (c) Microsoft Corporation. All rights reserved. + + + +// 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 Lock +{ + .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 Lock.dll +// MVID: {3273D43A-61AF-4B6E-B6CF-5B3C9A7E807A} +.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 +// Image base: 0x029B0000 + + +// =============== CLASS MEMBERS DECLARATION =================== + +.class public auto ansi beforefieldinit ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + extends [mscorlib]System.Object +{ + .method public hidebysig instance void + LockThis() cil managed + { + // Code size 36 (0x24) + .maxstack 2 + .locals init (class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock V_0, + bool V_1) + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: stloc.0 + IL_0003: ldc.i4.0 + IL_0004: stloc.1 + .try + { + IL_0005: ldloc.0 + IL_0006: ldloca.s V_1 + IL_0008: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_000d: nop + IL_000e: nop + IL_000f: call void [mscorlib]System.Console::WriteLine() + IL_0014: nop + IL_0015: nop + IL_0016: leave.s IL_0023 + + } // end .try + finally + { + IL_0018: ldloc.1 + IL_0019: brfalse.s IL_0022 + + IL_001b: ldloc.0 + IL_001c: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_0021: nop + IL_0022: endfinally + } // end handler + IL_0023: ret + } // end of method Lock::LockThis + + .method public hidebysig instance void + LockOnType() cil managed + { + // Code size 45 (0x2d) + .maxstack 2 + .locals init (class [mscorlib]System.Type V_0, + bool V_1) + IL_0000: nop + IL_0001: ldtoken ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + IL_0006: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) + IL_000b: stloc.0 + IL_000c: ldc.i4.0 + IL_000d: stloc.1 + .try + { + IL_000e: ldloc.0 + IL_000f: ldloca.s V_1 + IL_0011: call void [mscorlib]System.Threading.Monitor::Enter(object, + bool&) + IL_0016: nop + IL_0017: nop + IL_0018: call void [mscorlib]System.Console::WriteLine() + IL_001d: nop + IL_001e: nop + IL_001f: leave.s IL_002c + + } // end .try + finally + { + IL_0021: ldloc.1 + IL_0022: brfalse.s IL_002b + + IL_0024: ldloc.0 + IL_0025: call void [mscorlib]System.Threading.Monitor::Exit(object) + IL_002a: nop + IL_002b: endfinally + } // end handler + IL_002c: ret + } // end of method Lock::LockOnType + + .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 Lock::.ctor + +} // end of class ICSharpCode.Decompiler.Tests.TestCases.Pretty.Lock + + +// ============================================================= + +// *********** DISASSEMBLY COMPLETE *********************** diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 2778e79d4..568236cab 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -101,6 +101,7 @@ namespace ICSharpCode.Decompiler.CSharp PostOrderTransforms = { //new UseExitPoints(), new ConditionDetection(), + new LockTransform(), // CachedDelegateInitialization must run after ConditionDetection and before/in LoopingBlockTransform // and must run before NullCoalescingTransform new CachedDelegateInitialization(), diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 2d7c8a224..b015d2338 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -245,7 +245,15 @@ namespace ICSharpCode.Decompiler.CSharp tryCatch.CatchClauses.Add(new CatchClause { Body = faultBlock }); return tryCatch; } - + + protected internal override Statement VisitLockInstruction(LockInstruction inst) + { + return new LockStatement { + Expression = exprBuilder.Translate(inst.OnExpression), + EmbeddedStatement = ConvertAsBlock(inst.Body) + }; + } + protected internal override Statement VisitPinnedRegion(PinnedRegion inst) { var fixedStmt = new FixedStatement(); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 9c718b350..e0c89cfcf 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -85,11 +85,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms result = TransformFor(expressionStatement); if (result != null) return result; - if (context.Settings.LockStatement) { - result = TransformLock(expressionStatement); - if (result != null) - return result; - } if (context.Settings.AutomaticProperties) { result = ReplaceBackingFieldUsage(expressionStatement); if (result != null) @@ -677,104 +672,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } #endregion - #region lock - static readonly AstNode lockFlagInitPattern = new ExpressionStatement( - new AssignmentExpression( - new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)), - new PrimitiveExpression(false) - )); - - static readonly AstNode lockTryCatchPattern = new TryCatchStatement { - TryBlock = new BlockStatement { - new OptionalNode(new VariableDeclarationStatement()).ToStatement(), - new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Enter"), - new AnyNode("enter"), - new DirectionExpression { - FieldDirection = FieldDirection.Ref, - Expression = new NamedNode("flag", new IdentifierExpression(Pattern.AnyString)) - }), - new Repeat(new AnyNode()).ToStatement() - }, - FinallyBlock = new BlockStatement { - new IfElseStatement { - Condition = new Backreference("flag"), - TrueStatement = new BlockStatement { - new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Exit"), new AnyNode("exit")) - } - } - }}; - - static readonly AstNode oldMonitorCallPattern = new ExpressionStatement( - new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Enter"), new AnyNode("enter")) - ); - - static readonly AstNode oldLockTryCatchPattern = new TryCatchStatement - { - TryBlock = new BlockStatement { - new Repeat(new AnyNode()).ToStatement() - }, - FinallyBlock = new BlockStatement { - new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Monitor)).ToType()), "Exit"), new AnyNode("exit")) - } - }; - - bool AnalyzeLockV2(ExpressionStatement node, out Expression enter, out Expression exit) - { - enter = null; - exit = null; - Match m1 = oldMonitorCallPattern.Match(node); - if (!m1.Success) return false; - Match m2 = oldLockTryCatchPattern.Match(node.NextSibling); - if (!m2.Success) return false; - enter = m1.Get("enter").Single(); - exit = m2.Get("exit").Single(); - return true; - } - - bool AnalyzeLockV4(ExpressionStatement node, out Expression enter, out Expression exit) - { - enter = null; - exit = null; - Match m1 = lockFlagInitPattern.Match(node); - if (!m1.Success) return false; - Match m2 = lockTryCatchPattern.Match(node.NextSibling); - if (!m2.Success) return false; - enter = m2.Get("enter").Single(); - exit = m2.Get("exit").Single(); - return m1.Get("variable").Single().Identifier == m2.Get("flag").Single().Identifier; - } - - public LockStatement TransformLock(ExpressionStatement node) - { - Expression enter, exit; - bool isV2 = AnalyzeLockV2(node, out enter, out exit); - if (isV2 || AnalyzeLockV4(node, out enter, out exit)) { - AstNode tryCatch = node.NextSibling; - if (!exit.IsMatch(enter)) { - // If exit and enter are not the same, then enter must be "exit = ..." - AssignmentExpression assign = enter as AssignmentExpression; - if (assign == null) - return null; - if (!exit.IsMatch(assign.Left)) - return null; - enter = assign.Right; - // TODO: verify that 'obj' variable can be removed - } - // TODO: verify that 'flag' variable can be removed - // transform the code into a lock statement: - LockStatement l = new LockStatement(); - l.Expression = enter.Detach(); - l.EmbeddedStatement = ((TryCatchStatement)tryCatch).TryBlock.Detach(); - if (!isV2) // Remove 'Enter()' call - ((BlockStatement)l.EmbeddedStatement).Statements.First().Remove(); - tryCatch.ReplaceWith(l); - node.Remove(); // remove flag variable - return l; - } - return null; - } - #endregion - #region switch on strings static readonly IfElseStatement switchOnStringPattern = new IfElseStatement { Condition = new BinaryOperatorExpression { diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 0bd21adb5..5067537a5 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -273,12 +273,14 @@ + + diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 7c1668c89..25a4e8f03 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -73,6 +73,8 @@ namespace ICSharpCode.Decompiler.IL TryFinally, /// Try-fault statement TryFault, + /// Lock statement + LockInstruction, /// Breakpoint instruction DebugBreak, /// Comparison. The inputs must be both integers; or both floats; or both object references. Object references can only be compared for equality or inequality. Floating-point comparisons evaluate to 0 (false) when an input is NaN, except for 'NaN != NaN' which evaluates to 1 (true). @@ -1621,6 +1623,99 @@ namespace ICSharpCode.Decompiler.IL } } namespace ICSharpCode.Decompiler.IL +{ + /// Lock statement + public sealed partial class LockInstruction : ILInstruction + { + public LockInstruction(ILInstruction onExpression, ILInstruction body) : base(OpCode.LockInstruction) + { + this.OnExpression = onExpression; + this.Body = body; + } + public static readonly SlotInfo OnExpressionSlot = new SlotInfo("OnExpression", canInlineInto: true); + ILInstruction onExpression; + public ILInstruction OnExpression { + get { return this.onExpression; } + set { + ValidateChild(value); + SetChildInstruction(ref this.onExpression, value, 0); + } + } + public static readonly SlotInfo BodySlot = new SlotInfo("Body"); + ILInstruction body; + public ILInstruction Body { + get { return this.body; } + set { + ValidateChild(value); + SetChildInstruction(ref this.body, value, 1); + } + } + protected sealed override int GetChildCount() + { + return 2; + } + protected sealed override ILInstruction GetChild(int index) + { + switch (index) { + case 0: + return this.onExpression; + case 1: + return this.body; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override void SetChild(int index, ILInstruction value) + { + switch (index) { + case 0: + this.OnExpression = value; + break; + case 1: + this.Body = value; + break; + default: + throw new IndexOutOfRangeException(); + } + } + protected sealed override SlotInfo GetChildSlot(int index) + { + switch (index) { + case 0: + return OnExpressionSlot; + case 1: + return BodySlot; + default: + throw new IndexOutOfRangeException(); + } + } + public sealed override ILInstruction Clone() + { + var clone = (LockInstruction)ShallowClone(); + clone.OnExpression = this.onExpression.Clone(); + clone.Body = this.body.Clone(); + return clone; + } + public override void AcceptVisitor(ILVisitor visitor) + { + visitor.VisitLockInstruction(this); + } + public override T AcceptVisitor(ILVisitor visitor) + { + return visitor.VisitLockInstruction(this); + } + public override T AcceptVisitor(ILVisitor visitor, C context) + { + return visitor.VisitLockInstruction(this, context); + } + protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) + { + var o = other as LockInstruction; + return o != null && this.onExpression.PerformMatch(o.onExpression, ref match) && this.body.PerformMatch(o.body, ref match); + } + } +} +namespace ICSharpCode.Decompiler.IL { /// Breakpoint instruction public sealed partial class DebugBreak : SimpleInstruction @@ -4188,6 +4283,10 @@ namespace ICSharpCode.Decompiler.IL { Default(inst); } + protected internal virtual void VisitLockInstruction(LockInstruction inst) + { + Default(inst); + } protected internal virtual void VisitDebugBreak(DebugBreak inst) { Default(inst); @@ -4462,6 +4561,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst); } + protected internal virtual T VisitLockInstruction(LockInstruction inst) + { + return Default(inst); + } protected internal virtual T VisitDebugBreak(DebugBreak inst) { return Default(inst); @@ -4736,6 +4839,10 @@ namespace ICSharpCode.Decompiler.IL { return Default(inst, context); } + protected internal virtual T VisitLockInstruction(LockInstruction inst, C context) + { + return Default(inst, context); + } protected internal virtual T VisitDebugBreak(DebugBreak inst, C context) { return Default(inst, context); @@ -4939,6 +5046,7 @@ namespace ICSharpCode.Decompiler.IL "try.catch.handler", "try.finally", "try.fault", + "lock", "debug.break", "comp", "call", @@ -5069,6 +5177,18 @@ namespace ICSharpCode.Decompiler.IL variable = default(ILVariable); return false; } + public bool MatchLockInstruction(out ILInstruction onExpression, out ILInstruction body) + { + var inst = this as LockInstruction; + if (inst != null) { + onExpression = inst.OnExpression; + body = inst.Body; + return true; + } + onExpression = default(ILInstruction); + body = default(ILInstruction); + return false; + } public bool MatchDebugBreak() { var inst = this as DebugBreak; diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 41efe3d84..2ff5d34df 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -116,6 +116,11 @@ BaseClass("TryInstruction"), CustomConstructor, CustomWriteTo, CustomComputeFlags, MatchCondition("TryBlock.PerformMatch(o.TryBlock, ref match)"), MatchCondition("faultBlock.PerformMatch(o.faultBlock, ref match)")), + new OpCode("lock", "Lock statement", CustomClassName("LockInstruction"), + CustomChildren(new [] { + new ArgumentInfo("onExpression"), + new ChildInfo("body") + }), CustomWriteTo, CustomComputeFlags), new OpCode("debug.break", "Breakpoint instruction", NoArguments, VoidResult, SideEffect), new OpCode("comp", "Comparison. The inputs must be both integers; or both floats; or both object references. " diff --git a/ICSharpCode.Decompiler/IL/Instructions/LockInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/LockInstruction.cs new file mode 100644 index 000000000..815a4393d --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Instructions/LockInstruction.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.Decompiler.IL +{ + partial class LockInstruction + { + protected override InstructionFlags ComputeFlags() + { + return Body.Flags | OnExpression.Flags | InstructionFlags.ControlFlow | InstructionFlags.SideEffect; + } + + public override InstructionFlags DirectFlags => InstructionFlags.ControlFlow | InstructionFlags.SideEffect; + + public override StackType ResultType => StackType.Void; + + public override void WriteTo(ITextOutput output) + { + output.Write(".lock ("); + OnExpression.WriteTo(output); + output.WriteLine(") {"); + output.Indent(); + Body.WriteTo(output); + output.Unindent(); + output.WriteLine(); + output.Write("}"); + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs new file mode 100644 index 000000000..24961dd56 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/LockTransform.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + class LockTransform : IBlockTransform + { + BlockTransformContext context; + + void IBlockTransform.Run(Block block, BlockTransformContext context) + { + if (!context.Settings.LockStatement) return; + this.context = context; + for (int i = block.Instructions.Count - 1; i >= 0; i--) { + if (!TransformLockRoslyn(block, i)) + if (!TransformLockV4(block, i)) + TransformLockV2(block, i); + } + } + + /// + /// stloc lockObj(ldloc tempVar) + /// call Enter(ldloc tempVar) + /// .try BlockContainer { + /// Block lockBlock(incoming: 1) { + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } finally BlockContainer { + /// Block exitBlock(incoming: 1) { + /// call Exit(ldloc lockObj) + /// leave exitBlock (nop) + /// } + /// } + /// .lock (lockObj) BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } + /// + bool TransformLockV2(Block block, int i) + { + if (i < 2) return false; + if (!(block.Instructions[i] is TryFinally body) || !(block.Instructions[i - 2] is StLoc objectStore) || + !objectStore.Value.MatchLdLoc(out var tempVar) || !MatchCall(block.Instructions[i - 1] as Call, "Enter", tempVar)) + return false; + if (!objectStore.Variable.IsSingleDefinition) + return false; + if (!(body.TryBlock is BlockContainer tryContainer) || tryContainer.EntryPoint.Instructions.Count == 0 || tryContainer.EntryPoint.IncomingEdgeCount != 1) + return false; + if (!(body.FinallyBlock is BlockContainer finallyContainer) || !MatchExitBlock(finallyContainer.EntryPoint, null, objectStore.Variable)) + return false; + context.Step("LockTransformV2", block); + block.Instructions.RemoveAt(i - 1); + block.Instructions.RemoveAt(i - 2); + body.ReplaceWith(new LockInstruction(objectStore.Value, body.TryBlock)); + return true; + } + + /// + /// stloc flag(ldc.i4 0) + /// .try BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call Enter(stloc obj(lockObj), ldloca flag) + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } finally BlockContainer { + /// Block (incoming: 1) { + /// if (ldloc flag) Block { + /// call Exit(ldloc obj) + /// } + /// leave lockBlock (nop) + /// } + /// } + /// => + /// .lock (lockObj) BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } + /// + bool TransformLockV4(Block block, int i) + { + if (i < 1) return false; + if (!(block.Instructions[i] is TryFinally body) || !(block.Instructions[i - 1] is StLoc flagStore)) + return false; + if (!flagStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean) || !flagStore.Value.MatchLdcI4(0)) + return false; + if (!(body.TryBlock is BlockContainer tryContainer) || !MatchLockEntryPoint(tryContainer.EntryPoint, flagStore.Variable, out StLoc objectStore)) + return false; + if (!(body.FinallyBlock is BlockContainer finallyContainer) || !MatchExitBlock(finallyContainer.EntryPoint, flagStore.Variable, objectStore.Variable)) + return false; + context.Step("LockTransformV4", block); + block.Instructions.RemoveAt(i - 1); + tryContainer.EntryPoint.Instructions.RemoveAt(0); + body.ReplaceWith(new LockInstruction(objectStore.Value, body.TryBlock)); + return true; + } + + /// + /// stloc obj(lockObj) + /// stloc flag(ldc.i4 0) + /// .try BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call Enter(ldloc obj, ldloca flag) + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } finally BlockContainer { + /// Block (incoming: 1) { + /// if (ldloc flag) Block { + /// call Exit(ldloc obj) + /// } + /// leave lockBlock (nop) + /// } + /// } + /// => + /// .lock (lockObj) BlockContainer { + /// Block lockBlock (incoming: 1) { + /// call WriteLine() + /// leave lockBlock (nop) + /// } + /// } + /// + bool TransformLockRoslyn(Block block, int i) + { + if (i < 2) return false; + if (!(block.Instructions[i] is TryFinally body) || !(block.Instructions[i - 1] is StLoc flagStore) || !(block.Instructions[i - 2] is StLoc objectStore)) + return false; + if (!objectStore.Variable.IsSingleDefinition || !flagStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean) || !flagStore.Value.MatchLdcI4(0)) + return false; + if (!(body.TryBlock is BlockContainer tryContainer) || !MatchLockEntryPoint(tryContainer.EntryPoint, flagStore.Variable, objectStore.Variable)) + return false; + if (!(body.FinallyBlock is BlockContainer finallyContainer) || !MatchExitBlock(finallyContainer.EntryPoint, flagStore.Variable, objectStore.Variable)) + return false; + context.Step("LockTransformRoslyn", block); + block.Instructions.RemoveAt(i - 1); + block.Instructions.RemoveAt(i - 2); + tryContainer.EntryPoint.Instructions.RemoveAt(0); + body.ReplaceWith(new LockInstruction(objectStore.Value, body.TryBlock)); + return true; + } + + bool MatchExitBlock(Block entryPoint, ILVariable flag, ILVariable obj) + { + if (entryPoint.Instructions.Count != 2 || entryPoint.IncomingEdgeCount != 1) + return false; + if (flag != null) { + if (!entryPoint.Instructions[0].MatchIfInstruction(out var cond, out var trueInst) || !(trueInst is Block trueBlock)) + return false; + if (!(cond.MatchLdLoc(flag) || (cond.MatchCompNotEquals(out var left, out var right) && left.MatchLdLoc(flag) && right.MatchLdcI4(0))) || !MatchExitBlock(trueBlock, obj)) + return false; + } else { + if (!MatchCall(entryPoint.Instructions[0] as Call, "Exit", obj)) + return false; + } + if (!entryPoint.Instructions[1].MatchLeave((BlockContainer)entryPoint.Parent, out var retVal) || !retVal.MatchNop()) + return false; + return true; + } + + bool MatchExitBlock(Block exitBlock, ILVariable obj) + { + if (exitBlock.Instructions.Count != 1) + return false; + if (!MatchCall(exitBlock.Instructions[0] as Call, "Exit", obj)) + return false; + return true; + } + + bool MatchLockEntryPoint(Block entryPoint, ILVariable flag, ILVariable obj) + { + if (entryPoint.Instructions.Count == 0 || entryPoint.IncomingEdgeCount != 1) + return false; + if (!MatchCall(entryPoint.Instructions[0] as Call, "Enter", obj, flag)) + return false; + return true; + } + + bool MatchLockEntryPoint(Block entryPoint, ILVariable flag, out StLoc obj) + { + obj = null; + if (entryPoint.Instructions.Count == 0 || entryPoint.IncomingEdgeCount != 1) + return false; + if (!MatchCall(entryPoint.Instructions[0] as Call, "Enter", flag, out obj)) + return false; + return true; + } + + bool MatchCall(Call call, string methodName, ILVariable flag, out StLoc obj) + { + obj = null; + const string ThreadingMonitor = "System.Threading.Monitor"; + if (call == null || call.Method.Name != methodName || call.Method.DeclaringType.FullName != ThreadingMonitor || + call.Method.TypeArguments.Count != 0 || call.Arguments.Count != 2) + return false; + if (!call.Arguments[1].MatchLdLoca(flag) || !(call.Arguments[0] is StLoc val)) + return false; + obj = val; + return true; + } + + bool MatchCall(Call call, string methodName, params ILVariable[] variables) + { + const string ThreadingMonitor = "System.Threading.Monitor"; + if (call == null || call.Method.Name != methodName || call.Method.DeclaringType.FullName != ThreadingMonitor || + call.Method.TypeArguments.Count != 0 || call.Arguments.Count != variables.Length) + return false; + if (!call.Arguments[0].MatchLdLoc(variables[0])) + return false; + if (variables.Length == 2) { + if (!call.Arguments[1].MatchLdLoca(variables[1])) + return false; + } + return true; + } + } +}