commit 80cf67e749d33991d2988925240d105e10fe85c4 Author: upstream Date: Fri Sep 11 23:42:13 2026 +0200 Upstream icsharpcode/ILSpy state of the touched files before 259e513829 diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs new file mode 100644 index 0000000..86c86cb --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -0,0 +1,1103 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using ICSharpCode.Decompiler.Tests.Helpers; + +using NUnit.Framework; + +namespace ICSharpCode.Decompiler.Tests +{ + [TestFixture, Parallelizable(ParallelScope.All)] + public class PrettyTestRunner + { + static readonly string TestCasePath = Tester.TestCasePath + "/Pretty"; + + [Test] + public void AllFilesHaveTests() + { + var testNames = typeof(PrettyTestRunner).GetMethods() + .Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Any()) + .Select(m => m.Name) + .ToArray(); + foreach (var file in new DirectoryInfo(TestCasePath).EnumerateFiles()) + { + if (file.Extension.Equals(".il", StringComparison.OrdinalIgnoreCase) + || file.Extension.Equals(".cs", StringComparison.OrdinalIgnoreCase)) + { + var testName = file.Name.Split('.')[0]; + Assert.That(testNames, Has.Member(testName)); + } + } + } + + static readonly CompilerOptions[] noRoslynOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.None, + CompilerOptions.Optimize + }); + + static readonly CompilerOptions[] roslynOnlyWithNet40Options = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslynOnlyOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn2OrNewerWithNet40Options = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn2OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn3OrNewerWithNet40Options = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn3OrNewerWithNet40Roslyn4Options = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn3OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn4OrNewerWithNet40Options = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn4OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] roslyn5OrNewerOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] defaultOptions = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.None, + CompilerOptions.Optimize, + CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + }); + + static readonly CompilerOptions[] defaultOptionsWithMcs = Tester.SupportedOnCurrentPlatform(new[] + { + CompilerOptions.None, + CompilerOptions.Optimize, + CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0 | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest | CompilerOptions.TargetNet40, + CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn1_3_2, + CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn2_10_0, + CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, + CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.Optimize | CompilerOptions.UseRoslyn4_14_0, + CompilerOptions.UseRoslynLatest, + CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, + CompilerOptions.UseMcs2_6_4, + CompilerOptions.Optimize | CompilerOptions.UseMcs2_6_4, + CompilerOptions.UseMcs5_23, + CompilerOptions.Optimize | CompilerOptions.UseMcs5_23 + }); + + [Test, Platform("Win")] // uses the legacy (pre-Roslyn) csc, which only exists on Windows + public async Task HelloWorld() + { + await RunForLibrary(); + await RunForLibrary(asmOptions: AssemblerOptions.UseDebug); + } + + [Test] + public async Task IndexRangeTest([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + if (cscOptions.HasFlag(CompilerOptions.UseRoslyn4_14_0) || cscOptions.HasFlag(CompilerOptions.UseRoslynLatest)) + { + Assert.Ignore("See https://github.com/icsharpcode/ILSpy/issues/2540"); + } + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task InlineAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CompoundAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ShortCircuit([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CustomShortCircuitOperators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ExceptionHandling([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.NullPropagation = false; + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.FileScopedNamespaces = false; + }); + } + + [Test] + public async Task Switch([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.SwitchExpressions = false; + }); + } + + // Runs on Roslyn 3.x and Roslyn 4.x or newer, with TargetNet40 variants only for + // Roslyn 4.x or newer: targeting net40 makes the compiler emit the + // ThrowInvalidOperationException throw helper (SwitchExpressionException does not + // exist there), but Roslyn 3.x emits a plain inline + // "throw new InvalidOperationException()" instead, which is indistinguishable from + // user code and therefore intentionally not transformed. + [Test] + public async Task SwitchExpressions([ValueSource(nameof(roslyn3OrNewerWithNet40Roslyn4Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ReduceNesting([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task DelegateConstruction([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + settings.QueryExpressions = false; + settings.NullableReferenceTypes = false; + }); + } + + [Test] + public async Task AnonymousTypes([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task StringConcatenation([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Async([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Lock([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Using([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary( + cscOptions: cscOptions, + configureDecompiler: settings => { + settings.UseEnhancedUsing = false; + settings.FileScopedNamespaces = false; + } + ); + } + + [Test] + public async Task UsingVariables([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task GloballyQualifiedTypeInStringInterpolation([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions) + { + // https://github.com/icsharpcode/ILSpy/issues/3447 + await RunForLibrary( + cscOptions: cscOptions, + configureDecompiler: settings => { + settings.UsingDeclarations = false; + settings.AlwaysUseGlobal = true; + } + ); + } + + [Test] + public async Task LiftedOperators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Operators([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Generics([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Loops([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => { + // legacy csc generates a dead store in debug builds + settings.RemoveDeadStores = (cscOptions == CompilerOptions.None); + settings.UseExpressionBodyForCalculatedGetterOnlyProperties = false; + }); + } + + [Test] + public async Task LocalFunctions([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task PropertiesAndEvents([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task AutoProperties([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AutomaticEvents([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task QueryExpressions([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task TypeAnalysisTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CheckedUnchecked([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ConditionalOperatorNesting([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task UnsafeCode([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.ReferenceUnsafe); + } + + [Test] + public async Task ConstructorInitializers([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.ProcessXmlDoc); + } + + [Test] + public async Task PInvoke([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + // This tests needs our own disassembler; ildasm has a bug with marshalinfo. + await RunForLibrary(cscOptions: cscOptions, asmOptions: AssemblerOptions.UseOwnDisassembler); + } + + [Test] + public async Task OutVariables([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task DefaultLiteral([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task OverloadResolution([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task TargetTypedDefault([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task PatternMatching([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task InitializerTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task DynamicTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task DynamicAwait([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ExpressionTrees([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task FixProxyCalls([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ValueTypes([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task VariableNaming([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.GeneratePdb); + } + + [Test] + public async Task VariableNamingWithoutSymbols([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.UseDebugSymbols = false); + } + + [Test] + public async Task CS72_PrivateProtected([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AsyncForeach([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AsyncMain([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await Run(cscOptions: cscOptions); + } + + [Test] + public async Task AsyncStreams([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AsyncAwaitPatterns([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AsyncUsing([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary( + cscOptions: cscOptions, + configureDecompiler: settings => { settings.UseEnhancedUsing = false; } + ); + } + + [Test] + public async Task CustomTaskType([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task RuntimeAsync([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary("Async", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview); + } + + [Test] + public async Task RuntimeAsyncForeach([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary("AsyncForeach", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview | CompilerOptions.GeneratePdb); + } + + [Test] + public async Task RuntimeAsyncMain([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await Run("AsyncMain", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview); + } + + [Test] + public async Task RuntimeAsyncStreams([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary("AsyncStreams", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview); + } + + [Test] + public async Task RuntimeAsyncUsing([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary( + "AsyncUsing", + cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview, + configureDecompiler: settings => { settings.UseEnhancedUsing = false; } + ); + } + + [Test] + public async Task RuntimeAsyncCustomTaskType([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary("CustomTaskType", cscOptions: cscOptions | CompilerOptions.EnableRuntimeAsync | CompilerOptions.Preview); + } + + [Test] + public async Task NullableRefTypes([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task NativeInts([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task FileScopedNamespaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.FileScopedNamespaces = true); + } + + [Test] + public async Task Structs([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task FunctionPointers([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Records([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3610([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3611([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3909([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task Issue3452([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task Issue3598([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task ExtensionEverything([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview | CompilerOptions.NullableEnable); + } + + [Test] + public async Task FieldKeyword([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.NullableEnable); + } + + [Test] + public async Task NullPropagation([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task StringInterpolation([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions) + { + await Run(cscOptions: cscOptions); + } + + [Test] + public async Task CS73_StackAllocInitializers([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task RefLocalsAndReturns([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CachedReadOnlySpanInitialization([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task RefFields([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ThrowExpressions([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task WellKnownConstants([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task QualifierTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task TupleTests([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task NamedArguments([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task OptionalArguments([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task OptionalArgumentsDisabled([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary( + cscOptions: cscOptions, + configureDecompiler: settings => { + settings.OptionalArguments = false; + } + ); + } + + [Test] + public async Task Comparisons([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ConstantsTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ParamsCollections([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task LambdaOptionalAndParamsParameters([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task RefStructInterfaces([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task FirstClassSpanTypes([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task FirstClassSpanConversions([ValueSource(nameof(roslyn5OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task ExpandParamsArgumentsDisabled([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.ExpandParamsArguments = false); + } + + [Test] + public async Task Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions, configureDecompiler: settings => settings.SetLanguageVersion(CSharp.LanguageVersion.CSharp6)); + } + + [Test] + public async Task Issue3439([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3406([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3442([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3483([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.CheckForOverflowUnderflow, configureDecompiler: settings => settings.CheckForOverflowUnderflow = true); + } + + [Test] + public async Task Issue3541([ValueSource(nameof(roslyn4OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3571_A([ValueSource(nameof(roslyn2OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3571_B([ValueSource(nameof(roslyn2OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3571_C([ValueSource(nameof(roslyn2OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3576([ValueSource(nameof(roslyn2OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3584([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3877([ValueSource(nameof(roslyn2OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task AssemblyCustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CustomAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CustomAttributes2([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CustomAttributeConflicts([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CustomAttributeSamples([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task MemberTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task MultidimensionalArray([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task EnumTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task InterfaceTests([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task TypeMemberTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task YieldReturn([ValueSource(nameof(defaultOptionsWithMcs))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task UserDefinedConversions([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Discards([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task DeconstructionTests([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CS9_ExtensionGetEnumerator([ValueSource(nameof(roslyn3OrNewerWithNet40Options))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task CovariantReturns([ValueSource(nameof(roslyn3OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Variance([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task StaticAbstractInterfaceMembers([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task MetadataAttributes([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task PointerArithmetic([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task InlineArrayTests([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3684([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + [Test] + public async Task Issue3751([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) + { + await RunForLibrary(cscOptions: cscOptions); + } + + async Task RunForLibrary([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action configureDecompiler = null) + { + await Run(testName, asmOptions | AssemblerOptions.Library, cscOptions | CompilerOptions.Library, configureDecompiler); + } + + async Task Run([CallerMemberName] string testName = null, AssemblerOptions asmOptions = AssemblerOptions.None, CompilerOptions cscOptions = CompilerOptions.None, Action configureDecompiler = null) + { + var csFile = Path.Combine(TestCasePath, testName + ".cs"); + var exeFile = TestsAssemblyOutput.GetFilePath(TestCasePath, testName, Tester.GetSuffix(cscOptions) + ".exe"); + if (cscOptions.HasFlag(CompilerOptions.Library)) + { + exeFile = Path.ChangeExtension(exeFile, ".dll"); + } + + // 1. Compile + CompilerResults output = null; + try + { + output = await Tester.CompileCSharp(csFile, cscOptions, exeFile).ConfigureAwait(false); + } + finally + { + if (output != null) + output.DeleteTempFiles(); + } + + // 2. Decompile + var settings = Tester.GetSettings(cscOptions); + configureDecompiler?.Invoke(settings); + var decompiled = await Tester.DecompileCSharp(exeFile, settings).ConfigureAwait(false); + + // 3. Compare + CodeAssert.FilesAreEqual(csFile, decompiled, Tester.GetPreprocessorSymbols(cscOptions).Append("EXPECTED_OUTPUT").ToArray()); + Tester.RepeatOnIOError(() => File.Delete(decompiled)); + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs new file mode 100644 index 0000000..e67f311 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/CachedDelegateInitialization.cs @@ -0,0 +1,331 @@ +// Copyright (c) 2011-2016 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Linq; + +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + public class CachedDelegateInitialization : IBlockTransform + { + BlockTransformContext context; + + public void Run(Block block, BlockTransformContext context) + { + this.context = context; + if (!context.Settings.AnonymousMethods) + return; + for (int i = context.IndexOfFirstAlreadyTransformedInstruction - 1; i >= 0; i--) + { + if (block.Instructions[i] is IfInstruction inst) + { + if (CachedDelegateInitializationWithField(inst)) + { + block.Instructions.RemoveAt(i); + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + if (CachedDelegateInitializationWithLocal(inst)) + { + ILInlining.InlineOneIfPossible(block, i, InliningOptions.Aggressive, context); + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + if (CachedDelegateInitializationRoslynInStaticWithLocal(inst) || CachedDelegateInitializationRoslynWithLocal(inst)) + { + block.Instructions.RemoveAt(i); + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + if (CachedDelegateInitializationVB(inst)) + { + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + if (CachedDelegateInitializationVBWithReturn(inst)) + { + block.Instructions.RemoveAt(i); + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + if (CachedDelegateInitializationVBWithClosure(inst)) + { + context.IndexOfFirstAlreadyTransformedInstruction = block.Instructions.Count; + continue; + } + } + } + } + + /// + /// if (comp(ldsfld CachedAnonMethodDelegate == ldnull)) { + /// stsfld CachedAnonMethodDelegate(DelegateConstruction) + /// } + /// ... one usage of CachedAnonMethodDelegate ... + /// => + /// ... one usage of DelegateConstruction ... + /// + bool CachedDelegateInitializationWithField(IfInstruction inst) + { + + Block trueInst = inst.TrueInst as Block; + if (trueInst == null || trueInst.Instructions.Count != 1 || !inst.FalseInst.MatchNop()) + return false; + var storeInst = trueInst.Instructions[0]; + if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdsFld(out IField field) || !right.MatchLdNull()) + return false; + if (!storeInst.MatchStsFld(out IField field2, out ILInstruction value) || !field.Equals(field2) || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(value.UnwrapConv(ConversionKind.Invalid) as NewObj, out _, out _, out _, true)) + return false; + var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); + if (nextInstruction == null) + return false; + var usages = nextInstruction.Descendants.Where(i => i.MatchLdsFld(field)).ToArray(); + if (usages.Length != 1) + return false; + context.Step("CachedDelegateInitializationWithField", inst); + usages[0].ReplaceWith(value); + context.EndStep(value); + return true; + } + + /// + /// if (comp(ldloc v == ldnull)) { + /// stloc v(DelegateConstruction) + /// } + /// => + /// stloc v(DelegateConstruction) + /// + bool CachedDelegateInitializationWithLocal(IfInstruction inst) + { + Block trueInst = inst.TrueInst as Block; + if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop()) + return false; + if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable v) || !right.MatchLdNull()) + return false; + var storeInst = trueInst.Instructions.Last(); + if (!storeInst.MatchStLoc(v, out ILInstruction value)) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(value as NewObj, out _, out _, out _, true)) + return false; + // do not transform if there are other stores/loads of this variable + if (v.StoreCount != 2 || v.StoreInstructions.Count != 2 || v.LoadCount != 2 || v.AddressCount != 0) + return false; + // do not transform if the first assignment is not assigning null: + var otherStore = v.StoreInstructions.OfType().SingleOrDefault(store => store != storeInst); + if (otherStore == null || !otherStore.Value.MatchLdNull() || !(otherStore.Parent is Block)) + return false; + // do not transform if there is no usage directly afterwards + var nextInstruction = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1); + if (nextInstruction == null) + return false; + var usages = nextInstruction.Descendants.Where(i => i.MatchLdLoc(v)).ToArray(); + if (usages.Length != 1) + return false; + context.Step("CachedDelegateInitializationWithLocal", inst); + ((Block)otherStore.Parent).Instructions.Remove(otherStore); + inst.ReplaceWith(storeInst); + context.EndStep(storeInst); + return true; + } + + /// + /// stloc s(ldobj(ldsflda(CachedAnonMethodDelegate)) + /// if (comp(ldloc s == null)) { + /// stloc s(stobj(ldsflda(CachedAnonMethodDelegate), DelegateConstruction)) + /// } + /// => + /// stloc s(DelegateConstruction) + /// + bool CachedDelegateInitializationRoslynInStaticWithLocal(IfInstruction inst) + { + Block trueInst = inst.TrueInst as Block; + if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop()) + return false; + if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable s) || !right.MatchLdNull()) + return false; + var storeInst = trueInst.Instructions.Last() as StLoc; + var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc; + if (storeBeforeIf == null || storeInst == null || storeBeforeIf.Variable != s || storeInst.Variable != s) + return false; + if (!(storeInst.Value is StObj stobj) || !(storeBeforeIf.Value is LdObj ldobj)) + return false; + if (!(stobj.Value is NewObj)) + return false; + if (!stobj.Target.MatchLdsFlda(out var field1) || !ldobj.Target.MatchLdsFlda(out var field2) || !field1.Equals(field2)) + return false; + if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationRoslynInStaticWithLocal", inst); + storeBeforeIf.Value = stobj.Value; + return true; + } + + /// + /// stloc s(ldobj(ldflda(CachedAnonMethodDelegate)) + /// if (comp(ldloc s == null)) { + /// stloc s(stobj(ldflda(CachedAnonMethodDelegate), DelegateConstruction)) + /// } + /// => + /// stloc s(DelegateConstruction) + /// + bool CachedDelegateInitializationRoslynWithLocal(IfInstruction inst) + { + Block trueInst = inst.TrueInst as Block; + if (trueInst == null || (trueInst.Instructions.Count != 1) || !inst.FalseInst.MatchNop()) + return false; + if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !left.MatchLdLoc(out ILVariable s) || !right.MatchLdNull()) + return false; + var storeInst = trueInst.Instructions.Last() as StLoc; + var storeBeforeIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex - 1) as StLoc; + if (storeBeforeIf == null || storeInst == null || storeBeforeIf.Variable != s || storeInst.Variable != s) + return false; + if (!(storeInst.Value is StObj stobj) || !(storeBeforeIf.Value is LdObj ldobj)) + return false; + if (!(stobj.Value is NewObj)) + return false; + if (!stobj.Target.MatchLdFlda(out var _, out var field1) || !ldobj.Target.MatchLdFlda(out var __, out var field2) || !field1.Equals(field2)) + return false; + if (!DelegateConstruction.MatchDelegateConstruction((NewObj)stobj.Value, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationRoslynWithLocal", inst); + storeBeforeIf.Value = stobj.Value; + return true; + } + + /// + /// if (comp.i4(comp.o(ldobj delegateType(ldsflda CachedAnonMethodDelegate) != ldnull) == ldc.i4 0)) Block { + /// stloc s(stobj(ldflda(CachedAnonMethodDelegate), DelegateConstruction)) + /// } else Block { + /// stloc s(ldobj System.Action(ldsflda $I4-1)) + /// } + /// => + /// stloc s(DelegateConstruction) + /// + bool CachedDelegateInitializationVB(IfInstruction inst) + { + if (!(inst.TrueInst is Block trueInst && inst.FalseInst is Block falseInst)) + return false; + if (trueInst.Instructions.Count != 1 || falseInst.Instructions.Count != 1) + return false; + if (!(trueInst.Instructions[0].MatchStLoc(out var s, out var trueInitValue) + && falseInst.Instructions[0].MatchStLoc(s, out var falseInitValue))) + { + return false; + } + if (s.Kind != VariableKind.StackSlot || s.StoreCount != 2) + return false; + if (!(trueInitValue is StObj stobj) || !(falseInitValue is LdObj ldobj)) + return false; + if (!(stobj.Value is NewObj delegateConstruction)) + return false; + if (!stobj.Target.MatchLdsFlda(out var field1) + || !ldobj.Target.MatchLdsFlda(out var field2) + || !field1.Equals(field2)) + { + return false; + } + if (!inst.Condition.MatchCompEquals(out ILInstruction left, out ILInstruction right) || !right.MatchLdNull()) + return false; + if (!ldobj.Match(left).Success) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationVB", inst); + var stloc = new StLoc(s, delegateConstruction); + inst.ReplaceWith(stloc); + context.EndStep(stloc); + return true; + } + + /// + /// if (comp.o(ldsfld CachedAnonMethodDelegate != ldnull)) { + /// leave IL_0005 (ldsfld CachedAnonMethodDelegate) + /// } + /// leave IL_0005 (stsfld CachedAnonMethodDelegate(DelegateConstruction)) + /// => + /// leave IL_0005 (DelegateConstruction) + /// + bool CachedDelegateInitializationVBWithReturn(IfInstruction inst) + { + if (!inst.Condition.MatchCompNotEqualsNull(out var arg) || !arg.MatchLdsFld(out var field)) + return false; + if (!inst.FalseInst.MatchNop()) + return false; + if (!inst.TrueInst.MatchReturn(out arg) || !arg.MatchLdsFld(field)) + return false; + var leaveAfterIf = inst.Parent.Children.ElementAtOrDefault(inst.ChildIndex + 1) as Leave; + if (leaveAfterIf is null || !leaveAfterIf.IsLeavingFunction) + return false; + if (!leaveAfterIf.Value.MatchStsFld(out var field2, out var delegateConstruction) || !field.Equals(field2)) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationVBWithReturn", inst); + leaveAfterIf.Value = delegateConstruction; + return true; + } + + /// + /// if (comp.o(ldobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure)) != ldnull)) Block { + /// stloc s(ldobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure))) + /// } else Block { + /// stloc s(stobj delegateType(ldflda CachedAnonMethodDelegate(ldloc closure), DelegateConstruction)) + /// } + /// => + /// stloc s(DelegateConstruction) + /// + bool CachedDelegateInitializationVBWithClosure(IfInstruction inst) + { + if (!(inst.TrueInst is Block trueInst && inst.FalseInst is Block falseInst)) + return false; + if (trueInst.Instructions.Count != 1 || falseInst.Instructions.Count != 1) + return false; + if (!(trueInst.Instructions[0].MatchStLoc(out var s, out var trueInitValue) + && falseInst.Instructions[0].MatchStLoc(s, out var falseInitValue))) + { + return false; + } + if (s.Kind != VariableKind.StackSlot || s.StoreCount != 2) + return false; + if (!(falseInitValue is StObj stobj) || !(trueInitValue is LdObj ldobj)) + return false; + if (!(stobj.Value is NewObj delegateConstruction)) + return false; + if (!stobj.Target.MatchLdFlda(out var target1, out var field1) + || !ldobj.Target.MatchLdFlda(out var target2, out var field2) + || !field1.Equals(field2) || !target1.Match(target2).Success) + { + return false; + } + if (!inst.Condition.MatchCompNotEqualsNull(out ILInstruction left)) + return false; + if (!ldobj.Match(left).Success) + return false; + if (!DelegateConstruction.MatchDelegateConstruction(delegateConstruction, out _, out _, out _, true)) + return false; + context.Step("CachedDelegateInitializationVBWithClosure", inst); + var stloc = new StLoc(s, delegateConstruction); + inst.ReplaceWith(stloc); + context.EndStep(stloc); + return true; + } + } +}