diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs new file mode 100644 index 0000000..44d08a2 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -0,0 +1,471 @@ +// 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 ILPrettyTestRunner + { + static readonly string TestCasePath = Tester.TestCasePath + "/ILPretty"; + + [Test] + public void AllFilesHaveTests() + { + var testNames = typeof(ILPrettyTestRunner).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)) + { + var testName = file.Name.Split('.')[0]; + Assert.That(testNames, Has.Member(testName)); + Assert.That(File.Exists(Path.Combine(TestCasePath, testName + ".cs"))); + } + } + } + + [Test] + public async Task ConditionalChain() + { + await Run(); + } + + [Test] + public async Task AnonymousMethodEdgeCases() + { + await Run(); + } + + [Test, Ignore("Need to decide how to represent virtual methods without 'newslot' flag")] + public async Task Issue379() + { + await Run(); + } + + [Test] + public async Task Issue646() + { + await Run(); + } + + [Test] + public async Task Issue684() + { + await Run(); + } + + [Test] + public async Task Issue959() + { + await Run(); + } + + [Test] + public async Task Issue982() + { + await Run(); + } + + [Test] + public async Task Issue1038() + { + await Run(); + } + + [Test] + public async Task Issue1047() + { + await Run(); + } + + [Test] + public async Task Issue1389() + { + await Run(); + } + + [Test] + public async Task Issue1918() + { + await Run(); + } + + [Test] + public async Task Issue1922() + { + await Run(); + } + + [Test] + public async Task FSharpUsing_Debug() + { + await Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false, FileScopedNamespaces = false }); + } + + [Test] + public async Task FSharpUsing_Release() + { + await Run(settings: new DecompilerSettings { RemoveDeadStores = true, UseEnhancedUsing = false, FileScopedNamespaces = false }); + } + + [Test] + public async Task DirectCallToExplicitInterfaceImpl() + { + await Run(); + } + + [Test] + public async Task TruncatedAccessorBody() + { + await Run(); + } + + [Test] + public async Task EvalOrder() + { + await Run(); + } + + [Test] + public async Task TailCall() + { + await Run(); + } + + [Test] + public async Task CS1xSwitch_Debug() + { + await Run(settings: new DecompilerSettings { SwitchExpressions = false, FileScopedNamespaces = false }); + } + + [Test] + public async Task CS1xSwitch_Release() + { + await Run(settings: new DecompilerSettings { SwitchExpressions = false, FileScopedNamespaces = false }); + } + + [Test] + public async Task UnknownTypes() + { + await Run(); + } + + [Test] + public async Task NoAccessorProperties() + { + await Run(); + } + + [Test] + public async Task Issue1145() + { + await Run(); + } + + [Test] + public async Task Issue1157() + { + await Run(); + } + + [Test] + public async Task Issue1256() + { + await Run(); + } + + [Test] + public async Task Issue1323() + { + await Run(); + } + + [Test] + public async Task Issue1325() + { + await Run(); + } + + [Test] + public async Task Issue1681() + { + await Run(); + } + + [Test] + public async Task Issue1454() + { + await Run(); + } + + [Test] + public async Task Issue1638() + { + await Run(); + } + + [Test] + public async Task Issue2104() + { + await Run(); + } + + [Test] + public async Task Issue2443() + { + await Run(); + } + + [Test] + public async Task Issue3344CkFinite() + { + await Run(); + } + + [Test] + public async Task Issue3421() + { + await Run(); + } + + [Test] + public async Task Issue3442() + { + await Run(); + } + + [Test] + public async Task Issue3465() + { + await Run(); + } + + [Test] + public async Task Issue3466() + { + await Run(); + } + + [Test] + public async Task Issue3504() + { + await Run(); + } + + [Test] + public async Task Issue3524() + { + await Run(); + } + + [Test] + public async Task Issue3552() + { + await Run(); + } + + [Test] + public async Task Issue2260SwitchString() + { + await Run(); + } + + [Test] + public async Task SwitchOnStringNegativeCharIndex() + { + await Run(); + } + + [Test] + public async Task CachedReadOnlySpanFromLazyCache() + { + await Run(); + } + + [Test] + public async Task SpanConversionOperatorMismatch() + { + await Run(settings: new DecompilerSettings { FileScopedNamespaces = false, FirstClassSpanTypes = true }); + } + + [Test] + public async Task ConstantBlobs() + { + await Run(); + } + + [Test] + public async Task SequenceOfNestedIfs() + { + await Run(); + } + + [Test] + public async Task StackAllocDuplicateStore() + { + await Run(); + } + + [Test, Platform("Win")] // UseLegacyAssembler requires the .NET Framework ilasm + public async Task Unsafe() + { + await Run(assemblerOptions: AssemblerOptions.Library | AssemblerOptions.UseLegacyAssembler); + } + + [Test] + public async Task CallIndirect() + { + await Run(); + } + + [Test] + public async Task InstanceOperatorCall() + { + await Run(); + } + + [Test] + public async Task FSharpLoops_Debug() + { + CopyFSharpCoreDll(); + await Run(settings: new DecompilerSettings { RemoveDeadStores = true, FileScopedNamespaces = false }); + } + + [Test] + public async Task FSharpLoops_Release() + { + CopyFSharpCoreDll(); + await Run(settings: new DecompilerSettings { RemoveDeadStores = true, FileScopedNamespaces = false }); + } + + [Test] + public async Task WeirdEnums() + { + await Run(); + } + + [Test] + public async Task EnumArithmeticOutOfRange() + { + await Run(); + } + + [Test] + public async Task GuessAccessors() + { + await Run(); + } + + [Test] + public async Task InaccessibleParameterTypes() + { + await Run(); + } + + [Test] + public async Task EmptyBodies() + { + await Run(); + } + + [Test] + public async Task SealedRecordProtectedCopyCtor() + { + await Run(); + } + + [Test] + public async Task MonoFixed() + { + await Run(); + } + + [Test] + public async Task ExtensionEncodingV1() + { + // uses Microsoft.Net.Compilers.Toolset 5.0.0-2.25380.108 + // see ExtensionEncodingV1.il for details + await Run(); + } + + [Test] + public async Task ExtensionEncodingV2() + { + // uses Microsoft.Net.Compilers.Toolset 5.0.0-2.25451.107 + // see ExtensionEncodingV2.il for details + await Run(); + } + + [Test] + public async Task SortSwitchSections() + { + await Run(settings: new DecompilerSettings { SortSwitchSections = true, FileScopedNamespaces = false }); + } + + [Test] + public async Task Issue3729() + { + await Run(); + } + + async Task Run([CallerMemberName] string testName = null, DecompilerSettings settings = null, + AssemblerOptions assemblerOptions = AssemblerOptions.Library) + { + if (settings == null) + { + // never use file-scoped namespaces, unless explicitly specified + settings = new DecompilerSettings { FileScopedNamespaces = false }; + } + var ilFile = Path.Combine(TestCasePath, testName + ".il"); + var csFile = Path.Combine(TestCasePath, testName + ".cs"); + + var executable = await Tester.AssembleIL(ilFile, assemblerOptions).ConfigureAwait(false); + var decompiled = await Tester.DecompileCSharp(executable, settings).ConfigureAwait(false); + + CodeAssert.FilesAreEqual(csFile, decompiled, ["EXPECTED_OUTPUT"]); + Tester.RepeatOnIOError(() => File.Delete(decompiled)); + } + + static readonly object copyLock = new object(); + + static void CopyFSharpCoreDll() + { + lock (copyLock) + { + if (File.Exists(Path.Combine(TestCasePath, "FSharp.Core.dll"))) + return; + string fsharpCoreDll = Path.Combine(TestCasePath, "..", "..", "..", "ILSpy-tests", "FSharp", "FSharp.Core.dll"); + if (!File.Exists(fsharpCoreDll)) + Assert.Ignore("Ignored because of missing ILSpy-tests repo. Must be checked out separately from https://github.com/icsharpcode/ILSpy-tests!"); + File.Copy(fsharpCoreDll, Path.Combine(TestCasePath, "FSharp.Core.dll")); + } + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs index 2cc1ba5..5aa9064 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs @@ -69,79 +69,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms container.SortBlocks(deleteUnreachableBlocks: true); } - PropagateExceptionVariable(context, catchBlock); - } - else if (catchBlock.Filter is BlockContainer filterContainer - && MatchTypeTestConjunction(catchBlock.Variable, filterContainer, - out var conjunctionType, out var typeTest) - && conjunctionType.GetStackType() == catchBlock.Variable.StackType) - { - context.Step($"Detected catch-when type test for {catchBlock.Variable.Name}", typeTest); - catchBlock.Variable.Type = conjunctionType; - var remainder = typeTest.Right; - typeTest.ReplaceWith(remainder); - context.EndStep(remainder); - PropagateExceptionVariable(context, catchBlock); } } } - /// - /// BlockContainer { - /// Block entryPoint (incoming: 1) { - /// leave container(bit.and(bit.and(comp(isinst T(ldloc exceptionVar) != ldnull), cond1), cond2)) - /// } - /// } - /// The Visual Basic compiler emits the whole filter as one non-short-circuiting - /// expression rather than the block chain csc emits, so the type test sits in a - /// conjunction instead of a branch. On a match, typeTest is the innermost `bit.and`, - /// whose right operand is the filter that remains once the test moves to the catch type. - /// - bool MatchTypeTestConjunction(ILVariable exceptionVar, BlockContainer container, - out IType exceptionType, out BinaryNumericInstruction typeTest) - { - exceptionType = null; - typeTest = null; - var entryPoint = container.EntryPoint; - if (entryPoint == null || entryPoint.IncomingEdgeCount != 1 || entryPoint.Instructions.Count != 1) - return false; - if (!entryPoint.Instructions[0].MatchLeave(container, out var condition)) - return false; - // Once the test moves to the catch type, the other conjuncts stop running for a - // non-matching exception, which is only invisible if they have no side effects. - if (!SemanticHelper.IsPure(condition.Flags)) - return false; - // The test is the leftmost operand of a left-nested chain of `&`. - while (condition is BinaryNumericInstruction { Operator: BinaryNumericOperator.BitAnd } and) - { - if (MatchTypeTest(and.Left, exceptionVar, out exceptionType)) - { - typeTest = and; - return true; - } - condition = and.Left; - } - return false; - } - - /// - /// comp(isinst T(ldloc exceptionVar) != ldnull), however the compiler spelled the null test. - /// - static bool MatchTypeTest(ILInstruction condition, ILVariable exceptionVar, out IType exceptionType) - { - exceptionType = null; - if (condition is not Comp comp || !comp.Right.MatchLdNull()) - return false; - // `cgt.un x, null` is how both compilers spell `x != null` for a reference. - if (comp.Kind != ComparisonKind.Inequality - && !(comp.Kind == ComparisonKind.GreaterThan && comp.InputType == StackType.O)) - return false; - if (!comp.Left.MatchIsInst(out var argument, out exceptionType)) - return false; - return argument.MatchLdLoc(exceptionVar); - } - /// /// catch E_189 : 0200007C System.Exception when (BlockContainer { /// Block IL_0079 (incoming: 1) {