Browse Source

Rearrange project writing logic

The WholeProjectDecompiler shall not have too many responsibilities.
pull/2031/head
dymanoid 5 years ago
parent
commit
fdef5d11c6
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 92
      ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs
  3. 41
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
  4. 49
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs
  5. 175
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
  6. 94
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs
  7. 129
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
  8. 226
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  9. 5
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -88,6 +88,7 @@ @@ -88,6 +88,7 @@
<Compile Include="..\ILSpy\DebugInfo\DebugInfoUtils.cs" Link="DebugInfoUtils.cs" />
<Compile Include="..\ILSpy\DebugInfo\PortableDebugInfoProvider.cs" Link="PortableDebugInfoProvider.cs" />
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="ProjectDecompiler\TargetFrameworkTests.cs" />
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\ILPretty\Issue1681.cs" />

92
ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
// Copyright (c) 2020 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 ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using NUnit.Framework;
namespace ICSharpCode.Decompiler.Tests
{
[TestFixture]
public sealed class TargetFrameworkTests
{
[TestCase(-1)]
[TestCase(0)]
[TestCase(1)]
[TestCase(99)]
[TestCase(int.MinValue)]
public void VerifyThrowsForInvalidVersion(int invalidVersion)
{
// Arrange - nothing
// Act
void CreateInstance() => new TargetFramework(identifier: null, invalidVersion, profile: null);
// Assert
Assert.Throws<ArgumentException>(CreateInstance);
}
[TestCase(100, "v1.0")]
[TestCase(102, "v1.0.2")]
[TestCase(130, "v1.3")]
[TestCase(145, "v1.4.5")]
[TestCase(1670, "v16.7")]
[TestCase(1800, "v18.0")]
public void VerifyVersion(int version, string expectedVersion)
{
// Arrange - nothing
// Act
var targetFramework = new TargetFramework(identifier: null, version, profile: null);
// Assert
Assert.AreEqual(version, targetFramework.VersionNumber);
Assert.AreEqual(expectedVersion, targetFramework.VersionString);
}
[Test]
public void VerifyPortableLibrary()
{
// Arrange
const string identifier = ".NETPortable";
// Act
var targetFramework = new TargetFramework(identifier, 100, profile: null);
// Assert
Assert.IsTrue(targetFramework.IsPortableClassLibrary);
Assert.AreEqual(identifier, targetFramework.Identifier);
}
[Test]
[Pairwise]
public void VerifyIdentifierAndProfile(
[Values(null, "", ".NETFramework")] string identifier,
[Values(null, "", ".Client")] string profile)
{
// Arrange - nothing
// Act
var targetFramework = new TargetFramework(identifier, 100, profile);
// Assert
Assert.AreEqual(identifier, targetFramework.Identifier);
Assert.AreEqual(profile, targetFramework.Profile);
}
}
}

41
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
// Copyright (c) 2020 Siegfried Pammer
//
// 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.Collections.Generic;
using System.IO;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// An interface for a service that creates and writes a project file structure
/// for a specific module being decompiled.
/// </summary>
interface IProjectFileWriter
{
/// <summary>
/// Writes the content of a new project file for the specified <paramref name="module"/> being decompiled.
/// </summary>
/// <param name="target">The target to write to.</param>
/// <param name="project">The information about the project being created.</param>
/// <param name="files">A collection of source files to be included into the project, each item is a pair
/// of the project entry type and the file path.</param>
/// <param name="module">The module being decompiled.</param>
void Write(TextWriter target, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module);
}
}

49
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
// Copyright (c) 2020 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 ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// An interface that provides common information for a project being decompiled to.
/// </summary>
interface IProjectInfoProvider
{
/// <summary>
/// Gets the assembly resolver active for the project.
/// </summary>
IAssemblyResolver AssemblyResolver { get; }
/// <summary>
/// Gets the C# language version of the project.
/// </summary>
LanguageVersion LanguageVersion { get; }
/// <summary>
/// Gets the unique ID of the project.
/// </summary>
Guid ProjectGuid { get; }
/// <summary>
/// Gets the name of the key file being used for strong name signing. Can be null if no file is available.
/// </summary>
string StrongNameKeyFile { get; }
}
}

175
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs

@ -0,0 +1,175 @@ @@ -0,0 +1,175 @@
// Copyright (c) 2020 Siegfried Pammer
//
// 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.Generic;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Xml;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// A <see cref="IProjectFileWriter"/> implementation that creates the projects in the default format.
/// </summary>
sealed class ProjectFileWriterDefault : IProjectFileWriter
{
/// <summary>
/// Creates a new instance of the <see cref="ProjectFileWriterDefault"/> class.
/// </summary>
/// <returns>A new instance of the <see cref="ProjectFileWriterDefault"/> class.</returns>
public static IProjectFileWriter Create() => new ProjectFileWriterDefault();
/// <inheritdoc />
public void Write(
TextWriter target,
IProjectInfoProvider project,
IEnumerable<(string itemType, string fileName)> files,
PEFile module)
{
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName = TargetServices.GetPlatformName(module);
var targetFramework = TargetServices.DetectTargetFramework(module);
List<Guid> typeGuids = new List<Guid>();
if (targetFramework.IsPortableClassLibrary)
typeGuids.Add(ProjectTypeGuids.PortableLibrary);
typeGuids.Add(ProjectTypeGuids.CSharpWindows);
using (XmlTextWriter w = new XmlTextWriter(target)) {
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", project.ProjectGuid.ToString("B").ToUpperInvariant());
w.WriteElementString("ProjectTypeGuids", string.Join(";", typeGuids.Select(g => g.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>
if (module.Reader.PEHeaders.IsDll) {
w.WriteElementString("OutputType", "Library");
} else {
switch (module.Reader.PEHeaders.PEHeader.Subsystem) {
case Subsystem.WindowsGui:
w.WriteElementString("OutputType", "WinExe");
break;
case Subsystem.WindowsCui:
w.WriteElementString("OutputType", "Exe");
break;
default:
w.WriteElementString("OutputType", "Library");
break;
}
}
w.WriteElementString("LangVersion", project.LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.'));
w.WriteElementString("AssemblyName", module.Name);
if (targetFramework.Identifier != null)
w.WriteElementString("TargetFrameworkIdentifier", targetFramework.Identifier);
if (targetFramework.VersionString != null)
w.WriteElementString("TargetFrameworkVersion", targetFramework.VersionString);
if (targetFramework.Profile != null)
w.WriteElementString("TargetFrameworkProfile", targetFramework.Profile);
w.WriteElementString("WarningLevel", "4");
w.WriteElementString("AllowUnsafeBlocks", "True");
if (project.StrongNameKeyFile != null) {
w.WriteElementString("SignAssembly", "True");
w.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(project.StrongNameKeyFile));
}
w.WriteEndElement(); // </PropertyGroup>
w.WriteStartElement("PropertyGroup"); // platform-specific
w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' ");
w.WriteElementString("PlatformTarget", platformName);
if (targetFramework.VersionNumber > 400 && platformName == "AnyCPU" && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) == 0) {
w.WriteElementString("Prefer32Bit", "false");
}
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 (var r in module.AssemblyReferences) {
if (r.Name != "mscorlib") {
w.WriteStartElement("Reference");
w.WriteAttributeString("Include", r.Name);
var asm = project.AssemblyResolver.Resolve(r);
if (asm != null && !project.AssemblyResolver.IsGacAssembly(r)) {
w.WriteElementString("HintPath", asm.FileName);
}
w.WriteEndElement();
}
}
w.WriteEndElement(); // </ItemGroup> (References)
foreach (IGrouping<string, string> gr in from f in files group f.fileName by f.itemType 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();
}
if (targetFramework.IsPortableClassLibrary) {
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildExtensionsPath32)\\Microsoft\\Portable\\$(TargetFrameworkVersion)\\Microsoft.Portable.CSharp.targets");
w.WriteEndElement();
} else {
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets");
w.WriteEndElement();
}
w.WriteEndDocument();
}
}
}
}

94
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
// Copyright (c) 2020 Siegfried Pammer
//
// 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.Text;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// A class describing the target framework of a module.
/// </summary>
sealed class TargetFramework
{
const string DotNetPortableIdentifier = ".NETPortable";
/// <summary>
/// Initializes a new instance of the <see cref="TargetFramework"/> class.
/// </summary>
/// <param name="identifier">The framework identifier string. Can be null.</param>
/// <param name="version">The framework version string. Must be greater than 100 (where 100 corresponds to v1.0).</param>
/// <param name="profile">The framework profile. Can be null.</param>
public TargetFramework(string identifier, int version, string profile)
{
if (version < 100) {
throw new ArgumentException("The version number must be greater than or equal to 100", nameof(version));
}
Identifier = identifier;
VersionNumber = version;
VersionString = "v" + GetVersionString(version);
Profile = profile;
IsPortableClassLibrary = identifier == DotNetPortableIdentifier;
}
/// <summary>
/// Gets the target framework identifier. Can be null if not defined.
/// </summary>
public string Identifier { get; }
/// <summary>
/// Gets the target framework version, e.g. "v4.5".
/// </summary>
public string VersionString { get; }
/// <summary>
/// Gets the target framework version as integer (multiplied by 100), e.g. 450.
/// </summary>
public int VersionNumber { get; }
/// <summary>
/// Gets the target framework profile. Can be null if not set or not available.
/// </summary>
public string Profile { get; }
/// <summary>
/// Gets a value indicating whether the target is a portable class library (PCL).
/// </summary>
public bool IsPortableClassLibrary { get; }
static string GetVersionString(int version)
{
int major = version / 100;
int minor = version % 100 / 10;
int patch = version % 10;
var versionBuilder = new StringBuilder(8);
versionBuilder.Append(major);
versionBuilder.Append('.');
versionBuilder.Append(minor);
if (patch != 0) {
versionBuilder.Append('.');
versionBuilder.Append(patch);
}
return versionBuilder.ToString();
}
}
}

129
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
// Copyright (c) 2020 Siegfried Pammer
//
// 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.Linq;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
/// <summary>
/// Helper services for determining the target framework and platform of a module.
/// </summary>
static class TargetServices
{
const string VersionToken = "Version=";
const string ProfileToken = "Profile=";
/// <summary>
/// Gets the <see cref="TargetFramework"/> for the specified <paramref name="module"/>.
/// </summary>
/// <param name="module">The module to get the target framework description for. Cannot be null.</param>
/// <returns>A new instance of the <see cref="TargetFramework"/> class that describes the specified <paramref name="module"/>.
/// </returns>
public static TargetFramework DetectTargetFramework(PEFile module)
{
if (module is null) {
throw new ArgumentNullException(nameof(module));
}
int versionNumber;
switch (module.GetRuntime()) {
case TargetRuntime.Net_1_0:
versionNumber = 100;
break;
case TargetRuntime.Net_1_1:
versionNumber = 110;
break;
case TargetRuntime.Net_2_0:
versionNumber = 200;
// TODO: Detect when .NET 3.0/3.5 is required
break;
default:
versionNumber = 400;
break;
}
string targetFrameworkIdentifier = null;
string targetFrameworkProfile = null;
string targetFramework = module.DetectTargetFrameworkId();
if (!string.IsNullOrEmpty(targetFramework)) {
string[] frameworkParts = targetFramework.Split(',');
targetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase) && !a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase));
string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase));
if (frameworkVersion != null) {
versionNumber = int.Parse(frameworkVersion.Substring(VersionToken.Length + 1).Replace(".", ""));
if (versionNumber < 100) versionNumber *= 10;
}
string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase));
if (frameworkProfile != null)
targetFrameworkProfile = frameworkProfile.Substring(ProfileToken.Length);
}
return new TargetFramework(targetFrameworkIdentifier, versionNumber, targetFrameworkProfile);
}
/// <summary>
/// Gets the string representation (name) of the target platform of the specified <paramref name="module"/>.
/// </summary>
/// <param name="module">The module to get the target framework description for. Cannot be null.</param>
/// <returns>The platform name, e.g. "AnyCPU" or "x86".</returns>
public static string GetPlatformName(PEFile module)
{
if (module is null) {
throw new ArgumentNullException(nameof(module));
}
var headers = module.Reader.PEHeaders;
var architecture = headers.CoffHeader.Machine;
var characteristics = headers.CoffHeader.Characteristics;
var corflags = headers.CorHeader.Flags;
switch (architecture) {
case Machine.I386:
if ((corflags & CorFlags.Prefers32Bit) != 0)
return "AnyCPU";
if ((corflags & CorFlags.Requires32Bit) != 0)
return "x86";
// According to ECMA-335, II.25.3.3.1 CorFlags.Requires32Bit and Characteristics.Bit32Machine must be in sync
// for assemblies containing managed code. However, this is not true for C++/CLI assemblies.
if ((corflags & CorFlags.ILOnly) == 0 && (characteristics & Characteristics.Bit32Machine) != 0)
return "x86";
return "AnyCPU";
case Machine.Amd64:
return "x64";
case Machine.IA64:
return "Itanium";
default:
return architecture.ToString();
}
}
}
}

226
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -21,7 +21,6 @@ using System.Collections.Generic; @@ -21,7 +21,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
@ -29,9 +28,7 @@ using ICSharpCode.Decompiler.TypeSystem; @@ -29,9 +28,7 @@ using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using System.Threading;
using System.Text;
using System.Reflection.PortableExecutable;
using System.Reflection.Metadata;
using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions;
using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
@ -42,7 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -42,7 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// <summary>
/// Decompiles an assembly into a visual studio project file.
/// </summary>
public class WholeProjectDecompiler
public class WholeProjectDecompiler : IProjectInfoProvider
{
#region Settings
/// <summary>
@ -106,6 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -106,6 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
ProjectGuid = projectGuid;
AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
DebugInfoProvider = debugInfoProvider;
projectWriter = ProjectFileWriterDefault.Create();
}
// per-run members
@ -120,6 +118,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -120,6 +118,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </remarks>
protected string targetDirectory;
readonly IProjectFileWriter projectWriter;
public void DecompileProject(PEFile moduleDefinition, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken))
{
string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(moduleDefinition.Name) + ".csproj");
@ -140,197 +140,13 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -140,197 +140,13 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
if (StrongNameKeyFile != null) {
File.Copy(StrongNameKeyFile, Path.Combine(targetDirectory, Path.GetFileName(StrongNameKeyFile)));
}
return WriteProjectFile(projectFileWriter, files, moduleDefinition);
}
#region WriteProjectFile
ProjectId WriteProjectFile(TextWriter writer, IEnumerable<(string itemType, string fileName)> files, Metadata.PEFile module)
{
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName = GetPlatformName(module);
Guid guid = this.ProjectGuid;
var targetFramework = DetectTargetFramework(module);
List<Guid> typeGuids = new List<Guid>();
if (targetFramework.IsPortableClassLibrary)
typeGuids.Add(ProjectTypeGuids.PortableLibrary);
typeGuids.Add(ProjectTypeGuids.CSharpWindows);
// TODO: .NET core support
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.WriteElementString("ProjectTypeGuids", string.Join(";", typeGuids.Select(g => g.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>
if (module.Reader.PEHeaders.IsDll) {
w.WriteElementString("OutputType", "Library");
} else {
switch (module.Reader.PEHeaders.PEHeader.Subsystem) {
case Subsystem.WindowsGui:
w.WriteElementString("OutputType", "WinExe");
break;
case Subsystem.WindowsCui:
w.WriteElementString("OutputType", "Exe");
break;
default:
w.WriteElementString("OutputType", "Library");
break;
}
}
w.WriteElementString("LangVersion", LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.'));
w.WriteElementString("AssemblyName", module.Name);
if (targetFramework.TargetFrameworkIdentifier != null)
w.WriteElementString("TargetFrameworkIdentifier", targetFramework.TargetFrameworkIdentifier);
if (targetFramework.TargetFrameworkVersion != null)
w.WriteElementString("TargetFrameworkVersion", targetFramework.TargetFrameworkVersion);
if (targetFramework.TargetFrameworkProfile != null)
w.WriteElementString("TargetFrameworkProfile", targetFramework.TargetFrameworkProfile);
w.WriteElementString("WarningLevel", "4");
w.WriteElementString("AllowUnsafeBlocks", "True");
if (StrongNameKeyFile != null) {
w.WriteElementString("SignAssembly", "True");
w.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(StrongNameKeyFile));
}
w.WriteEndElement(); // </PropertyGroup>
w.WriteStartElement("PropertyGroup"); // platform-specific
w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' ");
w.WriteElementString("PlatformTarget", platformName);
if (targetFramework.VersionNumber > 400 && platformName == "AnyCPU" && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) == 0) {
w.WriteElementString("Prefer32Bit", "false");
}
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 (var r in module.AssemblyReferences) {
if (r.Name != "mscorlib") {
w.WriteStartElement("Reference");
w.WriteAttributeString("Include", r.Name);
var asm = AssemblyResolver.Resolve(r);
if (!AssemblyResolver.IsGacAssembly(r)) {
if (asm != null) {
w.WriteElementString("HintPath", asm.FileName);
}
}
w.WriteEndElement();
}
}
w.WriteEndElement(); // </ItemGroup> (References)
foreach (IGrouping<string, string> gr in (from f in files group f.fileName by f.itemType 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();
}
if (targetFramework.IsPortableClassLibrary) {
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildExtensionsPath32)\\Microsoft\\Portable\\$(TargetFrameworkVersion)\\Microsoft.Portable.CSharp.targets");
w.WriteEndElement();
} else {
w.WriteStartElement("Import");
w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets");
w.WriteEndElement();
}
w.WriteEndDocument();
}
return new ProjectId(platformName, guid, ProjectTypeGuids.CSharpWindows);
projectWriter.Write(projectFileWriter, this, files, moduleDefinition);
string platformName = TargetServices.GetPlatformName(moduleDefinition);
return new ProjectId(platformName, ProjectGuid, ProjectTypeGuids.CSharpWindows);
}
struct TargetFramework
{
public string TargetFrameworkIdentifier;
public string TargetFrameworkVersion;
public string TargetFrameworkProfile;
public int VersionNumber;
public bool IsPortableClassLibrary => TargetFrameworkIdentifier == ".NETPortable";
}
private TargetFramework DetectTargetFramework(PEFile module)
{
TargetFramework result = default;
switch (module.GetRuntime()) {
case Metadata.TargetRuntime.Net_1_0:
result.VersionNumber = 100;
result.TargetFrameworkVersion = "v1.0";
break;
case Metadata.TargetRuntime.Net_1_1:
result.VersionNumber = 110;
result.TargetFrameworkVersion = "v1.1";
break;
case Metadata.TargetRuntime.Net_2_0:
result.VersionNumber = 200;
result.TargetFrameworkVersion = "v2.0";
// TODO: Detect when .NET 3.0/3.5 is required
break;
default:
result.VersionNumber = 400;
result.TargetFrameworkVersion = "v4.0";
break;
}
string targetFramework = module.DetectTargetFrameworkId();
if (!string.IsNullOrEmpty(targetFramework)) {
string[] frameworkParts = targetFramework.Split(',');
result.TargetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase) && !a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase));
string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase));
if (frameworkVersion != null) {
result.TargetFrameworkVersion = frameworkVersion.Substring("Version=".Length);
result.VersionNumber = int.Parse(frameworkVersion.Substring("Version=v".Length).Replace(".", ""));
if (result.VersionNumber < 100) result.VersionNumber *= 10;
}
string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase));
if (frameworkProfile != null)
result.TargetFrameworkProfile = frameworkProfile.Substring("Profile=".Length);
}
return result;
}
#endregion
#region WriteCodeFilesInProject
protected virtual bool IncludeTypeWhenDecompilingProject(PEFile module, TypeDefinitionHandle type)
{
@ -571,32 +387,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -571,32 +387,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
return false;
}
}
public static string GetPlatformName(Metadata.PEFile module)
{
var headers = module.Reader.PEHeaders;
var architecture = headers.CoffHeader.Machine;
var characteristics = headers.CoffHeader.Characteristics;
var corflags = headers.CorHeader.Flags;
switch (architecture) {
case Machine.I386:
if ((corflags & CorFlags.Prefers32Bit) != 0)
return "AnyCPU";
if ((corflags & CorFlags.Requires32Bit) != 0)
return "x86";
// According to ECMA-335, II.25.3.3.1 CorFlags.Requires32Bit and Characteristics.Bit32Machine must be in sync
// for assemblies containing managed code. However, this is not true for C++/CLI assemblies.
if ((corflags & CorFlags.ILOnly) == 0 && (characteristics & Characteristics.Bit32Machine) != 0)
return "x86";
return "AnyCPU";
case Machine.Amd64:
return "x64";
case Machine.IA64:
return "Itanium";
default:
return architecture.ToString();
}
}
}
public readonly struct DecompilationProgress

5
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -61,9 +61,14 @@ @@ -61,9 +61,14 @@
<Compile Include="CSharp\Annotations.cs" />
<Compile Include="CSharp\CallBuilder.cs" />
<Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\ProjectDecompiler\IProjectFileWriter.cs" />
<Compile Include="CSharp\OutputVisitor\GenericGrammarAmbiguityVisitor.cs" />
<Compile Include="CSharp\ProjectDecompiler\IProjectInfoProvider.cs" />
<Compile Include="CSharp\ProjectDecompiler\ProjectFileWriterDefault.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetFramework.cs" />
<Compile Include="CSharp\ProjectDecompiler\TargetServices.cs" />
<Compile Include="IL\Transforms\IndexRangeTransform.cs" />
<Compile Include="CSharp\TranslatedStatement.cs" />
<Compile Include="DebugInfo\KnownGuids.cs" />

Loading…
Cancel
Save