diff --git a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs index 3a4304115..b9b1d98db 100644 --- a/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs @@ -75,8 +75,10 @@ namespace ICSharpCode.Decompiler.Tests CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, CompilerOptions.UseRoslynLatest, CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, - CompilerOptions.UseMcs, - CompilerOptions.Optimize | CompilerOptions.UseMcs + CompilerOptions.UseMcs2_6_4, + CompilerOptions.Optimize | CompilerOptions.UseMcs2_6_4, + CompilerOptions.UseMcs5_23, + CompilerOptions.Optimize | CompilerOptions.UseMcs5_23 }; static readonly CompilerOptions[] roslynOnlyOptions = @@ -286,7 +288,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void UnsafeCode([ValueSource(nameof(defaultOptions))] CompilerOptions options) { - if (options.HasFlag(CompilerOptions.UseMcs)) + if (options.HasFlag(CompilerOptions.UseMcs2_6_4)) { Assert.Ignore("Decompiler bug with mono!"); } @@ -314,7 +316,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void YieldReturn([ValueSource(nameof(defaultOptions))] CompilerOptions options) { - if (options.HasFlag(CompilerOptions.UseMcs)) + if ((options & CompilerOptions.UseMcsMask) != 0) { Assert.Ignore("Decompiler bug with mono!"); } @@ -348,7 +350,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public void MiniJSON([ValueSource(nameof(defaultOptions))] CompilerOptions options) { - if (options.HasFlag(CompilerOptions.UseMcs)) + if (options.HasFlag(CompilerOptions.UseMcs2_6_4)) { Assert.Ignore("Decompiler bug with mono!"); } @@ -366,12 +368,12 @@ namespace ICSharpCode.Decompiler.Tests outputFile = Tester.CompileCSharp(Path.Combine(TestCasePath, testFileName), options, outputFileName: Path.Combine(TestCasePath, testOutputFileName)); string decompiledCodeFile = Tester.DecompileCSharp(outputFile.PathToAssembly, Tester.GetSettings(options)); - if (options.HasFlag(CompilerOptions.UseMcs)) + if ((options & CompilerOptions.UseMcsMask) != 0) { // For second pass, use roslyn instead of mcs. // mcs has some compiler bugs that cause it to not accept ILSpy-generated code, // for example when there's unreachable code due to other compiler bugs in the first mcs run. - options &= ~CompilerOptions.UseMcs; + options &= ~CompilerOptions.UseMcsMask; options |= CompilerOptions.UseRoslynLatest; // Also, add an .exe.config so that we consistently use the .NET 4.x runtime. File.WriteAllText(outputFile.PathToAssembly + ".config", @" diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs index 24c363e7d..efff7eba9 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.VB.cs @@ -40,7 +40,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var preprocessorSymbols = GetPreprocessorSymbols(flags).Select(symbol => new KeyValuePair(symbol, 1)).ToList(); - if (!flags.HasFlag(CompilerOptions.UseMcs)) + if ((flags & CompilerOptions.UseMcsMask) == 0) { CompilerResults results = new CompilerResults(new TempFileCollection()); results.PathToAssembly = outputFileName ?? Path.GetTempFileName(); diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index a86b6c0f3..689c0fe30 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -55,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers Force32Bit = 0x4, Library = 0x8, UseRoslyn1_3_2 = 0x10, - UseMcs = 0x20, + UseMcs2_6_4 = 0x20, ReferenceVisualBasic = 0x40, ReferenceCore = 0x80, GeneratePdb = 0x100, @@ -63,6 +63,8 @@ namespace ICSharpCode.Decompiler.Tests.Helpers UseRoslyn2_10_0 = 0x400, UseRoslyn3_11_0 = 0x800, UseRoslynLatest = 0x1000, + UseMcs5_23 = 0x2000, + UseMcsMask = UseMcs2_6_4 | UseMcs5_23, UseRoslynMask = UseRoslyn1_3_2 | UseRoslyn2_10_0 | UseRoslyn3_11_0 | UseRoslynLatest } @@ -335,9 +337,17 @@ namespace ICSharpCode.Decompiler.Tests.Helpers } } } - else if (flags.HasFlag(CompilerOptions.UseMcs)) + else if ((flags & CompilerOptions.UseMcsMask) != 0) { preprocessorSymbols.Add("MCS"); + if (flags.HasFlag(CompilerOptions.UseMcs2_6_4)) + { + preprocessorSymbols.Add("MCS2"); + } + if (flags.HasFlag(CompilerOptions.UseMcs5_23)) + { + preprocessorSymbols.Add("MCS5"); + } } else { @@ -361,7 +371,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers var preprocessorSymbols = GetPreprocessorSymbols(flags); - if (!flags.HasFlag(CompilerOptions.UseMcs)) + if ((flags & CompilerOptions.UseMcsMask) == 0) { CompilerResults results = new CompilerResults(new TempFileCollection()); results.PathToAssembly = outputFileName ?? Path.GetTempFileName(); @@ -476,7 +486,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers Assert.Ignore($"Compilation with mcs ignored: test directory '{testBasePath}' needs to be checked out separately." + Environment.NewLine + $"git clone https://github.com/icsharpcode/ILSpy-tests \"{testBasePath}\""); } - string mcsPath = Path.Combine(testBasePath, @"mcs\2.6.4\bin\gmcs.bat"); + string mcsPath = (flags & CompilerOptions.UseMcsMask) switch { + CompilerOptions.UseMcs5_23 => Path.Combine(testBasePath, @"mcs\5.23\bin\mcs.bat"), + _ => Path.Combine(testBasePath, @"mcs\2.6.4\bin\gmcs.bat") + }; string otherOptions = " -unsafe -o" + (flags.HasFlag(CompilerOptions.Optimize) ? "+ " : "- "); if (flags.HasFlag(CompilerOptions.Library)) @@ -544,7 +557,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers else { var settings = new DecompilerSettings(CSharp.LanguageVersion.CSharp5); - if (cscOptions.HasFlag(CompilerOptions.UseMcs)) + if ((cscOptions & CompilerOptions.UseMcsMask) != 0) { // we don't recompile with mcs but with roslyn, so we can use ref locals settings.UseRefLocalsForAccurateOrderOfEvaluation = true; @@ -609,8 +622,10 @@ namespace ICSharpCode.Decompiler.Tests.Helpers suffix += ".roslyn3"; if ((cscOptions & CompilerOptions.UseRoslynLatest) != 0) suffix += ".roslyn"; - if ((cscOptions & CompilerOptions.UseMcs) != 0) - suffix += ".mcs"; + if ((cscOptions & CompilerOptions.UseMcs2_6_4) != 0) + suffix += ".mcs2"; + if ((cscOptions & CompilerOptions.UseMcs5_23) != 0) + suffix += ".mcs5"; return suffix; } diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 4c817a233..e01d6dfdc 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -127,8 +127,10 @@ namespace ICSharpCode.Decompiler.Tests CompilerOptions.Optimize | CompilerOptions.UseRoslyn3_11_0, CompilerOptions.UseRoslynLatest, CompilerOptions.Optimize | CompilerOptions.UseRoslynLatest, - CompilerOptions.UseMcs, - CompilerOptions.Optimize | CompilerOptions.UseMcs + CompilerOptions.UseMcs2_6_4, + CompilerOptions.Optimize | CompilerOptions.UseMcs2_6_4, + CompilerOptions.UseMcs5_23, + CompilerOptions.Optimize | CompilerOptions.UseMcs5_23 }; [Test] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs index 19e61af25..7a89332a6 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs @@ -507,7 +507,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public void ForEachBreakWhenFound(string name, ref StringComparison output) { -#if MCS +#if MCS2 foreach (int value in Enum.GetValues(typeof(StringComparison))) { if (((StringComparison)value).ToString() == name) @@ -516,6 +516,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty break; } } +#elif MCS5 + foreach (int value in Enum.GetValues(typeof(StringComparison))) + { + StringComparison stringComparison = (StringComparison)value; + if (stringComparison.ToString() == name) + { + output = (StringComparison)value; + break; + } + } #else foreach (StringComparison value in Enum.GetValues(typeof(StringComparison))) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs index 4a80ba55c..8ae658ae7 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/IntroduceUsingDeclarations.cs @@ -163,7 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { foreach (var v in function.Variables) { - if (v.Kind != IL.VariableKind.Parameter) + if (v.Kind != IL.VariableKind.Parameter && v.Name != null) resolver = resolver.AddVariable(new DefaultVariable(v.Type, v.Name)); } } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs index 73d23329b..724aaf51c 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs @@ -172,7 +172,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow newBody.SortBlocks(deleteUnreachableBlocks: true); function.CheckInvariant(ILPhase.Normal); - if (!isCompiledWithMono) + if (isCompiledWithMono) + { + CleanSkipFinallyBodies(function); + } + else { DecompileFinallyBlocks(); ReconstructTryFinallyBlocks(function); @@ -181,8 +185,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow context.Step("Translate fields to local accesses", function); TranslateFieldsToLocalAccess(function, function, fieldToParameterMap, isCompiledWithMono); - CleanSkipFinallyBodies(function); - // On mono, we still need to remove traces of the state variable(s): if (isCompiledWithMono) { @@ -1230,8 +1232,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow { return; // only mono-compiled code uses skipFinallyBodies } - context.StepStartGroup("CleanSkipFinallyBodies", function); - Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(function.Body as BlockContainer); + context.StepStartGroup("CleanFinallyBlocks", function); if (skipFinallyBodies.StoreInstructions.Count != 0 || skipFinallyBodies.AddressCount != 0) { // misdetected another variable as doFinallyBodies? @@ -1241,7 +1242,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow } foreach (var tryFinally in function.Descendants.OfType()) { - entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(tryFinally.FinallyBlock as BlockContainer); + if (!(tryFinally.FinallyBlock is BlockContainer container)) + continue; + Block entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container); if (entryPoint?.Instructions[0] is IfInstruction ifInst) { if (ifInst.Condition.MatchLogicNot(out var logicNotArg) && logicNotArg.MatchLdLoc(skipFinallyBodies)) @@ -1250,10 +1253,41 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow // condition will always be true now that we're using 'yield' instructions entryPoint.Instructions[0] = ifInst.TrueInst; entryPoint.Instructions.RemoveRange(1, entryPoint.Instructions.Count - 1); + // find new entryPoint after the old one was modified + entryPoint = AsyncAwaitDecompiler.GetBodyEntryPoint(container); } } + if (entryPoint?.Instructions.Count == 2 + && IsCallToMonoFinallyMethod(entryPoint.Instructions[0] as Call, out var finallyMethod) + && entryPoint.Instructions[1].MatchLeave((BlockContainer)tryFinally.FinallyBlock)) + { + context.Step("Inline " + finallyMethod.FullName + " into finally", tryFinally); + var finallyFunction = CreateILAst((MethodDefinitionHandle)finallyMethod.MetadataToken, context); + tryFinally.FinallyBlock = finallyFunction.Body; + var variables = finallyFunction.Variables.ToArray(); + finallyFunction.Variables.Clear(); + function.Variables.AddRange(variables); + } } context.StepEndGroup(keepIfEmpty: true); + + bool IsCallToMonoFinallyMethod(Call call, out IMethod finallyMethod) + { + finallyMethod = default; + if (call == null) + return false; + if (!(call.Arguments.Count == 1 && call.Arguments[0].MatchLdThis())) + return false; + if (!call.Method.Name.StartsWith("<>__Finally")) + return false; + ITypeDefinition declaringTypeDefinition = call.Method.DeclaringTypeDefinition; + if (declaringTypeDefinition.MetadataToken != this.enumeratorType) + return false; + if (declaringTypeDefinition.ParentModule.PEFile.Metadata != metadata) + return false; + finallyMethod = call.Method; + return !call.Method.MetadataToken.IsNil; + } } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index 2539a8fe3..cefe2c261 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -715,8 +715,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms foreach (var section in sectionsWithoutLabels) { - if (!section.Body.Match(defaultSection.Body).Success) - return false; defaultSection.Labels = defaultSection.Labels.UnionWith(section.Labels); if (section.HasNullLabel) defaultSection.HasNullLabel = true; diff --git a/ILSpy-tests b/ILSpy-tests index aa8f1197e..6f8860e42 160000 --- a/ILSpy-tests +++ b/ILSpy-tests @@ -1 +1 @@ -Subproject commit aa8f1197e6a513bcc10bcc38ec7d2143d27a2246 +Subproject commit 6f8860e420b54bdfd726ec3c58a4d178416f9156