mirror of https://github.com/icsharpcode/ILSpy.git
10 changed files with 597 additions and 441 deletions
@ -0,0 +1,400 @@
@@ -0,0 +1,400 @@
|
||||
// Copyright (c) 2016 Daniel Grunwald
|
||||
//
|
||||
// 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.Collections; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Resources; |
||||
using System.Threading.Tasks; |
||||
using System.Xml; |
||||
using ICSharpCode.Decompiler.CSharp.Transforms; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.CSharp |
||||
{ |
||||
/// <summary>
|
||||
/// Decompiles an assembly into a visual studio project file.
|
||||
/// </summary>
|
||||
public class WholeProjectDecompiler |
||||
{ |
||||
#region Settings
|
||||
DecompilerSettings settings = new DecompilerSettings(); |
||||
|
||||
public DecompilerSettings Settings { |
||||
get { |
||||
return settings; |
||||
} |
||||
set { |
||||
if (value == null) |
||||
throw new ArgumentNullException(); |
||||
settings = value; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The MSBuild ProjectGuid to use for the new project.
|
||||
/// <c>null</c> to automatically generate a new GUID.
|
||||
/// </summary>
|
||||
public Guid? ProjectGuid { get; set; } |
||||
|
||||
public int MaxDegreeOfParallelism { get; set; } = Environment.ProcessorCount; |
||||
#endregion
|
||||
|
||||
// per-run members
|
||||
HashSet<string> directories = new HashSet<string>(Platform.FileNameComparer); |
||||
|
||||
/// <summary>
|
||||
/// The target directory that the decompiled files are written to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This field is set by DecompileProject() and protected so that overridden protected members
|
||||
/// can access it.
|
||||
/// </remarks>
|
||||
protected string targetDirectory; |
||||
|
||||
public void DecompileProject(ModuleDefinition moduleDefinition, string targetDirectory) |
||||
{ |
||||
string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(moduleDefinition.Assembly.Name.Name) + ".csproj"); |
||||
using (var writer = new StreamWriter(projectFileName)) { |
||||
DecompileProject(moduleDefinition, targetDirectory, writer); |
||||
} |
||||
} |
||||
|
||||
public void DecompileProject(ModuleDefinition moduleDefinition, string targetDirectory, TextWriter projectFileWriter) |
||||
{ |
||||
if (string.IsNullOrEmpty(targetDirectory)) { |
||||
throw new InvalidOperationException("Must set TargetDirectory"); |
||||
} |
||||
this.targetDirectory = targetDirectory; |
||||
directories.Clear(); |
||||
var files = WriteCodeFilesInProject(moduleDefinition).ToList(); |
||||
files.AddRange(WriteResourceFilesInProject(moduleDefinition)); |
||||
WriteProjectFile(projectFileWriter, files, moduleDefinition); |
||||
} |
||||
|
||||
#region WriteProjectFile
|
||||
void WriteProjectFile(TextWriter writer, IEnumerable<Tuple<string, string>> files, ModuleDefinition module) |
||||
{ |
||||
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; |
||||
string platformName = GetPlatformName(module); |
||||
Guid guid = this.ProjectGuid ?? Guid.NewGuid(); |
||||
using (XmlTextWriter w = new XmlTextWriter(writer)) { |
||||
w.Formatting = Formatting.Indented; |
||||
w.WriteStartDocument(); |
||||
w.WriteStartElement("Project", ns); |
||||
w.WriteAttributeString("ToolsVersion", "4.0"); |
||||
w.WriteAttributeString("DefaultTargets", "Build"); |
||||
|
||||
w.WriteStartElement("PropertyGroup"); |
||||
w.WriteElementString("ProjectGuid", guid.ToString("B").ToUpperInvariant()); |
||||
|
||||
w.WriteStartElement("Configuration"); |
||||
w.WriteAttributeString("Condition", " '$(Configuration)' == '' "); |
||||
w.WriteValue("Debug"); |
||||
w.WriteEndElement(); // </Configuration>
|
||||
|
||||
w.WriteStartElement("Platform"); |
||||
w.WriteAttributeString("Condition", " '$(Platform)' == '' "); |
||||
w.WriteValue(platformName); |
||||
w.WriteEndElement(); // </Platform>
|
||||
|
||||
switch (module.Kind) { |
||||
case ModuleKind.Windows: |
||||
w.WriteElementString("OutputType", "WinExe"); |
||||
break; |
||||
case ModuleKind.Console: |
||||
w.WriteElementString("OutputType", "Exe"); |
||||
break; |
||||
default: |
||||
w.WriteElementString("OutputType", "Library"); |
||||
break; |
||||
} |
||||
|
||||
w.WriteElementString("AssemblyName", module.Assembly.Name.Name); |
||||
bool useTargetFrameworkAttribute = false; |
||||
var targetFrameworkAttribute = module.Assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute"); |
||||
if (targetFrameworkAttribute != null && targetFrameworkAttribute.ConstructorArguments.Any()) { |
||||
string frameworkName = (string)targetFrameworkAttribute.ConstructorArguments[0].Value; |
||||
string[] frameworkParts = frameworkName.Split(','); |
||||
string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase)); |
||||
if (frameworkVersion != null) { |
||||
w.WriteElementString("TargetFrameworkVersion", frameworkVersion.Substring("Version=".Length)); |
||||
useTargetFrameworkAttribute = true; |
||||
} |
||||
string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase)); |
||||
if (frameworkProfile != null) |
||||
w.WriteElementString("TargetFrameworkProfile", frameworkProfile.Substring("Profile=".Length)); |
||||
} |
||||
if (!useTargetFrameworkAttribute) { |
||||
switch (module.Runtime) { |
||||
case TargetRuntime.Net_1_0: |
||||
w.WriteElementString("TargetFrameworkVersion", "v1.0"); |
||||
break; |
||||
case TargetRuntime.Net_1_1: |
||||
w.WriteElementString("TargetFrameworkVersion", "v1.1"); |
||||
break; |
||||
case TargetRuntime.Net_2_0: |
||||
w.WriteElementString("TargetFrameworkVersion", "v2.0"); |
||||
// TODO: Detect when .NET 3.0/3.5 is required
|
||||
break; |
||||
default: |
||||
w.WriteElementString("TargetFrameworkVersion", "v4.0"); |
||||
break; |
||||
} |
||||
} |
||||
w.WriteElementString("WarningLevel", "4"); |
||||
w.WriteElementString("AllowUnsafeBlocks", "True"); |
||||
|
||||
w.WriteEndElement(); // </PropertyGroup>
|
||||
|
||||
w.WriteStartElement("PropertyGroup"); // platform-specific
|
||||
w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' "); |
||||
w.WriteElementString("PlatformTarget", platformName); |
||||
w.WriteEndElement(); // </PropertyGroup> (platform-specific)
|
||||
|
||||
w.WriteStartElement("PropertyGroup"); // Debug
|
||||
w.WriteAttributeString("Condition", " '$(Configuration)' == 'Debug' "); |
||||
w.WriteElementString("OutputPath", "bin\\Debug\\"); |
||||
w.WriteElementString("DebugSymbols", "true"); |
||||
w.WriteElementString("DebugType", "full"); |
||||
w.WriteElementString("Optimize", "false"); |
||||
w.WriteEndElement(); // </PropertyGroup> (Debug)
|
||||
|
||||
w.WriteStartElement("PropertyGroup"); // Release
|
||||
w.WriteAttributeString("Condition", " '$(Configuration)' == 'Release' "); |
||||
w.WriteElementString("OutputPath", "bin\\Release\\"); |
||||
w.WriteElementString("DebugSymbols", "true"); |
||||
w.WriteElementString("DebugType", "pdbonly"); |
||||
w.WriteElementString("Optimize", "true"); |
||||
w.WriteEndElement(); // </PropertyGroup> (Release)
|
||||
|
||||
|
||||
w.WriteStartElement("ItemGroup"); // References
|
||||
foreach (AssemblyNameReference r in module.AssemblyReferences) { |
||||
if (r.Name != "mscorlib") { |
||||
w.WriteStartElement("Reference"); |
||||
w.WriteAttributeString("Include", r.Name); |
||||
if (!IsGacAssembly(r)) { |
||||
var asm = module.AssemblyResolver.Resolve(r); |
||||
if (asm != null) { |
||||
w.WriteElementString("HintPath", asm.MainModule.FullyQualifiedName); |
||||
} |
||||
} |
||||
w.WriteEndElement(); |
||||
} |
||||
} |
||||
w.WriteEndElement(); // </ItemGroup> (References)
|
||||
|
||||
foreach (IGrouping<string, string> gr in (from f in files group f.Item2 by f.Item1 into g orderby g.Key select g)) { |
||||
w.WriteStartElement("ItemGroup"); |
||||
foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) { |
||||
w.WriteStartElement(gr.Key); |
||||
w.WriteAttributeString("Include", file); |
||||
w.WriteEndElement(); |
||||
} |
||||
w.WriteEndElement(); |
||||
} |
||||
|
||||
w.WriteStartElement("Import"); |
||||
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"); |
||||
w.WriteEndElement(); |
||||
|
||||
w.WriteEndDocument(); |
||||
} |
||||
} |
||||
|
||||
protected virtual bool IsGacAssembly(AssemblyNameReference r) |
||||
{ |
||||
return false; |
||||
} |
||||
#endregion
|
||||
|
||||
#region WriteCodeFilesInProject
|
||||
protected virtual bool IncludeTypeWhenDecompilingProject(TypeDefinition type) |
||||
{ |
||||
if (type.Name == "<Module>" || CSharpDecompiler.MemberIsHidden(type, settings)) |
||||
return false; |
||||
if (type.Namespace == "XamlGeneratedNamespace" && type.Name == "GeneratedInternalTypeHelper") |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts) |
||||
{ |
||||
var decompiler = new CSharpDecompiler(ts, settings); |
||||
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); |
||||
return decompiler; |
||||
} |
||||
|
||||
IEnumerable<Tuple<string, string>> WriteAssemblyInfo(DecompilerTypeSystem ts) |
||||
{ |
||||
var decompiler = CreateDecompiler(ts); |
||||
decompiler.AstTransforms.Add(new RemoveCompilerGeneratedAssemblyAttributes()); |
||||
SyntaxTree syntaxTree = decompiler.DecompileModuleAndAssemblyAttributes(); |
||||
|
||||
const string prop = "Properties"; |
||||
if (directories.Add(prop)) |
||||
Directory.CreateDirectory(Path.Combine(targetDirectory, prop)); |
||||
string assemblyInfo = Path.Combine(targetDirectory, prop, "AssemblyInfo.cs"); |
||||
using (StreamWriter w = new StreamWriter(assemblyInfo)) { |
||||
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions)); |
||||
} |
||||
return new Tuple<string, string>[] { Tuple.Create("Compile", assemblyInfo) }; |
||||
} |
||||
|
||||
IEnumerable<Tuple<string, string>> WriteCodeFilesInProject(ModuleDefinition module) |
||||
{ |
||||
var files = module.Types.Where(IncludeTypeWhenDecompilingProject).GroupBy( |
||||
delegate(TypeDefinition type) { |
||||
string file = CleanUpFileName(type.Name) + ".cs"; |
||||
if (string.IsNullOrEmpty(type.Namespace)) { |
||||
return file; |
||||
} else { |
||||
string dir = CleanUpFileName(type.Namespace); |
||||
if (directories.Add(dir)) |
||||
Directory.CreateDirectory(Path.Combine(targetDirectory, dir)); |
||||
return Path.Combine(targetDirectory, dir, file); |
||||
} |
||||
}, StringComparer.OrdinalIgnoreCase).ToList(); |
||||
DecompilerTypeSystem ts = new DecompilerTypeSystem(module); |
||||
Parallel.ForEach( |
||||
files, |
||||
new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, |
||||
delegate(IGrouping<string, TypeDefinition> file) { |
||||
using (StreamWriter w = new StreamWriter(Path.Combine(targetDirectory, file.Key))) { |
||||
CSharpDecompiler decompiler = CreateDecompiler(ts); |
||||
var syntaxTree = decompiler.DecompileTypes(file.ToArray()); |
||||
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions)); |
||||
} |
||||
}); |
||||
return files.Select(f => Tuple.Create("Compile", f.Key)).Concat(WriteAssemblyInfo(ts)); |
||||
} |
||||
#endregion
|
||||
|
||||
#region WriteResourceFilesInProject
|
||||
protected virtual IEnumerable<Tuple<string, string>> WriteResourceFilesInProject(ModuleDefinition module) |
||||
{ |
||||
foreach (EmbeddedResource r in module.Resources.OfType<EmbeddedResource>()) { |
||||
Stream stream = r.GetResourceStream(); |
||||
stream.Position = 0; |
||||
|
||||
IEnumerable<DictionaryEntry> entries; |
||||
if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { |
||||
if (GetEntries(stream, out entries) && entries.All(e => e.Value is Stream)) { |
||||
foreach (var pair in entries) { |
||||
string fileName = Path.Combine(((string)pair.Key).Split('/').Select(p => CleanUpFileName(p)).ToArray()); |
||||
string dirName = Path.GetDirectoryName(fileName); |
||||
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) { |
||||
Directory.CreateDirectory(Path.Combine(targetDirectory, dirName)); |
||||
} |
||||
Stream entryStream = (Stream)pair.Value; |
||||
entryStream.Position = 0; |
||||
WriteResourceToFile(Path.Combine(targetDirectory, fileName), (string)pair.Key, entryStream); |
||||
} |
||||
} else { |
||||
stream.Position = 0; |
||||
string fileName = GetFileNameForResource(Path.ChangeExtension(r.Name, ".resource")); |
||||
WriteResourceToFile(fileName, r.Name, stream); |
||||
} |
||||
} else { |
||||
string fileName = GetFileNameForResource(r.Name); |
||||
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write)) { |
||||
stream.Position = 0; |
||||
stream.CopyTo(fs); |
||||
} |
||||
yield return Tuple.Create("EmbeddedResource", fileName); |
||||
} |
||||
} |
||||
} |
||||
|
||||
protected virtual IEnumerable<Tuple<string, string>> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) |
||||
{ |
||||
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write)) { |
||||
entryStream.CopyTo(fs); |
||||
} |
||||
yield return Tuple.Create("EmbeddedResource", fileName); |
||||
} |
||||
|
||||
string GetFileNameForResource(string fullName) |
||||
{ |
||||
string[] splitName = fullName.Split('.'); |
||||
string fileName = CleanUpFileName(fullName); |
||||
for (int i = splitName.Length - 1; i > 0; i--) { |
||||
string ns = string.Join(".", splitName, 0, i); |
||||
if (directories.Contains(ns)) { |
||||
string name = string.Join(".", splitName, i, splitName.Length - i); |
||||
fileName = Path.Combine(ns, CleanUpFileName(name)); |
||||
break; |
||||
} |
||||
} |
||||
return fileName; |
||||
} |
||||
|
||||
bool GetEntries(Stream stream, out IEnumerable<DictionaryEntry> entries) |
||||
{ |
||||
try { |
||||
entries = new ResourceSet(stream).Cast<DictionaryEntry>(); |
||||
return true; |
||||
} catch (ArgumentException) { |
||||
entries = null; |
||||
return false; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up a node name for use as a file name.
|
||||
/// </summary>
|
||||
public static string CleanUpFileName(string text) |
||||
{ |
||||
int pos = text.IndexOf(':'); |
||||
if (pos > 0) |
||||
text = text.Substring(0, pos); |
||||
pos = text.IndexOf('`'); |
||||
if (pos > 0) |
||||
text = text.Substring(0, pos); |
||||
text = text.Trim(); |
||||
foreach (char c in Path.GetInvalidFileNameChars()) |
||||
text = text.Replace(c, '-'); |
||||
return text; |
||||
} |
||||
|
||||
public static string GetPlatformName(ModuleDefinition module) |
||||
{ |
||||
switch (module.Architecture) { |
||||
case TargetArchitecture.I386: |
||||
if ((module.Attributes & ModuleAttributes.Preferred32Bit) == ModuleAttributes.Preferred32Bit) |
||||
return "AnyCPU"; |
||||
else if ((module.Attributes & ModuleAttributes.Required32Bit) == ModuleAttributes.Required32Bit) |
||||
return "x86"; |
||||
else |
||||
return "AnyCPU"; |
||||
case TargetArchitecture.AMD64: |
||||
return "x64"; |
||||
case TargetArchitecture.IA64: |
||||
return "Itanium"; |
||||
default: |
||||
return module.Architecture.ToString(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,131 +0,0 @@
@@ -1,131 +0,0 @@
|
||||
// Copyright (c) 2014 Daniel Grunwald
|
||||
//
|
||||
// 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.Diagnostics; |
||||
using ICSharpCode.Decompiler.IL; |
||||
using NUnit.Framework; |
||||
using ICSharpCode.Decompiler.Tests.Helpers; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests.ILTransforms |
||||
{ |
||||
/* |
||||
/// <summary>
|
||||
/// IL Inlining unit tests.
|
||||
/// </summary>
|
||||
public class InliningTests |
||||
{ |
||||
static ILFunction MakeFunction(ILInstruction body) |
||||
{ |
||||
var f = new ILFunction(null, body); |
||||
f.AddRef(); |
||||
return f; |
||||
} |
||||
|
||||
[Test] |
||||
public void Simple() |
||||
{ |
||||
var f = MakeFunction( |
||||
new Block { |
||||
Instructions = { |
||||
new LdcI4(1), |
||||
new LdcI4(2), |
||||
new Add(new Pop(StackType.I4), new Pop(StackType.I4), false, Sign.Signed) |
||||
} |
||||
}); |
||||
f.AcceptVisitor(new TransformingVisitor()); |
||||
Assert.AreEqual( |
||||
new Block { |
||||
Instructions = { |
||||
new Add(new LdcI4(1), new LdcI4(2), false, Sign.Signed) |
||||
} |
||||
}.ToString(), |
||||
f.Body.ToString() |
||||
); |
||||
} |
||||
|
||||
[Test] |
||||
public void SkipInlineBlocks() |
||||
{ |
||||
var f = MakeFunction( |
||||
new Block { |
||||
Instructions = { |
||||
new LdcI4(1), |
||||
new LdcI4(2), |
||||
new LdcI4(3), |
||||
new Call(TypeSystem.Action<int, int, int>()) { |
||||
Arguments = { |
||||
new Pop(StackType.I4), |
||||
new Block { FinalInstruction = new Pop(StackType.I4) }, |
||||
new Pop(StackType.I4), |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
f.AcceptVisitor(new TransformingVisitor()); |
||||
Assert.AreEqual( |
||||
new Block { |
||||
Instructions = { |
||||
new LdcI4(1), |
||||
new Call(TypeSystem.Action<int, int, int>()) { |
||||
Arguments = { |
||||
new LdcI4(2), |
||||
new Block { FinalInstruction = new Pop(StackType.I4) }, |
||||
new LdcI4(3) |
||||
} |
||||
} |
||||
} |
||||
}.ToString(), |
||||
f.Body.ToString() |
||||
); |
||||
} |
||||
|
||||
[Test, Ignore("Inline blocks are currently disabled")] |
||||
public void BuildInlineBlock() |
||||
{ |
||||
var f = MakeFunction( |
||||
new Block { |
||||
Instructions = { |
||||
new LdcI4(1), |
||||
new LdcI4(2), |
||||
new Call(TypeSystem.Action<int>()) { Arguments = { new Peek(StackType.I4) } }, |
||||
new Call(TypeSystem.Action<int>()) { Arguments = { new Peek(StackType.I4) } }, |
||||
new Call(TypeSystem.Action<int, int>()) { Arguments = { new Pop(StackType.I4), new Pop(StackType.I4) } } |
||||
} |
||||
}); |
||||
f.AcceptVisitor(new TransformingVisitor()); |
||||
Debug.WriteLine(f.ToString()); |
||||
Assert.AreEqual( |
||||
new Call(TypeSystem.Action<int, int>()) { |
||||
Arguments = { |
||||
new LdcI4(1), |
||||
new Block { |
||||
Instructions = { |
||||
new LdcI4(2), |
||||
new Call(TypeSystem.Action<int>()) { Arguments = { new Peek(StackType.I4) } }, |
||||
new Call(TypeSystem.Action<int>()) { Arguments = { new Peek(StackType.I4) } }, |
||||
}, |
||||
FinalInstruction = new Pop(StackType.I4) |
||||
} |
||||
} |
||||
}.ToString(), |
||||
f.Body.ToString() |
||||
); |
||||
} |
||||
} |
||||
*/ |
||||
} |
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2016 Daniel Grunwald
|
||||
//
|
||||
// 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.Diagnostics; |
||||
using System.IO; |
||||
using System.Text.RegularExpressions; |
||||
using ICSharpCode.Decompiler.CSharp; |
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.Decompiler.Tests |
||||
{ |
||||
public class RoundtripAssembly |
||||
{ |
||||
const string testDir = "C:\\temp\\ILSpy-test-assemblies"; |
||||
static readonly string msbuild = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "msbuild", "14.0", "bin", "msbuild.exe"); |
||||
static readonly string nunit = Path.Combine(testDir, "nunit", "nunit3-console.exe"); |
||||
|
||||
[Test] |
||||
public void Cecil_net45() |
||||
{ |
||||
Run("Mono.Cecil-net45", "Mono.Cecil.dll", "Mono.Cecil.Tests.dll"); |
||||
} |
||||
|
||||
void Run(string dir, string fileToRoundtrip, string fileToTest) |
||||
{ |
||||
if (!Directory.Exists(testDir)) { |
||||
Assert.Ignore($"Assembly-roundtrip test ignored: test directory '${testDir}' needs to be checked out seperately."); |
||||
} |
||||
string inputDir = Path.Combine(testDir, dir); |
||||
//RunTest(inputDir, fileToTest);
|
||||
string decompiledDir = inputDir + "-decompiled"; |
||||
string outputDir = inputDir + "-output"; |
||||
ClearDirectory(decompiledDir); |
||||
ClearDirectory(outputDir); |
||||
string projectFile = null; |
||||
foreach (string file in Directory.EnumerateFiles(inputDir, "*", SearchOption.AllDirectories)) { |
||||
if (!file.StartsWith(inputDir + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)) { |
||||
Assert.Fail($"Unexpected file name: ${file}"); |
||||
} |
||||
string relFile = file.Substring(inputDir.Length + 1); |
||||
Directory.CreateDirectory(Path.Combine(outputDir, Path.GetDirectoryName(relFile))); |
||||
if (relFile.Equals(fileToRoundtrip, StringComparison.OrdinalIgnoreCase)) { |
||||
Console.WriteLine($"Decompiling {fileToRoundtrip}..."); |
||||
Stopwatch w = Stopwatch.StartNew(); |
||||
DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); |
||||
resolver.AddSearchDirectory(inputDir); |
||||
var module = ModuleDefinition.ReadModule(file, new ReaderParameters { AssemblyResolver = resolver }); |
||||
var decompiler = new WholeProjectDecompiler(); |
||||
decompiler.DecompileProject(module, decompiledDir); |
||||
Console.WriteLine($"Decompiled {fileToRoundtrip} in {w.Elapsed.TotalSeconds:f2}"); |
||||
projectFile = Path.Combine(decompiledDir, module.Assembly.Name.Name + ".csproj"); |
||||
} else { |
||||
File.Copy(file, Path.Combine(outputDir, relFile)); |
||||
} |
||||
} |
||||
Assert.IsNotNull(projectFile, $"Could not find {fileToRoundtrip}"); |
||||
|
||||
Compile(projectFile); |
||||
RunTest(outputDir, fileToTest); |
||||
} |
||||
|
||||
static void ClearDirectory(string dir) |
||||
{ |
||||
Directory.CreateDirectory(dir); |
||||
foreach (string subdir in Directory.EnumerateDirectories(dir)) { |
||||
Directory.Delete(subdir, true); |
||||
} |
||||
foreach (string file in Directory.EnumerateFiles(dir)) { |
||||
File.Delete(file); |
||||
} |
||||
} |
||||
|
||||
static void Compile(string projectFile) |
||||
{ |
||||
var info = new ProcessStartInfo(msbuild); |
||||
info.Arguments = $"/nologo /v:minimal \"{projectFile}\""; |
||||
info.CreateNoWindow = true; |
||||
info.UseShellExecute = false; |
||||
info.RedirectStandardOutput = true; |
||||
Console.WriteLine($"\"{info.FileName}\" {info.Arguments}"); |
||||
using (var p = Process.Start(info)) { |
||||
Regex errorRegex = new Regex(@"^[\w\d.\\]+\(\d+,\d+\):"); |
||||
string suffix = $" [{projectFile}]"; |
||||
string line; |
||||
while ((line = p.StandardOutput.ReadLine()) != null) { |
||||
if (line.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) { |
||||
line = line.Substring(0, line.Length - suffix.Length); |
||||
} |
||||
Match m = errorRegex.Match(line); |
||||
if (m.Success) { |
||||
// Make path absolute so that it gets hyperlinked
|
||||
line = Path.GetDirectoryName(projectFile) + Path.DirectorySeparatorChar + line; |
||||
} |
||||
Console.WriteLine(line); |
||||
} |
||||
p.WaitForExit(); |
||||
Assert.AreEqual(0, p.ExitCode, "Compilation failed"); |
||||
} |
||||
} |
||||
|
||||
static void RunTest(string outputDir, string fileToTest) |
||||
{ |
||||
var info = new ProcessStartInfo(nunit); |
||||
info.WorkingDirectory = outputDir; |
||||
info.Arguments = $"\"{fileToTest}\""; |
||||
info.CreateNoWindow = true; |
||||
info.UseShellExecute = false; |
||||
info.RedirectStandardOutput = true; |
||||
Console.WriteLine($"\"{info.FileName}\" {info.Arguments}"); |
||||
using (var p = Process.Start(info)) { |
||||
string line; |
||||
while ((line = p.StandardOutput.ReadLine()) != null) { |
||||
Console.WriteLine(line); |
||||
} |
||||
p.WaitForExit(); |
||||
Assert.AreEqual(0, p.ExitCode, "Test execution failed"); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
The files in this folder are correctness tests for the decompiler. |
||||
|
||||
The NUnit class running these tests is ../TestRunner.cs. |
||||
|
||||
We: |
||||
* compile/assemble a test case (call the result "executable 1") |
||||
* decompile "executable 1" to C# ("decompiled.cs") |
||||
* compile decompiled.cs, resulting in "executable 2" |
||||
* run both executable and compare their output (exit code, stdout, stderr) |
||||
|
||||
We repeat the steps above with a few different compiler options (/o+ or not; /debug or not). |
||||
|
||||
The tests pass if the code compiles without error and produces the same output. |
||||
The tests do not care at all if the resulting code is pretty, or if any high-level constructs like closures were detected. |
Loading…
Reference in new issue