diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs deleted file mode 100644 index 7980d6579..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace ICSharpCode.Decompiler.Tests.FSharpPatterns -{ - [TestFixture] - public class FSharpPatternTests - { - [Test] - public void FSharpUsingDecompilesToCSharpUsing_Debug() - { - var ilCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Debug.il"); - var csharpCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Debug.cs"); - TestHelpers.RunIL(ilCode, csharpCode); - } - - [Test] - public void FSharpUsingDecompilesToCSharpUsing_Release() - { - var ilCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Release.il"); - var csharpCode = TestHelpers.FuzzyReadResource("FSharpUsing.fs.Release.cs"); - TestHelpers.RunIL(ilCode, csharpCode); - } - } -} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs deleted file mode 100644 index 3c62c0cac..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.IO; - -public static class FSharpUsingPatterns -{ - public static void sample1() - { - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)1); - } - } - - public static void sample2() - { - Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)2); - Console.WriteLine("some text"); - } - } - - public static void sample3() - { - Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte((byte)3); - } - Console.WriteLine("some text"); - } - - public static void sample4() - { - Console.WriteLine("some text"); - int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); - } - int firstByte = num; - Console.WriteLine("read:" + firstByte.ToString()); - } - - public static void sample5() - { - Console.WriteLine("some text"); - int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); - } - int firstByte = num; - int num3; - using (FileStream fs = File.OpenRead("x.txt")) - { - int num2 = fs.ReadByte(); - num3 = fs.ReadByte(); - } - int secondByte = num3; - Console.WriteLine("read: {0}, {1}", firstByte, secondByte); - } -} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs deleted file mode 100644 index c9d091847..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.IO; - -public static class FSharpUsingPatterns -{ - public static void sample1() - { - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(1); - } - } - - public static void sample2() - { - Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(2); - Console.WriteLine("some text"); - } - } - - public static void sample3() - { - Console.WriteLine("some text"); - using (FileStream fs = File.Create("x.txt")) - { - fs.WriteByte(3); - } - Console.WriteLine("some text"); - } - - public static void sample4() - { - Console.WriteLine("some text"); - int num; - using (FileStream fs = File.OpenRead("x.txt")) - { - num = fs.ReadByte(); - } - int firstByte = num; - Console.WriteLine("read:" + firstByte.ToString()); - } - - public static void sample5() - { - Console.WriteLine("some text"); - int secondByte; - using (FileStream fs = File.OpenRead("x.txt")) - { - secondByte = fs.ReadByte(); - } - int firstByte = secondByte; - int num2; - using (FileStream fs = File.OpenRead("x.txt")) - { - int num = fs.ReadByte(); - num2 = fs.ReadByte(); - } - secondByte = num2; - Console.WriteLine("read: {0}, {1}", firstByte, secondByte); - } -} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs b/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs deleted file mode 100644 index dc6cd0bc4..000000000 --- a/ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs +++ /dev/null @@ -1,87 +0,0 @@ -using ICSharpCode.Decompiler.Ast; -using ICSharpCode.Decompiler.Tests.Helpers; -using ICSharpCode.NRefactory.CSharp; -using Mono.Cecil; -using NUnit.Framework; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace ICSharpCode.Decompiler.Tests.FSharpPatterns -{ - public class TestHelpers - { - public static string FuzzyReadResource(string resourceName) - { - var asm = Assembly.GetExecutingAssembly(); - var allResources = asm.GetManifestResourceNames(); - var fullResourceName = allResources.Single(r => r.EndsWith(resourceName, StringComparison.OrdinalIgnoreCase)); - return new StreamReader(asm.GetManifestResourceStream(fullResourceName)).ReadToEnd(); - } - - static Lazy ilasm = new Lazy(() => ToolLocator.FindTool("ilasm.exe")); - static Lazy ildasm = new Lazy(() => ToolLocator.FindTool("ildasm.exe")); - - public static string CompileIL(string source) - { - if (ilasm.Value == null) - Assert.NotNull(ilasm.Value, "Could not find ILASM.exe"); - var tmp = Path.GetTempFileName(); - File.Delete(tmp); - var sourceFile = Path.ChangeExtension(tmp, ".il"); - File.WriteAllText(sourceFile, source); - var asmFile = Path.ChangeExtension(sourceFile, ".dll"); - - var args = string.Format("{0} /dll /debug /output:{1}", sourceFile, asmFile); - using (var proc = Process.Start(new ProcessStartInfo(ilasm.Value, args) { UseShellExecute = false, })) - { - proc.WaitForExit(); - Assert.AreEqual(0, proc.ExitCode); - } - - File.Delete(sourceFile); - Assert.True(File.Exists(asmFile), "Assembly File does not exist"); - return asmFile; - } - - public static void RunIL(string ilCode, string expectedCSharpCode) - { - var asmFilePath = CompileIL(ilCode); - CompareAssemblyAgainstCSharp(expectedCSharpCode, asmFilePath); - } - - private static void CompareAssemblyAgainstCSharp(string expectedCSharpCode, string asmFilePath) - { - var module = ModuleDefinition.ReadModule(asmFilePath); - try - { - try { module.ReadSymbols(); } catch { } - AstBuilder decompiler = new AstBuilder(new DecompilerContext(module)); - decompiler.AddAssembly(module); - new Helpers.RemoveCompilerAttribute().Run(decompiler.SyntaxTree); - StringWriter output = new StringWriter(); - - // the F# assembly contains a namespace `` where the part after tmp is randomly generated. - // remove this from the ast to simplify the diff - var startupCodeNode = decompiler.SyntaxTree.Children.OfType().SingleOrDefault(d => d.Name.StartsWith(" Path.Combine(dir, fileName)).FirstOrDefault(File.Exists); - } - - private static IEnumerable FindPathForWindowsSdk() - { - string[] windowsSdkPaths = new[] - { - @"Microsoft SDKs\Windows\v8.0A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v8.0A\bin\", - @"Microsoft SDKs\Windows\v8.0\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v8.0\bin\", - @"Microsoft SDKs\Windows\v7.1A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v7.1A\bin\", - @"Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\", - @"Microsoft SDKs\Windows\v7.0A\bin\", - @"Microsoft SDKs\Windows\v6.1A\bin\", - @"Microsoft SDKs\Windows\v6.0A\bin\", - @"Microsoft SDKs\Windows\v6.0\bin\", - @"Microsoft.NET\FrameworkSDK\bin" - }; - - foreach (var possiblePath in windowsSdkPaths) - { - string fullPath = string.Empty; - - // Check alternate program file paths as well as 64-bit versions. - if (Environment.Is64BitProcess) - { - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath, "x64"); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath, "x64"); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - } - - private static IEnumerable FindPathForDotNetFramework() - { - string[] frameworkPaths = new[] - { - @"Microsoft.NET\Framework\v4.0.30319", - @"Microsoft.NET\Framework\v2.0.50727" - }; - - foreach (var possiblePath in frameworkPaths) - { - string fullPath = string.Empty; - - if (Environment.Is64BitProcess) - { - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath.Replace(@"\Framework\", @"\Framework64\")); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - - fullPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), possiblePath); - if (Directory.Exists(fullPath)) - { - yield return fullPath; - } - } - } - } -} diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 1affff8f5..cd71918fa 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -45,6 +45,9 @@ + + + @@ -53,6 +56,8 @@ + + diff --git a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs index 6b5a1aaec..799ea3924 100644 --- a/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs @@ -42,6 +42,18 @@ namespace ICSharpCode.Decompiler.Tests Run(); } + [Test] + public void FSharpUsing_Debug() + { + Run(); + } + + [Test] + public void FSharpUsing_Release() + { + Run(); + } + void Run([CallerMemberName] string testName = null) { var ilFile = Path.Combine(TestCasePath, testName + ".il"); diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs new file mode 100644 index 000000000..972149116 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +public static class FSharpUsingPatterns +{ + public static void sample1() + { + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)1); + } + } + + public static void sample2() + { + Console.WriteLine("some text"); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)2); + Console.WriteLine("some text"); + } + } + + public static void sample3() + { + Console.WriteLine("some text"); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte((byte)3); + } + Console.WriteLine("some text"); + } + + public static void sample4() + { + Console.WriteLine("some text"); + int num = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); + } + int num2 = num; + Console.WriteLine("read:" + num2.ToString()); + } + + public static void sample5() + { + Console.WriteLine("some text"); + int num = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); + } + int num2 = num; + int num3 = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + fileStream.ReadByte(); + num3 = fileStream.ReadByte(); + } + int num4 = num3; + Console.WriteLine("read: {0}, {1}", num2, num4); + } +} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.il rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il diff --git a/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs new file mode 100644 index 000000000..9585b51a1 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; + +public static class FSharpUsingPatterns +{ + public static void sample1() + { + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(1); + } + } + + public static void sample2() + { + Console.WriteLine("some text"); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(2); + Console.WriteLine("some text"); + } + } + + public static void sample3() + { + Console.WriteLine("some text"); + using (FileStream fileStream = File.Create("x.txt")) { + fileStream.WriteByte(3); + } + Console.WriteLine("some text"); + } + + public static void sample4() + { + Console.WriteLine("some text"); + int num = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); + } + int num2 = num; + Console.WriteLine("read:" + num2.ToString()); + } + + public static void sample5() + { + Console.WriteLine("some text"); + int num = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + num = fileStream.ReadByte(); + } + int num2 = num; + int num3 = default(int); + using (FileStream fileStream = File.OpenRead("x.txt")) { + fileStream.ReadByte(); + num3 = fileStream.ReadByte(); + } + num = num3; + Console.WriteLine("read: {0}, {1}", num2, num); + } +} diff --git a/ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.il b/ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il similarity index 100% rename from ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.il rename to ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index 7a05ecd92..657a5f7a0 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -172,10 +172,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) { // if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint; // -> if (...) { ... } else { ... } goto exitPoint; - context.Step("Inline block as else-branch", ifInst); - targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); - targetBlock.Remove(); - ifInst.FalseInst = targetBlock; + + // the else block is not empty or nop-only: + if (targetBlock.Children.Any(inst => !(inst is Nop) && inst != falseExitInst)) { + context.Step("Inline block as else-branch", ifInst); + targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); + targetBlock.Remove(); + ifInst.FalseInst = targetBlock; + } else { + // the else block is empty or nop-only and can be safely removed: + context.Step("Remove empty else-branch", ifInst); + targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); + targetBlock.Remove(); + } exitInst = block.Instructions[block.Instructions.Count - 1] = falseExitInst; Block trueBlock = ifInst.TrueInst as Block; if (trueBlock != null) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs index 1ef40d116..071f59d63 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -49,7 +49,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { // dead store to stack - if (copiedExpr.Flags == InstructionFlags.None) { + if (SemanticHelper.IsPure(copiedExpr.Flags)) { // no-op -> delete context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); block.Instructions.RemoveAt(i--); diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs index b8dc74578..e5d1831bf 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Collections.Generic; +using System.Linq; using ICSharpCode.Decompiler.FlowAnalysis; namespace ICSharpCode.Decompiler.IL.Transforms @@ -37,13 +39,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms v.HasInitialValue = false; } } - if (function.IsIterator || function.IsAsync) { - // In yield return + async, the C# compiler tends to store null/default(T) to variables - // when the variable goes out of scope. Remove such useless stores. - foreach (var v in function.Variables) { - if (v.Kind == VariableKind.Local && v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 0) { - if (v.StoreInstructions[0] is StLoc stloc && (stloc.Value.MatchLdNull() || stloc.Value is DefaultValue) && stloc.Parent is Block block) { + // Remove dead stores to variables that are never read from. + // If the stored value has some side-effect, the value is unwrapped. + // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. + // In yield return + async, the C# compiler tends to store null/default(T) to variables + // when the variable goes out of scope. + var variableQueue = new Queue(function.Variables); + while (variableQueue.Count > 0) { + var v = variableQueue.Dequeue(); + if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot) + continue; + if (v.LoadCount != 0 || v.AddressCount != 0) + continue; + foreach (var stloc in v.StoreInstructions.OfType().ToArray()) { + if (stloc.Parent is Block block) { + if (SemanticHelper.IsPure(stloc.Value.Flags)) { block.Instructions.Remove(stloc); + } else { + stloc.ReplaceWith(stloc.Value); + } + if (stloc.Value is LdLoc ldloc) { + variableQueue.Enqueue(ldloc.Variable); } } }