Browse Source

Move whole-project-decompilation into ICSharpCode.Decompiler and create test case.

pull/728/head
Daniel Grunwald 9 years ago
parent
commit
0efc55d594
  1. 400
      ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
  2. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  3. 3
      ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 131
      ICSharpCode.Decompiler/Tests/ILTransforms/InliningTests.cs
  5. 138
      ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs
  6. 14
      ICSharpCode.Decompiler/Tests/TestCases/Readme.txt
  7. 2
      ICSharpCode.Decompiler/Tests/TestRunner.cs
  8. 252
      ILSpy/Languages/CSharpLanguage.cs
  9. 85
      ILSpy/Languages/Language.cs
  10. 12
      ILSpy/TextView/DecompilerTextView.cs

400
ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs

@ -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
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -82,6 +82,7 @@ @@ -82,6 +82,7 @@
<Compile Include="CSharp\Transforms\IAstTransform.cs" />
<Compile Include="CSharp\Transforms\IntroduceUnsafeModifier.cs" />
<Compile Include="CSharp\Transforms\ReplaceMethodCallsWithOperators.cs" />
<Compile Include="CSharp\WholeProjectDecompiler.cs" />
<Compile Include="FlowAnalysis\ControlFlowNode.cs" />
<Compile Include="FlowAnalysis\DataFlowVisitor.cs" />
<Compile Include="FlowAnalysis\DefiniteAssignmentVisitor.cs" />

3
ICSharpCode.Decompiler/Tests/ICSharpCode.Decompiler.Tests.csproj

@ -89,6 +89,7 @@ @@ -89,6 +89,7 @@
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="TestCases\Readme.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\cecil\Mono.Cecil.csproj">
@ -113,8 +114,8 @@ @@ -113,8 +114,8 @@
<Compile Include="Helpers\RemoveCompilerAttribute.cs" />
<Compile Include="Helpers\Tester.cs" />
<Compile Include="Helpers\TypeSystemHelper.cs" />
<Compile Include="ILTransforms\InliningTests.cs" />
<Compile Include="Loops.cs" />
<Compile Include="RoundtripAssembly.cs" />
<Compile Include="TestCases\CompoundAssignment.cs" />
<Compile Include="TestCases\ControlFlow.cs" />
<Compile Include="TestCases\Conversions.cs" />

131
ICSharpCode.Decompiler/Tests/ILTransforms/InliningTests.cs

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

138
ICSharpCode.Decompiler/Tests/RoundtripAssembly.cs

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

14
ICSharpCode.Decompiler/Tests/TestCases/Readme.txt

@ -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.

2
ICSharpCode.Decompiler/Tests/TestRunner.cs

@ -23,6 +23,8 @@ namespace ICSharpCode.Decompiler.Tests @@ -23,6 +23,8 @@ namespace ICSharpCode.Decompiler.Tests
.Select(m => m.Name)
.ToArray();
foreach (var file in new DirectoryInfo(TestCasePath).EnumerateFiles()) {
if (file.Extension == ".txt")
continue;
var testName = Path.GetFileNameWithoutExtension(file.Name);
Assert.Contains(testName, testNames);
}

252
ILSpy/Languages/CSharpLanguage.cs

@ -17,10 +17,12 @@ @@ -17,10 +17,12 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Resources;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
@ -152,7 +154,7 @@ namespace ICSharpCode.ILSpy @@ -152,7 +154,7 @@ namespace ICSharpCode.ILSpy
}
}
}
*/
*/
public override void DecompileProperty(PropertyDefinition property, ITextOutput output, DecompilationOptions options)
{
@ -160,7 +162,7 @@ namespace ICSharpCode.ILSpy @@ -160,7 +162,7 @@ namespace ICSharpCode.ILSpy
CSharpDecompiler decompiler = CreateDecompiler(property.Module, options);
WriteCode(output, decompiler.Decompile(property));
}
/*
/*
public override void DecompileField(FieldDefinition field, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, TypeToString(field.DeclaringType, includeNamespace: true));
@ -208,7 +210,7 @@ namespace ICSharpCode.ILSpy @@ -208,7 +210,7 @@ namespace ICSharpCode.ILSpy
codeDomBuilder.AddMethod(ctor);
}
}
*/
*/
public override void DecompileEvent(EventDefinition ev, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, TypeToString(ev.DeclaringType, includeNamespace: true));
@ -223,7 +225,7 @@ namespace ICSharpCode.ILSpy @@ -223,7 +225,7 @@ namespace ICSharpCode.ILSpy
WriteCode(output, decompiler.Decompile(type));
}
/*
/*
void RunTransformsAndGenerateCode(AstBuilder astBuilder, ITextOutput output, DecompilationOptions options, IAstTransform additionalTransform = null)
{
astBuilder.RunTransformations(transformAbortCondition);
@ -261,25 +263,6 @@ namespace ICSharpCode.ILSpy @@ -261,25 +263,6 @@ namespace ICSharpCode.ILSpy
return module.Architecture.ToString();
}
}
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();
}
}
public static string GetRuntimeDisplayName(ModuleDefinition module)
{
@ -299,10 +282,9 @@ namespace ICSharpCode.ILSpy @@ -299,10 +282,9 @@ namespace ICSharpCode.ILSpy
public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
HashSet<string> directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var files = WriteCodeFilesInProject(assembly.ModuleDefinition, options, directories).ToList();
files.AddRange(WriteResourceFilesInProject(assembly, options, directories));
WriteProjectFile(new TextOutputWriter(output), files, assembly.ModuleDefinition);
var decompiler = new ILSpyWholeProjectDecompiler(assembly, options);
decompiler.ProjectGuid = App.CommandLineArguments.FixedGuid;
decompiler.DecompileProject(assembly.ModuleDefinition, options.SaveAsProjectDirectory, new TextOutputWriter(output));
} else {
base.DecompileAssembly(assembly, output, options);
output.WriteLine();
@ -341,199 +323,41 @@ namespace ICSharpCode.ILSpy @@ -341,199 +323,41 @@ namespace ICSharpCode.ILSpy
}
}
#region WriteProjectFile
void WriteProjectFile(TextWriter writer, IEnumerable<Tuple<string, string>> files, ModuleDefinition module)
class ILSpyWholeProjectDecompiler : WholeProjectDecompiler
{
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName = GetPlatformName(module);
Guid guid = App.CommandLineArguments.FixedGuid ?? 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 (GacInterop.FindAssemblyInNetGac(r) == null) {
var asm = module.AssemblyResolver.Resolve(r);
if (asm != null) {
w.WriteElementString("HintPath", asm.MainModule.FullyQualifiedName);
}
readonly LoadedAssembly assembly;
readonly DecompilationOptions options;
public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options)
{
this.assembly = assembly;
this.options = options;
base.Settings = options.DecompilerSettings;
}
protected override IEnumerable<Tuple<string, string>> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
{
if (fileName.EndsWith(".resource", StringComparison.OrdinalIgnoreCase)) {
using (ResourceReader reader = new ResourceReader(entryStream))
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write))
using (ResXResourceWriter writer = new ResXResourceWriter(fs)) {
foreach (DictionaryEntry entry in reader) {
writer.AddResource((string)entry.Key, entry.Value);
}
w.WriteEndElement();
}
return new[] { Tuple.Create("EmbeddedResource", fileName) };
}
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();
foreach (var handler in App.CompositionContainer.GetExportedValues<IResourceFileHandler>()) {
if (handler.CanHandle(fileName, options)) {
entryStream.Position = 0;
return new[] { Tuple.Create(handler.EntryType, handler.WriteResourceToFile(assembly, fileName, entryStream, options)) };
}
w.WriteEndElement();
}
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets");
w.WriteEndElement();
w.WriteEndDocument();
}
}
#endregion
#region WriteCodeFilesInProject
bool IncludeTypeWhenDecompilingProject(TypeDefinition type, DecompilationOptions options)
{
if (type.Name == "<Module>" || CSharpDecompiler.MemberIsHidden(type, options.DecompilerSettings))
return false;
if (type.Namespace == "XamlGeneratedNamespace" && type.Name == "GeneratedInternalTypeHelper")
return false;
return true;
}
IEnumerable<Tuple<string, string>> WriteAssemblyInfo(DecompilerTypeSystem ts, DecompilationOptions options, HashSet<string> directories)
{
// don't automatically load additional assemblies when an assembly node is selected in the tree view
using (LoadedAssembly.DisableAssemblyLoad())
{
CSharpDecompiler decompiler = new CSharpDecompiler(ts, options.DecompilerSettings);
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
decompiler.AstTransforms.Add(new RemoveCompilerGeneratedAssemblyAttributes());
SyntaxTree syntaxTree = decompiler.DecompileModuleAndAssemblyAttributes();
string prop = "Properties";
if (directories.Add("Properties"))
Directory.CreateDirectory(Path.Combine(options.SaveAsProjectDirectory, prop));
string assemblyInfo = Path.Combine(prop, "AssemblyInfo" + this.FileExtension);
using (StreamWriter w = new StreamWriter(Path.Combine(options.SaveAsProjectDirectory, assemblyInfo))) {
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, options.DecompilerSettings.CSharpFormattingOptions));
}
return new Tuple<string, string>[] { Tuple.Create("Compile", assemblyInfo) };
return base.WriteResourceToFile(fileName, resourceName, entryStream);
}
}
IEnumerable<Tuple<string, string>> WriteCodeFilesInProject(ModuleDefinition module, DecompilationOptions options, HashSet<string> directories)
{
var files = module.Types.Where(t => IncludeTypeWhenDecompilingProject(t, options)).GroupBy(
delegate(TypeDefinition type) {
string file = TextView.DecompilerTextView.CleanUpName(type.Name) + this.FileExtension;
if (string.IsNullOrEmpty(type.Namespace)) {
return file;
} else {
string dir = TextView.DecompilerTextView.CleanUpName(type.Namespace);
if (directories.Add(dir))
Directory.CreateDirectory(Path.Combine(options.SaveAsProjectDirectory, dir));
return Path.Combine(dir, file);
}
}, StringComparer.OrdinalIgnoreCase).ToList();
DecompilerTypeSystem ts = new DecompilerTypeSystem(module);
Parallel.ForEach(
files,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
delegate(IGrouping<string, TypeDefinition> file) {
using (StreamWriter w = new StreamWriter(Path.Combine(options.SaveAsProjectDirectory, file.Key))) {
CSharpDecompiler decompiler = new CSharpDecompiler(ts, options.DecompilerSettings);
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
var syntaxTree = decompiler.DecompileTypes(file.ToArray());
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, options.DecompilerSettings.CSharpFormattingOptions));
}
});
return files.Select(f => Tuple.Create("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, options, directories));
}
#endregion
/*
/*
AstBuilder CreateAstBuilder(DecompilationOptions options, ModuleDefinition currentModule = null, TypeDefinition currentType = null, bool isSingleMember = false)
{
if (currentModule == null)
@ -579,7 +403,7 @@ namespace ICSharpCode.ILSpy @@ -579,7 +403,7 @@ namespace ICSharpCode.ILSpy
astType.AcceptVisitor(new CSharpOutputVisitor(w, FormattingOptionsFactory.CreateAllman()));
return w.ToString();
}
*/
*/
public override string FormatPropertyName(PropertyDefinition property, bool? isIndexer)
{
if (property == null)
@ -623,7 +447,7 @@ namespace ICSharpCode.ILSpy @@ -623,7 +447,7 @@ namespace ICSharpCode.ILSpy
{
return showAllMembers || !AstBuilder.MemberIsHidden(member, new DecompilationOptions().DecompilerSettings);
}
*/
*/
public override MemberReference GetOriginalCodeLocation(MemberReference member)
{
if (showAllMembers || !DecompilerSettingsPanel.CurrentDecompilerSettings.AnonymousMethods)

85
ILSpy/Languages/Language.cs

@ -166,90 +166,5 @@ namespace ICSharpCode.ILSpy @@ -166,90 +166,5 @@ namespace ICSharpCode.ILSpy
{
return member;
}
#region WriteResourceFilesInProject
protected virtual IEnumerable<Tuple<string, string>> WriteResourceFilesInProject(LoadedAssembly assembly, DecompilationOptions options, HashSet<string> directories)
{
foreach (EmbeddedResource r in assembly.ModuleDefinition.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 => TextView.DecompilerTextView.CleanUpName(p)).ToArray());
string dirName = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName)) {
Directory.CreateDirectory(Path.Combine(options.SaveAsProjectDirectory, dirName));
}
Stream entryStream = (Stream)pair.Value;
bool handled = false;
foreach (var handler in App.CompositionContainer.GetExportedValues<IResourceFileHandler>()) {
if (handler.CanHandle(fileName, options)) {
handled = true;
entryStream.Position = 0;
yield return Tuple.Create(handler.EntryType, handler.WriteResourceToFile(assembly, fileName, entryStream, options));
break;
}
}
if (!handled) {
using (FileStream fs = new FileStream(Path.Combine(options.SaveAsProjectDirectory, fileName), FileMode.Create, FileAccess.Write)) {
entryStream.Position = 0;
entryStream.CopyTo(fs);
}
yield return Tuple.Create("EmbeddedResource", fileName);
}
}
} else {
stream.Position = 0;
string fileName = GetFileNameForResource(Path.ChangeExtension(r.Name, ".resx"), directories);
using (ResourceReader reader = new ResourceReader(stream))
using (FileStream fs = new FileStream(Path.Combine(options.SaveAsProjectDirectory, fileName), FileMode.Create, FileAccess.Write))
using (ResXResourceWriter writer = new ResXResourceWriter(fs)) {
foreach (DictionaryEntry entry in reader) {
writer.AddResource((string)entry.Key, entry.Value);
}
}
yield return Tuple.Create("EmbeddedResource", fileName);
}
} else {
string fileName = GetFileNameForResource(r.Name, directories);
using (FileStream fs = new FileStream(Path.Combine(options.SaveAsProjectDirectory, fileName), FileMode.Create, FileAccess.Write)) {
stream.Position = 0;
stream.CopyTo(fs);
}
yield return Tuple.Create("EmbeddedResource", fileName);
}
}
}
string GetFileNameForResource(string fullName, HashSet<string> directories)
{
string[] splitName = fullName.Split('.');
string fileName = TextView.DecompilerTextView.CleanUpName(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, TextView.DecompilerTextView.CleanUpName(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
}
}

12
ILSpy/TextView/DecompilerTextView.cs

@ -42,6 +42,7 @@ using ICSharpCode.AvalonEdit.Highlighting.Xshd; @@ -42,6 +42,7 @@ using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.ILSpy.AvalonEdit;
using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpy.TreeNodes;
@ -684,16 +685,7 @@ namespace ICSharpCode.ILSpy.TextView @@ -684,16 +685,7 @@ namespace ICSharpCode.ILSpy.TextView
/// </summary>
internal static string CleanUpName(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;
return WholeProjectDecompiler.CleanUpFileName(text);
}
#endregion

Loading…
Cancel
Save