From 8813d254f8c50278b312975eb8d2a3852170d4f3 Mon Sep 17 00:00:00 2001 From: ElektroKill Date: Fri, 23 Dec 2022 20:08:54 +0100 Subject: [PATCH] Add VBPretty test case for VB yield return decompilation --- .../ICSharpCode.Decompiler.Tests.csproj | 2 + .../TestCases/VBPretty/YieldReturn.cs | 425 ++++++++++++++++++ .../TestCases/VBPretty/YieldReturn.vb | 300 +++++++++++++ .../VBPrettyTestRunner.cs | 6 + 4 files changed, 733 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 15d7d383b..52ad5e804 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -117,6 +117,8 @@ + + diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs new file mode 100644 index 000000000..78c047ac1 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +#if LEGACY_VBC +using System.Diagnostics; +#endif + +using Microsoft.VisualBasic.CompilerServices; + +namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty +{ + internal struct StructWithYieldReturn + { + private int val; + + public IEnumerable Count() + { + yield return val; + yield return val; + } + } + + public class YieldReturnPrettyTest + { + private int fieldOnThis; + +#if LEGACY_VBC + [DebuggerStepThrough] +#endif + public static IEnumerable YieldChars { + get { + yield return 'a'; + yield return 'b'; + yield return 'c'; + } + } + + internal static void Print(string name, IEnumerator enumerator) + { + Console.WriteLine(name + ": Test start"); + while (enumerator.MoveNext()) + { + Console.WriteLine(name + ": " + enumerator.Current.ToString()); + } + } + + public static IEnumerable SimpleYieldReturn() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public static IEnumerator SimpleYieldReturnEnumerator() + { + yield return "A"; + yield return "B"; + yield return "C"; + } + + public IEnumerable YieldReturnParameters(int p) + { + yield return p; + yield return fieldOnThis; + } + + public IEnumerator YieldReturnParametersEnumerator(int p) + { + yield return p; + yield return fieldOnThis; + } + + public static IEnumerable YieldReturnInLoop() + { + int num = 0; + do + { + yield return num; + num = checked(num + 1); + } while (num <= 99); + } + + public static IEnumerable YieldReturnWithTryFinally() + { + yield return 0; + try + { + yield return 1; + } + finally + { + Console.WriteLine("Finally!"); + } + yield return 2; + } + + public static IEnumerable YieldReturnWithNestedTryFinally(bool breakInMiddle) + { + Console.WriteLine("Start of method - 1"); + yield return "Start of method"; + Console.WriteLine("Start of method - 2"); + try + { + Console.WriteLine("Within outer try - 1"); + yield return "Within outer try"; + Console.WriteLine("Within outer try - 2"); + try + { + Console.WriteLine("Within inner try - 1"); + yield return "Within inner try"; + Console.WriteLine("Within inner try - 2"); + if (breakInMiddle) + { + Console.WriteLine("Breaking..."); + yield break; + } + Console.WriteLine("End of inner try - 1"); + yield return "End of inner try"; + Console.WriteLine("End of inner try - 2"); + } + finally + { + Console.WriteLine("Inner Finally"); + } + Console.WriteLine("End of outer try - 1"); + yield return "End of outer try"; + Console.WriteLine("End of outer try - 2"); + } + finally + { + Console.WriteLine("Outer Finally"); + } + Console.WriteLine("End of method - 1"); + yield return "End of method"; + Console.WriteLine("End of method - 2"); + } + + public static IEnumerable YieldReturnWithTwoNonNestedFinallyBlocks(IEnumerable input) + { + // outer try-finally block + foreach (string item in input) + { + // nested try-finally block + try + { + yield return item; + } + finally + { + Console.WriteLine("Processed " + item); + } + } + yield return "A"; + yield return "B"; + yield return "C"; + yield return "D"; + yield return "E"; + yield return "F"; + // outer try-finally block + foreach (string item2 in input) + { + yield return item2.ToUpper(); + } + } + + public static IEnumerable GetEvenNumbers(int n) + { + int num = checked(n - 1); + for (int i = 0; i <= num; i = checked(i + 1)) + { + if (i % 2 == 0) + { + yield return i; + } + } + } + + public static IEnumerable ExceptionHandling() + { + yield return 'a'; + try + { + Console.WriteLine("1 - try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("1 - catch"); + ProjectData.ClearProjectError(); + } + yield return 'b'; + try + { + try + { + Console.WriteLine("2 - try"); + } + finally + { + Console.WriteLine("2 - finally"); + } + yield return 'c'; + } + finally + { + Console.WriteLine("outer finally"); + } + } + + public static IEnumerable YieldBreakInCatch() + { + yield return 0; + try + { + Console.WriteLine("In Try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + ProjectData.ClearProjectError(); + // yield return is not allowed in catch, but yield break is + yield break; + } + yield return 1; + } + + public static IEnumerable YieldBreakInCatchInTryFinally() + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + ProjectData.ClearProjectError(); + // yield return is not allowed in catch, but yield break is + // Note that pre-roslyn, this code triggers a compiler bug: + // If the finally block throws an exception, it ends up getting + // called a second time. + yield break; + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakInTryCatchInTryFinally() + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + // same compiler bug as in YieldBreakInCatchInTryFinally + yield break; + } + catch (Exception projectError) + { + ProjectData.SetProjectError(projectError); + Console.WriteLine("Catch"); + ProjectData.ClearProjectError(); + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakInTryFinallyInTryFinally(bool b) + { + try + { + yield return 0; + try + { + Console.WriteLine("In Try"); + if (b) + { + // same compiler bug as in YieldBreakInCatchInTryFinally + yield break; + } + } + finally + { + Console.WriteLine("Inner Finally"); + } + yield return 1; + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable YieldBreakOnly() + { + yield break; + } + + public static IEnumerable UnconditionalThrowInTryFinally() + { + // Here, MoveNext() doesn't call the finally methods at all + // (only indirectly via Dispose()) + try + { + yield return 0; + throw new NotImplementedException(); + } + finally + { + Console.WriteLine("Finally"); + } + } + + public static IEnumerable NestedTryFinallyStartingOnSamePosition() + { + // The first user IL instruction is already in 2 nested try blocks. +#if ((ROSLYN2 && !ROSLYN4) && OPT) + int num = -1; +#endif + try + { +#if ((ROSLYN2 && !ROSLYN4) && OPT) + _ = num - 1; +#endif + try + { + yield return 0; + } + finally + { + Console.WriteLine("Inner Finally"); + } + } + finally + { + Console.WriteLine("Outer Finally"); + } + } + +#if ROSLYN + public static IEnumerable LocalInFinally(T a) where T : IDisposable + { + yield return 1; + try + { + yield return 2; + } + finally + { + T val = a; + val.Dispose(); + val.Dispose(); + } + yield return 3; + } +#endif + + public static IEnumerable GenericYield() where T : new() + { + T val = new T(); + int num = 0; + do + { + yield return val; + num = checked(num + 1); + } while (num <= 2); + } + + public static IEnumerable MultipleYieldBreakInTryFinally(int i) + { + try + { + if (i == 2) + { + yield break; + } + + while (i < 40) + { + if (i % 2 == 0) + { + yield break; + } + i = checked(i + 1); + + yield return i; + } + } + finally + { + Console.WriteLine("finally"); + } + Console.WriteLine("normal exit"); + } + + internal IEnumerable ForLoopWithYieldReturn(int end, int evil) + { + // This loop needs to pick the implicit "yield break;" as exit point + // in order to produce pretty code; not the "throw" which would + // be a less-pretty option. + checked + { + int num = end - 1; + for (int i = 0; i <= num; i++) + { + if (i == evil) + { + throw new InvalidOperationException("Found evil number"); + } + yield return i; + } + } + } + } +} diff --git a/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb new file mode 100644 index 000000000..a12d95b12 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/VBPretty/YieldReturn.vb @@ -0,0 +1,300 @@ +Imports System +Imports System.Collections.Generic + +Namespace ICSharpCode.Decompiler.Tests.TestCases.VBPretty + Friend Structure StructWithYieldReturn + Private val As Integer + + Public Iterator Function Count() As IEnumerable(Of Integer) + Yield val + Yield val + End Function + End Structure + + Public Class YieldReturnPrettyTest + Private fieldOnThis As Integer + + Public Shared ReadOnly Iterator Property YieldChars As IEnumerable(Of Char) + Get + Yield "a"c + Yield "b"c + Yield "c"c + End Get + End Property + + Friend Shared Sub Print(Of T)(ByVal name As String, ByVal enumerator As IEnumerator(Of T)) + Console.WriteLine(name + ": Test start") + While enumerator.MoveNext() + Console.WriteLine(name + ": " + enumerator.Current.ToString()) + End While + End Sub + + Public Shared Iterator Function SimpleYieldReturn() As IEnumerable(Of String) + Yield "A" + Yield "B" + Yield "C" + End Function + + Public Shared Iterator Function SimpleYieldReturnEnumerator() As IEnumerator(Of String) + Yield "A" + Yield "B" + Yield "C" + End Function + + Public Iterator Function YieldReturnParameters(ByVal p As Integer) As IEnumerable(Of Integer) + Yield p + Yield fieldOnThis + End Function + + Public Iterator Function YieldReturnParametersEnumerator(ByVal p As Integer) As IEnumerator(Of Integer) + Yield p + Yield fieldOnThis + End Function + + Public Shared Iterator Function YieldReturnInLoop() As IEnumerable(Of Integer) + For i = 0 To 99 + Yield i + Next + End Function + + Public Shared Iterator Function YieldReturnWithTryFinally() As IEnumerable(Of Integer) + Yield 0 + Try + Yield 1 + Finally + Console.WriteLine("Finally!") + End Try + Yield 2 + End Function + + Public Shared Iterator Function YieldReturnWithNestedTryFinally(ByVal breakInMiddle As Boolean) As IEnumerable(Of String) + Console.WriteLine("Start of method - 1") + Yield "Start of method" + Console.WriteLine("Start of method - 2") + Try + Console.WriteLine("Within outer try - 1") + Yield "Within outer try" + Console.WriteLine("Within outer try - 2") + Try + Console.WriteLine("Within inner try - 1") + Yield "Within inner try" + Console.WriteLine("Within inner try - 2") + If breakInMiddle Then + Console.WriteLine("Breaking...") + Return + End If + Console.WriteLine("End of inner try - 1") + Yield "End of inner try" + Console.WriteLine("End of inner try - 2") + Finally + Console.WriteLine("Inner Finally") + End Try + Console.WriteLine("End of outer try - 1") + Yield "End of outer try" + Console.WriteLine("End of outer try - 2") + Finally + Console.WriteLine("Outer Finally") + End Try + Console.WriteLine("End of method - 1") + Yield "End of method" + Console.WriteLine("End of method - 2") + End Function + + Public Shared Iterator Function YieldReturnWithTwoNonNestedFinallyBlocks(ByVal input As IEnumerable(Of String)) As IEnumerable(Of String) + ' outer try-finally block + For Each line In input + ' nested try-finally block + Try + Yield line + Finally + Console.WriteLine("Processed " & line) + End Try + Next + Yield "A" + Yield "B" + Yield "C" + Yield "D" + Yield "E" + Yield "F" + ' outer try-finally block + For Each item In input + Yield item.ToUpper() + Next + End Function + + Public Shared Iterator Function GetEvenNumbers(ByVal n As Integer) As IEnumerable(Of Integer) + For i = 0 To n - 1 + If i Mod 2 = 0 Then + Yield i + End If + Next + End Function + + Public Shared Iterator Function ExceptionHandling() As IEnumerable(Of Char) + Yield "a"c + Try + Console.WriteLine("1 - try") + Catch __unusedException1__ As Exception + Console.WriteLine("1 - catch") + End Try + Yield "b"c + Try + Try + Console.WriteLine("2 - try") + Finally + Console.WriteLine("2 - finally") + End Try + Yield "c"c + Finally + Console.WriteLine("outer finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInCatch() As IEnumerable(Of Integer) + Yield 0 + Try + Console.WriteLine("In Try") + Catch + ' yield return is not allowed in catch, but yield break is + Return + End Try + Yield 1 + End Function + + Public Shared Iterator Function YieldBreakInCatchInTryFinally() As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + Catch + ' yield return is not allowed in catch, but yield break is + ' Note that pre-roslyn, this code triggers a compiler bug: + ' If the finally block throws an exception, it ends up getting + ' called a second time. + Return + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInTryCatchInTryFinally() As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + ' same compiler bug as in YieldBreakInCatchInTryFinally + Return + Catch + Console.WriteLine("Catch") + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakInTryFinallyInTryFinally(ByVal b As Boolean) As IEnumerable(Of Integer) + Try + Yield 0 + Try + Console.WriteLine("In Try") + If b Then + ' same compiler bug as in YieldBreakInCatchInTryFinally + Return + End If + + Finally + Console.WriteLine("Inner Finally") + End Try + Yield 1 + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function YieldBreakOnly() As IEnumerable(Of Integer) + Return + End Function + + Public Shared Iterator Function UnconditionalThrowInTryFinally() As IEnumerable(Of Integer) + ' Here, MoveNext() doesn't call the finally methods at all + ' (only indirectly via Dispose()) + Try + Yield 0 + Throw New NotImplementedException() + Finally + Console.WriteLine("Finally") + End Try + End Function + + Public Shared Iterator Function NestedTryFinallyStartingOnSamePosition() As IEnumerable(Of Integer) + ' The first user IL instruction is already in 2 nested try blocks. + Try + Try + Yield 0 + Finally + Console.WriteLine("Inner Finally") + End Try + + Finally + Console.WriteLine("Outer Finally") + End Try + End Function + +#If ROSLYN Then + Public Shared Iterator Function LocalInFinally(Of T As IDisposable)(ByVal a As T) As IEnumerable(Of Integer) + Yield 1 + Try + Yield 2 + Finally + Dim val = a + val.Dispose() + val.Dispose() + End Try + Yield 3 + End Function +#End If + + Public Shared Iterator Function GenericYield(Of T As New)() As IEnumerable(Of T) + Dim val As T = New T() + For i = 0 To 2 + Yield val + Next + End Function + + Public Shared Iterator Function MultipleYieldBreakInTryFinally(ByVal i As Integer) As IEnumerable(Of Integer) + Try + If i = 2 Then + Return + End If + + While i < 40 + If i Mod 2 = 0 Then + Return + End If + i += 1 + + Yield i + End While + + Finally + Console.WriteLine("finally") + End Try + Console.WriteLine("normal exit") + End Function + + Friend Iterator Function ForLoopWithYieldReturn(ByVal [end] As Integer, ByVal evil As Integer) As IEnumerable(Of Integer) + ' This loop needs to pick the implicit "yield break;" as exit point + ' in order to produce pretty code; not the "throw" which would + ' be a less-pretty option. + For i = 0 To [end] - 1 + If i = evil Then + Throw New InvalidOperationException("Found evil number") + End If + Yield i + Next + End Function + End Class +End Namespace diff --git a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs index 0783a67bf..2dcd06139 100644 --- a/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs @@ -137,6 +137,12 @@ namespace ICSharpCode.Decompiler.Tests await Run(options: options | CompilerOptions.Library); } + [Test] + public async Task YieldReturn([ValueSource(nameof(defaultOptions))] CompilerOptions options) + { + await Run(options: options | CompilerOptions.Library); + } + async Task Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null) { var vbFile = Path.Combine(TestCasePath, testName + ".vb");