Browse Source

Merge pull request #900 from icsharpcode/fsharp_using

Implement F# Using: Fixes #670: [Feature Request] Detect F# using patterns when decompiling to C#
pull/940/merge
Siegfried Pammer 8 years ago committed by GitHub
parent
commit
f5bc3e17a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs
  2. 64
      ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs
  3. 64
      ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs
  4. 87
      ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs
  5. 99
      ICSharpCode.Decompiler.Tests/FSharpPatterns/ToolLocator.cs
  6. 5
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  7. 12
      ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs
  8. 0
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs
  9. 58
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.cs
  10. 0
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il
  11. 58
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.cs
  12. 0
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il
  13. 17
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  14. 2
      ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs
  15. 28
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

30
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpPatternTests.cs

@ -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);
}
}
}

64
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.cs

@ -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);
}
}

64
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.cs

@ -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);
}
}

87
ICSharpCode.Decompiler.Tests/FSharpPatterns/TestHelpers.cs

@ -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<string> ilasm = new Lazy<string>(() => ToolLocator.FindTool("ilasm.exe"));
static Lazy<string> ildasm = new Lazy<string>(() => 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 `<StartupCode$tmp6D55>` where the part after tmp is randomly generated.
// remove this from the ast to simplify the diff
var startupCodeNode = decompiler.SyntaxTree.Children.OfType<NamespaceDeclaration>().SingleOrDefault(d => d.Name.StartsWith("<StartupCode$", StringComparison.Ordinal));
if (startupCodeNode != null)
startupCodeNode.Remove();
decompiler.GenerateCode(new PlainTextOutput(output));
var fullCSharpCode = output.ToString();
CodeAssert.AreEqual(expectedCSharpCode, output.ToString());
}
finally
{
File.Delete(asmFilePath);
File.Delete(Path.ChangeExtension(asmFilePath, ".pdb"));
}
}
}
}

99
ICSharpCode.Decompiler.Tests/FSharpPatterns/ToolLocator.cs

@ -1,99 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.FSharpPatterns
{
public class ToolLocator
{
public static string FindTool(string fileName)
{
var allPaths = FindPathForDotNetFramework().Concat(FindPathForWindowsSdk());
return allPaths.Select(dir => Path.Combine(dir, fileName)).FirstOrDefault(File.Exists);
}
private static IEnumerable<string> 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<string> 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;
}
}
}
}
}

5
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -45,6 +45,9 @@
<ItemGroup> <ItemGroup>
<None Include="TestCases\Correctness\Jmp.il" /> <None Include="TestCases\Correctness\Jmp.il" />
<None Include="TestCases\ILPretty\FSharpUsing.fs" />
<None Include="TestCases\ILPretty\FSharpUsing_Debug.il" />
<None Include="TestCases\ILPretty\FSharpUsing_Release.il" />
<None Include="TestCases\Correctness\BitNot.il" /> <None Include="TestCases\Correctness\BitNot.il" />
<None Include="TestCases\Correctness\Readme.txt" /> <None Include="TestCases\Correctness\Readme.txt" />
</ItemGroup> </ItemGroup>
@ -53,6 +56,8 @@
<Compile Include="DataFlowTest.cs" /> <Compile Include="DataFlowTest.cs" />
<Compile Include="TestCases\Correctness\LINQRaytracer.cs" /> <Compile Include="TestCases\Correctness\LINQRaytracer.cs" />
<Compile Include="TestCases\Pretty\DelegateConstruction.cs" /> <Compile Include="TestCases\Pretty\DelegateConstruction.cs" />
<None Include="TestCases\ILPretty\FSharpUsing_Debug.cs" />
<None Include="TestCases\ILPretty\FSharpUsing_Release.cs" />
<Compile Include="Helpers\CodeAssert.cs" /> <Compile Include="Helpers\CodeAssert.cs" />
<Compile Include="Helpers\SdkUtility.cs" /> <Compile Include="Helpers\SdkUtility.cs" />
<Compile Include="Helpers\RemoveCompilerAttribute.cs" /> <Compile Include="Helpers\RemoveCompilerAttribute.cs" />

12
ICSharpCode.Decompiler.Tests/ILPrettyTestRunner.cs

@ -42,6 +42,18 @@ namespace ICSharpCode.Decompiler.Tests
Run(); Run();
} }
[Test]
public void FSharpUsing_Debug()
{
Run();
}
[Test]
public void FSharpUsing_Release()
{
Run();
}
void Run([CallerMemberName] string testName = null) void Run([CallerMemberName] string testName = null)
{ {
var ilFile = Path.Combine(TestCasePath, testName + ".il"); var ilFile = Path.Combine(TestCasePath, testName + ".il");

0
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs → ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing.fs

58
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);
}
}

0
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Debug.il → ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Debug.il

58
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);
}
}

0
ICSharpCode.Decompiler.Tests/FSharpPatterns/FSharpUsing.fs.Release.il → ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpUsing_Release.il

17
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -172,10 +172,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) { if (DetectExitPoints.CompatibleExitInstruction(trueExitInst, falseExitInst)) {
// if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint; // if (...) { ...; goto exitPoint; } goto nextBlock; nextBlock: ...; goto exitPoint;
// -> if (...) { ... } else { ... } goto exitPoint; // -> if (...) { ... } else { ... } goto exitPoint;
context.Step("Inline block as else-branch", ifInst);
targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1); // the else block is not empty or nop-only:
targetBlock.Remove(); if (targetBlock.Children.Any(inst => !(inst is Nop) && inst != falseExitInst)) {
ifInst.FalseInst = targetBlock; 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; exitInst = block.Instructions[block.Instructions.Count - 1] = falseExitInst;
Block trueBlock = ifInst.TrueInst as Block; Block trueBlock = ifInst.TrueInst as Block;
if (trueBlock != null) { if (trueBlock != null) {

2
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 (block.Instructions[i].MatchStLoc(out v, out copiedExpr)) {
if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) { if (v.IsSingleDefinition && v.LoadCount == 0 && v.Kind == VariableKind.StackSlot) {
// dead store to stack // dead store to stack
if (copiedExpr.Flags == InstructionFlags.None) { if (SemanticHelper.IsPure(copiedExpr.Flags)) {
// no-op -> delete // no-op -> delete
context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]); context.Step("remove dead store to stack: no-op -> delete", block.Instructions[i]);
block.Instructions.RemoveAt(i--); block.Instructions.RemoveAt(i--);

28
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.FlowAnalysis;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
@ -37,13 +39,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
v.HasInitialValue = false; v.HasInitialValue = false;
} }
} }
if (function.IsIterator || function.IsAsync) { // Remove dead stores to variables that are never read from.
// In yield return + async, the C# compiler tends to store null/default(T) to variables // If the stored value has some side-effect, the value is unwrapped.
// when the variable goes out of scope. Remove such useless stores. // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler.
foreach (var v in function.Variables) { // In yield return + async, the C# compiler tends to store null/default(T) to variables
if (v.Kind == VariableKind.Local && v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 0) { // when the variable goes out of scope.
if (v.StoreInstructions[0] is StLoc stloc && (stloc.Value.MatchLdNull() || stloc.Value is DefaultValue) && stloc.Parent is Block block) { var variableQueue = new Queue<ILVariable>(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<StLoc>().ToArray()) {
if (stloc.Parent is Block block) {
if (SemanticHelper.IsPure(stloc.Value.Flags)) {
block.Instructions.Remove(stloc); block.Instructions.Remove(stloc);
} else {
stloc.ReplaceWith(stloc.Value);
}
if (stloc.Value is LdLoc ldloc) {
variableQueue.Enqueue(ldloc.Variable);
} }
} }
} }

Loading…
Cancel
Save