From 23bca3713f55fdaaa07a50ef24c84f5bbc83a2b7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 6 Oct 2017 00:12:28 +0200 Subject: [PATCH] Fix #646: VB-compilers 'For Each In' is not recognized as foreach during decompilation --- .../ICSharpCode.Decompiler.Tests.csproj | 2 + .../ILPrettyTestRunner.cs | 6 ++ .../TestCases/ILPretty/Issue646.cs | 20 ++++ .../TestCases/ILPretty/Issue646.il | 93 +++++++++++++++++++ .../IL/Transforms/UsingTransform.cs | 51 +++++++++- 5 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.il diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 0572fe08c..a46ed0bcf 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -62,6 +62,7 @@ + @@ -115,6 +116,7 @@ + HelloWorld.cs diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 7d5efb32c..7d2f9ed47 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -36,6 +36,12 @@ namespace ICSharpCode.Decompiler.Tests Run(); } + [Test] + public void Issue646() + { + Run(); + } + void Run([CallerMemberName] string testName = null) { var ilFile = Path.Combine(TestCasePath, testName + ".il"); diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.cs new file mode 100644 index 000000000..b8e2f59f2 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.cs @@ -0,0 +1,20 @@ +using Microsoft.VisualBasic.CompilerServices; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace ICSharpCode.Decompiler.Tests.TestCases.ILPretty +{ + [StandardModule] + internal sealed class Issue646 + { + [STAThread] + public static void Main() + { + List list = new List(); + foreach (string item in list) { + Debug.WriteLine(item); + } + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.il new file mode 100644 index 000000000..a3772cb6f --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue646.il @@ -0,0 +1,93 @@ +// 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 extern System +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. + .ver 4:0:0:0 +} +.assembly extern Microsoft.VisualBasic +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) // .?_....: + .ver 10:0:0:0 +} +.assembly ConsoleApp11 +{ + .ver 1:0:0:0 +} +.module ConsoleApp11.exe +// MVID: {B973FCD6-A9C4-48A9-8291-26DDC248E208} +.imagebase 0x00400000 +.file alignment 0x00000200 +.stackreserve 0x00100000 +.subsystem 0x0003 // WINDOWS_CUI +.corflags 0x00020003 // ILONLY 32BITPREFERRED +// Image base: 0x000001C4B6C90000 + +.class private auto ansi sealed ICSharpCode.Decompiler.Tests.TestCases.ILPretty.Issue646 + extends [mscorlib]System.Object +{ + .custom instance void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute::.ctor() = ( + 01 00 00 00 + ) + // Methods + .method public static + void Main () cil managed + { + .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( + 01 00 00 00 + ) + // Method begins at RVA 0x21b4 + // Code size 61 (0x3d) + .maxstack 1 + .entrypoint + .locals init ( + [0] class [mscorlib]System.Collections.Generic.List`1, + [1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator, + [2] string, + [3] bool + ) + + IL_0000: nop + IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1::.ctor() + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator class [mscorlib]System.Collections.Generic.List`1::GetEnumerator() + IL_000d: stloc.1 + IL_000e: br.s IL_0020 + // loop start (head: IL_0020) + IL_0010: ldloca.s 1 + IL_0012: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator::get_Current() + IL_0017: stloc.2 + IL_0018: ldloc.2 + IL_0019: call void [System]System.Diagnostics.Debug::WriteLine(string) + IL_001e: nop + IL_001f: nop + + IL_0020: ldloca.s 1 + IL_0022: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator::MoveNext() + IL_0027: stloc.3 + IL_0028: ldloc.3 + IL_0029: brtrue.s IL_0010 + // end loop + + IL_002b: leave.s IL_003c + } // end .try + finally + { + IL_002d: ldloca.s 1 + IL_002f: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator + IL_0035: callvirt instance void [mscorlib]System.IDisposable::Dispose() + IL_003a: nop + IL_003b: endfinally + } // end handler + + IL_003c: ret + } // end of method Module1::Main + +} // end of class ConsoleApp11.Module1 diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index f74df84dc..ac0ff75dc 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!context.Settings.UsingStatement) return; this.context = context; for (int i = block.Instructions.Count - 1; i >= 0; i--) { - if (!TransformUsing(block, i)) + if (!TransformUsing(block, i) && !TransformUsingVB(block, i)) continue; // This happens in some cases: // Use correct index after transformation. @@ -91,6 +91,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms return true; } + /// + /// .try BlockContainer { + /// Block IL_0003(incoming: 1) { + /// stloc obj(resourceExpression) + /// call WriteLine(ldstr "using (null)") + /// leave IL_0003(nop) + /// } + /// } finally BlockContainer { + /// Block IL_0012(incoming: 1) { + /// if (comp(ldloc obj != ldnull)) Block IL_001a { + /// callvirt Dispose(ldnull) + /// } + /// leave IL_0012(nop) + /// } + /// } + /// leave IL_0000(nop) + /// => + /// using (resourceExpression) { + /// BlockContainer { + /// Block IL_0003(incoming: 1) { + /// call WriteLine(ldstr "using (null)") + /// leave IL_0003(nop) + /// } + /// } + /// } + /// + bool TransformUsingVB(Block block, int i) + { + if (!(block.Instructions[i] is TryFinally tryFinally)) + return false; + if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst)) + return false; + if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type))) + return false; + if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) + return false; + if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la)))) + return false; + if (storeInst.Variable.StoreInstructions.OfType().Any(st => st != storeInst)) + return false; + if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) + return false; + context.Step("UsingTransformVB", tryFinally); + storeInst.Variable.Kind = VariableKind.UsingLocal; + tryContainer.EntryPoint.Instructions.RemoveAt(0); + block.Instructions[i] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock); + return true; + } + bool CheckResourceType(IType type) { // non-generic IEnumerator does not implement IDisposable.