Browse Source

Merge branch 'master' into ref

pull/1596/head
Siegfried Pammer 6 years ago committed by GitHub
parent
commit
23179c5dc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1047.cs
  2. 13
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs
  3. 10
      ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
  4. 3
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  5. 2
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  6. 3
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  7. 48
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  8. 4
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  9. 12
      ICSharpCode.Decompiler/IL/ILReader.cs
  10. 48
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  11. 1
      ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs
  12. 12
      ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs
  13. 11
      ICSharpCode.Decompiler/SRMExtensions.cs
  14. 56
      ICSharpCode.Decompiler/Solution/ProjectId.cs
  15. 55
      ICSharpCode.Decompiler/Solution/ProjectItem.cs
  16. 201
      ICSharpCode.Decompiler/Solution/SolutionCreator.cs
  17. 2
      ILSpy.BamlDecompiler/Handlers/Records/PropertyTypeReferenceHandler.cs
  18. 133
      ILSpy/Commands/SaveCodeContextMenuEntry.cs
  19. 2
      ILSpy/ILSpy.csproj
  20. 8
      ILSpy/Languages/CSharpLanguage.cs
  21. 5
      ILSpy/Languages/ILLanguage.cs
  22. 6
      ILSpy/Languages/Language.cs
  23. 1
      ILSpy/MainWindow.xaml
  24. 15
      ILSpy/MainWindow.xaml.cs
  25. 18
      ILSpy/Properties/Resources.Designer.cs
  26. 6
      ILSpy/Properties/Resources.resx
  27. 182
      ILSpy/SolutionWriter.cs
  28. 5
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  29. 1
      ILSpy/TreeNodes/ReferenceFolderTreeNode.cs

4
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1047.cs

@ -6,12 +6,8 @@ @@ -6,12 +6,8 @@
private void ProblemMethod()
{
IL_0000:
while (!dummy) {
}
return;
IL_0014:
goto IL_0000;
}
}
}

13
ICSharpCode.Decompiler.Tests/TestCases/Pretty/LocalFunctions.cs

@ -273,6 +273,19 @@ namespace LocalFunctions @@ -273,6 +273,19 @@ namespace LocalFunctions
}
});
}
public static IEnumerable<int> YieldReturn(int n)
{
return GetNumbers();
IEnumerable<int> GetNumbers()
{
for (int i = 0; i < n; i++) {
yield return i;
}
}
}
//public static void LocalFunctionInUsing()
//{
// using (MemoryStream memoryStream = new MemoryStream()) {

10
ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs

@ -35,6 +35,7 @@ using System.Reflection.Metadata; @@ -35,6 +35,7 @@ using System.Reflection.Metadata;
using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions;
using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
namespace ICSharpCode.Decompiler.CSharp
{
@ -101,7 +102,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -101,7 +102,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
public void DecompileProject(PEFile moduleDefinition, string targetDirectory, TextWriter projectFileWriter, CancellationToken cancellationToken = default(CancellationToken))
public ProjectId DecompileProject(PEFile moduleDefinition, string targetDirectory, TextWriter projectFileWriter, CancellationToken cancellationToken = default(CancellationToken))
{
if (string.IsNullOrEmpty(targetDirectory)) {
throw new InvalidOperationException("Must set TargetDirectory");
@ -110,7 +111,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -110,7 +111,7 @@ namespace ICSharpCode.Decompiler.CSharp
directories.Clear();
var files = WriteCodeFilesInProject(moduleDefinition, cancellationToken).ToList();
files.AddRange(WriteResourceFilesInProject(moduleDefinition));
WriteProjectFile(projectFileWriter, files, moduleDefinition);
return WriteProjectFile(projectFileWriter, files, moduleDefinition);
}
enum LanguageTargets
@ -120,11 +121,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -120,11 +121,12 @@ namespace ICSharpCode.Decompiler.CSharp
}
#region WriteProjectFile
void WriteProjectFile(TextWriter writer, IEnumerable<Tuple<string, string>> files, Metadata.PEFile module)
ProjectId WriteProjectFile(TextWriter writer, IEnumerable<Tuple<string, string>> files, Metadata.PEFile 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();
@ -281,6 +283,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -281,6 +283,8 @@ namespace ICSharpCode.Decompiler.CSharp
w.WriteEndDocument();
}
return new ProjectId(platformName, guid);
}
protected virtual bool IsGacAssembly(Metadata.IAssemblyReference r, Metadata.PEFile asm)

3
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -65,6 +65,9 @@ @@ -65,6 +65,9 @@
<Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="Solution\ProjectId.cs" />
<Compile Include="Solution\ProjectItem.cs" />
<Compile Include="Solution\SolutionCreator.cs" />
<Compile Include="CSharp\Syntax\AstNode.cs" />
<Compile Include="CSharp\Syntax\AstNodeCollection.cs" />
<Compile Include="CSharp\Syntax\AstType.cs" />

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

@ -354,7 +354,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -354,7 +354,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
//assert then block terminates
var trueExitInst = GetExit(ifInst.TrueInst);
var exitInst = GetExit(block);
context.Step("Negate if for desired branch "+trueExitInst, ifInst);
context.Step($"InvertIf at IL_{ifInst.StartILOffset:x4}", ifInst);
//if the then block terminates, else blocks are redundant, and should not exist
Debug.Assert(IsEmpty(ifInst.FalseInst));

3
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -158,9 +158,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -158,9 +158,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
// Remove return blocks that are no longer reachable:
container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0 && b.Instructions.Count == 0);
if (context.Settings.RemoveDeadCode) {
container.SortBlocks(deleteUnreachableBlocks: true);
}
}
}

48
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -107,7 +107,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -107,7 +107,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
IncludeNestedContainers(loop);
// Try to extend the loop to reduce the number of exit points:
ExtendLoop(h, loop, out var exitPoint);
IncludeUnreachablePredecessors(loop);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
// (if the loop doesn't contain nested loops, this is a topological sort)
@ -115,7 +114,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -115,7 +114,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(loop[0] == h);
foreach (var node in loop) {
node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The loop body must be dominated by the loop head");
Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head");
}
ConstructLoop(loop, exitPoint);
}
@ -150,7 +149,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -150,7 +149,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// (the entry-point itself doesn't have a CFG node, because it's newly created by this transform)
for (int i = 1; i < nestedContainer.Blocks.Count; i++) {
var node = context.ControlFlowGraph.GetNode(nestedContainer.Blocks[i]);
Debug.Assert(loop[0].Dominates(node) || !node.IsReachable);
Debug.Assert(loop[0].Dominates(node));
if (!node.Visited) {
node.Visited = true;
loop.Add(node);
@ -258,6 +257,22 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -258,6 +257,22 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
loop.Add(node);
}
}
// The loop/switch can only be entered through the entry point.
if (isSwitch) {
// In the case of a switch, false positives in the "continue;" detection logic
// can lead to falsely excludes some blocks from the body.
// Fix that by including all predecessors of included blocks.
Debug.Assert(loop[0] == loopHead);
for (int i = 1; i < loop.Count; i++) {
foreach (var p in loop[i].Predecessors) {
if (!p.Visited) {
p.Visited = true;
loop.Add(p);
}
}
}
}
Debug.Assert(loop.All(n => n == loopHead || n.Predecessors.All(p => p.Visited)));
} else {
// We are in case 2, but could not find a suitable exit point.
// Heuristically try to minimize the number of exit points
@ -602,30 +617,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -602,30 +617,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
#endregion
/// <summary>
/// While our normal dominance logic ensures the loop has just a single reachable entry point,
/// it's possible that there are unreachable code blocks that have jumps into the loop.
/// We'll also include those into the loop.
///
/// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop.
/// </summary>
private void IncludeUnreachablePredecessors(List<ControlFlowNode> loop)
{
for (int i = 1; i < loop.Count; i++) {
Debug.Assert(loop[i].Visited);
foreach (var pred in loop[i].Predecessors) {
if (!pred.Visited) {
if (pred.IsReachable) {
Debug.Fail("All jumps into the loop body should go through the entry point");
} else {
pred.Visited = true;
loop.Add(pred);
}
}
}
}
}
/// <summary>
/// Move the blocks associated with the loop into a new block container.
/// </summary>
@ -708,7 +699,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -708,7 +699,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
exitPoint = null;
}
IncludeUnreachablePredecessors(nodesInSwitch);
context.Step("Create BlockContainer for switch", switchInst);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
@ -717,7 +707,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -717,7 +707,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(nodesInSwitch[0] == h);
foreach (var node in nodesInSwitch) {
node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The switch body must be dominated by the switch head");
Debug.Assert(h.Dominates(node), "The switch body must be dominated by the switch head");
}
BlockContainer switchContainer = new BlockContainer(ContainerKind.Switch);

4
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -142,7 +142,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -142,7 +142,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.Body = newBody;
// register any locals used in newBody
function.Variables.AddRange(newBody.Descendants.OfType<IInstructionWithVariableOperand>().Select(inst => inst.Variable).Distinct());
function.CheckInvariant(ILPhase.Normal);
PrintFinallyMethodStateRanges(newBody);
@ -164,6 +163,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -164,6 +163,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid
// (though some may point to now-deleted blocks)
newBody.SortBlocks(deleteUnreachableBlocks: true);
function.CheckInvariant(ILPhase.Normal);
if (!isCompiledWithMono) {
DecompileFinallyBlocks();
@ -338,7 +338,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -338,7 +338,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
public static bool IsCompilerGeneratorEnumerator(TypeDefinitionHandle type, MetadataReader metadata)
{
TypeDefinition td;
if (type.IsNil || !type.IsCompilerGenerated(metadata) || (td = metadata.GetTypeDefinition(type)).GetDeclaringType().IsNil)
if (type.IsNil || !type.IsCompilerGeneratedOrIsInCompilerGeneratedClass(metadata) || (td = metadata.GetTypeDefinition(type)).GetDeclaringType().IsNil)
return false;
foreach (var i in td.GetInterfaceImplementations()) {
var tr = metadata.GetInterfaceImplementation(i).Interface.GetFullTypeName(metadata);

12
ICSharpCode.Decompiler/IL/ILReader.cs

@ -493,8 +493,18 @@ namespace ICSharpCode.Decompiler.IL @@ -493,8 +493,18 @@ namespace ICSharpCode.Decompiler.IL
CollectionExtensions.AddRange(function.Variables, stackVariables);
CollectionExtensions.AddRange(function.Variables, variableByExceptionHandler.Values);
function.AddRef(); // mark the root node
var removedBlocks = new List<Block>();
foreach (var c in function.Descendants.OfType<BlockContainer>()) {
c.SortBlocks();
var newOrder = c.TopologicalSort(deleteUnreachableBlocks: true);
if (newOrder.Count < c.Blocks.Count) {
removedBlocks.AddRange(c.Blocks.Except(newOrder));
}
c.Blocks.ReplaceList(newOrder);
}
if (removedBlocks.Count > 0) {
removedBlocks.SortBy(b => b.StartILOffset);
function.Warnings.Add("Discarded unreachable code: "
+ string.Join(", ", removedBlocks.Select(b => $"IL_{b.StartILOffset:x4}")));
}
function.Warnings.AddRange(Warnings);
return function;

48
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -184,6 +184,7 @@ namespace ICSharpCode.Decompiler.IL @@ -184,6 +184,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(EntryPoint == null || Parent is ILFunction || !HasILRange);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
Debug.Assert(Blocks.All(b => b.Kind == BlockKind.ControlFlow)); // this also implies that the blocks don't use FinalInstruction
Debug.Assert(TopologicalSort(deleteUnreachableBlocks: true).Count == Blocks.Count, "Container should not have any unreachable blocks");
Block bodyStartBlock;
switch (Kind) {
case ContainerKind.Normal:
@ -239,43 +240,54 @@ namespace ICSharpCode.Decompiler.IL @@ -239,43 +240,54 @@ namespace ICSharpCode.Decompiler.IL
}
/// <summary>
/// Sort the blocks in reverse post-order over the control flow graph between the blocks.
/// Topologically sort the blocks.
/// The new order is returned without modifying the BlockContainer.
/// </summary>
public void SortBlocks(bool deleteUnreachableBlocks = false)
/// <param name="deleteUnreachableBlocks">If true, unreachable blocks are not included in the new order.</param>
public List<Block> TopologicalSort(bool deleteUnreachableBlocks = false)
{
if (Blocks.Count < 2)
return;
// Visit blocks in post-order
BitSet visited = new BitSet(Blocks.Count);
List<Block> postOrder = new List<Block>();
Visit(EntryPoint);
postOrder.Reverse();
if (!deleteUnreachableBlocks) {
for (int i = 0; i < Blocks.Count; i++) {
if (!visited[i])
postOrder.Add(Blocks[i]);
}
}
return postOrder;
Action<Block> visit = null;
visit = delegate(Block block) {
void Visit(Block block)
{
Debug.Assert(block.Parent == this);
if (!visited[block.ChildIndex]) {
visited[block.ChildIndex] = true;
foreach (var branch in block.Descendants.OfType<Branch>()) {
if (branch.TargetBlock.Parent == this) {
visit(branch.TargetBlock);
Visit(branch.TargetBlock);
}
}
postOrder.Add(block);
}
};
visit(EntryPoint);
postOrder.Reverse();
if (!deleteUnreachableBlocks) {
for (int i = 0; i < Blocks.Count; i++) {
if (!visited[i])
postOrder.Add(Blocks[i]);
}
}
Debug.Assert(postOrder[0] == Blocks[0]);
Blocks.ReplaceList(postOrder);
/// <summary>
/// Topologically sort the blocks.
/// </summary>
/// <param name="deleteUnreachableBlocks">If true, delete unreachable blocks.</param>
public void SortBlocks(bool deleteUnreachableBlocks = false)
{
if (Blocks.Count < 2)
return;
var newOrder = TopologicalSort(deleteUnreachableBlocks);
Debug.Assert(newOrder[0] == Blocks[0]);
Blocks.ReplaceList(newOrder);
}
public static BlockContainer FindClosestContainer(ILInstruction inst)

1
ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

@ -85,7 +85,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -85,7 +85,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.CancellationToken.ThrowIfCancellationRequested();
blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken);
VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext);
// TODO: handle unreachable code?
}
} finally {
running = false;

12
ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs

@ -194,6 +194,18 @@ namespace ICSharpCode.Decompiler.IL @@ -194,6 +194,18 @@ namespace ICSharpCode.Decompiler.IL
// if (cond) { ...; exit; }
// ...; exit;
EnsureEndPointUnreachable(ifInst.TrueInst, exitInst);
if (ifInst.FalseInst.HasFlag(InstructionFlags.EndPointUnreachable)) {
Debug.Assert(ifInst.HasFlag(InstructionFlags.EndPointUnreachable));
Debug.Assert(ifInst.Parent == block);
int removeAfter = ifInst.ChildIndex + 1;
if (removeAfter < block.Instructions.Count) {
// Remove all instructions that ended up dead
// (this should just be exitInst itself)
Debug.Assert(block.Instructions.SecondToLastOrDefault() == ifInst);
Debug.Assert(block.Instructions.Last() == exitInst);
block.Instructions.RemoveRange(removeAfter, block.Instructions.Count - removeAfter);
}
}
ExtractElseBlock(ifInst);
ifInst = elseIfInst;
} while (ifInst != null);

11
ICSharpCode.Decompiler/SRMExtensions.cs

@ -309,6 +309,17 @@ namespace ICSharpCode.Decompiler @@ -309,6 +309,17 @@ namespace ICSharpCode.Decompiler
return false;
}
public static bool IsCompilerGeneratedOrIsInCompilerGeneratedClass(this TypeDefinitionHandle handle, MetadataReader metadata)
{
TypeDefinition type = metadata.GetTypeDefinition(handle);
if (type.IsCompilerGenerated(metadata))
return true;
TypeDefinitionHandle declaringTypeHandle = type.GetDeclaringType();
if (!declaringTypeHandle.IsNil && declaringTypeHandle.IsCompilerGenerated(metadata))
return true;
return false;
}
public static bool IsCompilerGenerated(this MethodDefinition method, MetadataReader metadata)
{
return method.GetCustomAttributes().HasKnownAttribute(metadata, KnownAttribute.CompilerGenerated);

56
ICSharpCode.Decompiler/Solution/ProjectId.cs

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
//
// 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;
namespace ICSharpCode.Decompiler.Solution
{
/// <summary>
/// A container class that holds platform and GUID information about a Visual Studio project.
/// </summary>
public class ProjectId
{
/// <summary>
/// Initializes a new instance of the <see cref="ProjectId"/> class.
/// </summary>
/// <param name="projectPlatform">The project platform.</param>
/// <param name="projectGuid">The project GUID.</param>
///
/// <exception cref="ArgumentException">Thrown when <paramref name="projectFile"/>
/// or <paramref name="projectPlatform"/> is null or empty.</exception>
public ProjectId(string projectPlatform, Guid projectGuid)
{
if (string.IsNullOrWhiteSpace(projectPlatform)) {
throw new ArgumentException("The platform cannot be null or empty.", nameof(projectPlatform));
}
Guid = projectGuid;
PlatformName = projectPlatform;
}
/// <summary>
/// Gets the GUID of this project.
/// </summary>
public Guid Guid { get; }
/// <summary>
/// Gets the platform name of this project. Only single platform per project is supported.
/// </summary>
public string PlatformName { get; }
}
}

55
ICSharpCode.Decompiler/Solution/ProjectItem.cs

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
//
// 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.IO;
namespace ICSharpCode.Decompiler.Solution
{
/// <summary>
/// A container class that holds information about a Visual Studio project.
/// </summary>
public sealed class ProjectItem : ProjectId
{
/// <summary>
/// Initializes a new instance of the <see cref="ProjectItem"/> class.
/// </summary>
/// <param name="projectFile">The full path of the project file.</param>
/// <param name="projectPlatform">The project platform.</param>
/// <param name="projectGuid">The project GUID.</param>
///
/// <exception cref="ArgumentException">Thrown when <paramref name="projectFile"/>
/// or <paramref name="projectPlatform"/> is null or empty.</exception>
public ProjectItem(string projectFile, string projectPlatform, Guid projectGuid)
: base(projectPlatform, projectGuid)
{
ProjectName = Path.GetFileNameWithoutExtension(projectFile);
FilePath = projectFile;
}
/// <summary>
/// Gets the name of the project.
/// </summary>
public string ProjectName { get; }
/// <summary>
/// Gets the full path to the project file.
/// </summary>
public string FilePath { get; }
}
}

201
ICSharpCode.Decompiler/Solution/SolutionCreator.cs

@ -0,0 +1,201 @@ @@ -0,0 +1,201 @@
// Copyright (c) 2019 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Xml.Linq;
namespace ICSharpCode.Decompiler.Solution
{
/// <summary>
/// A helper class that can write a Visual Studio Solution file for the provided projects.
/// </summary>
public static class SolutionCreator
{
private static readonly XNamespace ProjectFileNamespace = XNamespace.Get("http://schemas.microsoft.com/developer/msbuild/2003");
/// <summary>
/// Writes a solution file to the specified <paramref name="targetFile"/>.
/// </summary>
/// <param name="targetFile">The full path of the file to write.</param>
/// <param name="projects">The projects contained in this solution.</param>
///
/// <exception cref="ArgumentException">Thrown when <paramref name="targetFile"/> is null or empty.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="projects"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when <paramref name="projects"/> contains no items.</exception>
public static void WriteSolutionFile(string targetFile, IEnumerable<ProjectItem> projects)
{
if (string.IsNullOrWhiteSpace(targetFile)) {
throw new ArgumentException("The target file cannot be null or empty.", nameof(targetFile));
}
if (projects == null) {
throw new ArgumentNullException(nameof(projects));
}
if (!projects.Any()) {
throw new InvalidOperationException("At least one project is expected.");
}
using (var writer = new StreamWriter(targetFile)) {
WriteSolutionFile(writer, projects, targetFile);
}
FixProjectReferences(projects);
}
private static void WriteSolutionFile(TextWriter writer, IEnumerable<ProjectItem> projects, string solutionFilePath)
{
WriteHeader(writer);
WriteProjects(writer, projects, solutionFilePath);
writer.WriteLine("Global");
var platforms = WriteSolutionConfigurations(writer, projects);
WriteProjectConfigurations(writer, projects, platforms);
writer.WriteLine("\tGlobalSection(SolutionProperties) = preSolution");
writer.WriteLine("\t\tHideSolutionNode = FALSE");
writer.WriteLine("\tEndGlobalSection");
writer.WriteLine("EndGlobal");
}
private static void WriteHeader(TextWriter writer)
{
writer.WriteLine("Microsoft Visual Studio Solution File, Format Version 12.00");
writer.WriteLine("# Visual Studio 14");
writer.WriteLine("VisualStudioVersion = 14.0.24720.0");
writer.WriteLine("MinimumVisualStudioVersion = 10.0.40219.1");
}
private static void WriteProjects(TextWriter writer, IEnumerable<ProjectItem> projects, string solutionFilePath)
{
var solutionGuid = Guid.NewGuid().ToString("B").ToUpperInvariant();
foreach (var project in projects) {
var projectRelativePath = GetRelativePath(solutionFilePath, project.FilePath);
var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
writer.WriteLine($"Project(\"{solutionGuid}\") = \"{project.ProjectName}\", \"{projectRelativePath}\", \"{projectGuid}\"");
writer.WriteLine("EndProject");
}
}
private static IEnumerable<string> WriteSolutionConfigurations(TextWriter writer, IEnumerable<ProjectItem> projects)
{
var platforms = projects.GroupBy(p => p.PlatformName).Select(g => g.Key).ToList();
platforms.Sort();
writer.WriteLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution");
foreach (var platform in platforms) {
writer.WriteLine($"\t\tDebug|{platform} = Debug|{platform}");
}
foreach (var platform in platforms) {
writer.WriteLine($"\t\tRelease|{platform} = Release|{platform}");
}
writer.WriteLine("\tEndGlobalSection");
return platforms;
}
private static void WriteProjectConfigurations(
TextWriter writer,
IEnumerable<ProjectItem> projects,
IEnumerable<string> solutionPlatforms)
{
writer.WriteLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution");
foreach (var project in projects) {
var projectGuid = project.Guid.ToString("B").ToUpperInvariant();
foreach (var platform in solutionPlatforms) {
writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.ActiveCfg = Debug|{project.PlatformName}");
writer.WriteLine($"\t\t{projectGuid}.Debug|{platform}.Build.0 = Debug|{project.PlatformName}");
}
foreach (var platform in solutionPlatforms) {
writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.ActiveCfg = Release|{project.PlatformName}");
writer.WriteLine($"\t\t{projectGuid}.Release|{platform}.Build.0 = Release|{project.PlatformName}");
}
}
writer.WriteLine("\tEndGlobalSection");
}
private static void FixProjectReferences(IEnumerable<ProjectItem> projects)
{
var projectsMap = projects.ToDictionary(p => p.ProjectName, p => p);
foreach (var project in projects) {
XDocument projectDoc = XDocument.Load(project.FilePath);
var referencesItemGroups = projectDoc.Root
.Elements(ProjectFileNamespace + "ItemGroup")
.Where(e => e.Elements(ProjectFileNamespace + "Reference").Any());
foreach (var itemGroup in referencesItemGroups) {
FixProjectReferences(project.FilePath, itemGroup, projectsMap);
}
projectDoc.Save(project.FilePath);
}
}
private static void FixProjectReferences(string projectFilePath, XElement itemGroup, IDictionary<string, ProjectItem> projects)
{
foreach (var item in itemGroup.Elements(ProjectFileNamespace + "Reference").ToList()) {
var assemblyName = item.Attribute("Include")?.Value;
if (assemblyName != null && projects.TryGetValue(assemblyName, out var referencedProject)) {
item.Remove();
var projectReference = new XElement(ProjectFileNamespace + "ProjectReference",
new XElement(ProjectFileNamespace + "Project", referencedProject.Guid.ToString("B").ToUpperInvariant()),
new XElement(ProjectFileNamespace + "Name", referencedProject.ProjectName));
projectReference.SetAttributeValue("Include", GetRelativePath(projectFilePath, referencedProject.FilePath));
itemGroup.Add(projectReference);
}
}
}
private static string GetRelativePath(string fromFilePath, string toFilePath)
{
Uri fromUri = new Uri(fromFilePath);
Uri toUri = new Uri(toFilePath);
if (fromUri.Scheme != toUri.Scheme) {
return toFilePath;
}
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
string relativePath = Uri.UnescapeDataString(relativeUri.ToString());
if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) {
relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
}
return relativePath;
}
}
}

2
ILSpy.BamlDecompiler/Handlers/Records/PropertyTypeReferenceHandler.cs

@ -40,7 +40,7 @@ namespace ILSpy.BamlDecompiler.Handlers { @@ -40,7 +40,7 @@ namespace ILSpy.BamlDecompiler.Handlers {
var elemAttr = ctx.ResolveProperty(record.AttributeId);
elem.Xaml = new XElement(elemAttr.ToXName(ctx, null));
if (attr.ResolvedMember.FullNameIs("System.Windows.Style", "TargetType")) {
if (attr.ResolvedMember?.FullNameIs("System.Windows.Style", "TargetType") == true) {
parent.Xaml.Element.AddAnnotation(new TargetTypeAnnotation(type));
}

133
ILSpy/Commands/SaveCodeContextMenuEntry.cs

@ -0,0 +1,133 @@ @@ -0,0 +1,133 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Windows;
using System.Windows.Input;
using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.TreeView;
using Microsoft.Win32;
namespace ICSharpCode.ILSpy.TextView
{
[ExportContextMenuEntry(Header = nameof(Resources._SaveCode), Category = nameof(Resources.Save), Icon = "Images/SaveFile.png")]
sealed class SaveCodeContextMenuEntry : IContextMenuEntry
{
public void Execute(TextViewContext context)
{
Execute(context.SelectedTreeNodes);
}
public bool IsEnabled(TextViewContext context) => true;
public bool IsVisible(TextViewContext context)
{
return CanExecute(context.SelectedTreeNodes);
}
public static bool CanExecute(IReadOnlyList<SharpTreeNode> selectedNodes)
{
if (selectedNodes == null || selectedNodes.Any(n => !(n is ILSpyTreeNode)))
return false;
return selectedNodes.Count == 1
|| (selectedNodes.Count > 1 && (selectedNodes.All(n => n is AssemblyTreeNode) || selectedNodes.All(n => n is IMemberTreeNode)));
}
public static void Execute(IReadOnlyList<SharpTreeNode> selectedNodes)
{
var currentLanguage = MainWindow.Instance.CurrentLanguage;
var textView = MainWindow.Instance.TextView;
if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) {
// if there's only one treenode selected
// we will invoke the custom Save logic
if (singleSelection.Save(textView))
return;
} else if (selectedNodes.Count > 1 && selectedNodes.All(n => n is AssemblyTreeNode)) {
var initialPath = Path.GetDirectoryName(((AssemblyTreeNode)selectedNodes[0]).LoadedAssembly.FileName);
var selectedPath = SelectSolutionFile(initialPath);
if (!string.IsNullOrEmpty(selectedPath)) {
var assemblies = selectedNodes.OfType<AssemblyTreeNode>()
.Select(n => n.LoadedAssembly)
.Where(a => !a.HasLoadError).ToArray();
SolutionWriter.CreateSolution(textView, selectedPath, currentLanguage, assemblies);
}
return;
}
// Fallback: if nobody was able to handle the request, use default behavior.
// try to save all nodes to disk.
var options = new DecompilationOptions() { FullDecompilation = true };
textView.SaveToDisk(currentLanguage, selectedNodes.OfType<ILSpyTreeNode>(), options);
}
/// <summary>
/// Shows a File Selection dialog where the user can select the target file for the solution.
/// </summary>
/// <param name="path">The initial path to show in the dialog. If not specified, the 'Documents' directory
/// will be used.</param>
///
/// <returns>The full path of the selected target file, or <c>null</c> if the user canceled.</returns>
static string SelectSolutionFile(string path)
{
const string SolutionExtension = ".sln";
const string DefaultSolutionName = "Solution";
if (string.IsNullOrWhiteSpace(path)) {
path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
}
SaveFileDialog dlg = new SaveFileDialog();
dlg.InitialDirectory = path;
dlg.FileName = Path.Combine(path, DefaultSolutionName + SolutionExtension);
dlg.Filter = "Visual Studio Solution file|*" + SolutionExtension;
bool targetInvalid;
do {
if (dlg.ShowDialog() != true) {
return null;
}
string selectedPath = Path.GetDirectoryName(dlg.FileName);
try {
targetInvalid = Directory.EnumerateFileSystemEntries(selectedPath).Any();
} catch (Exception e) when (e is IOException || e is UnauthorizedAccessException || e is System.Security.SecurityException) {
MessageBox.Show(
"The directory cannot be accessed. Please ensure it exists and you have sufficient rights to access it.",
"Solution directory not accessible",
MessageBoxButton.OK, MessageBoxImage.Error);
targetInvalid = true;
continue;
}
if (targetInvalid) {
MessageBox.Show(
"The directory is not empty. Please select an empty directory.",
"Solution directory not empty",
MessageBoxButton.OK, MessageBoxImage.Warning);
}
} while (targetInvalid);
return dlg.FileName;
}
}
}

2
ILSpy/ILSpy.csproj

@ -204,9 +204,11 @@ @@ -204,9 +204,11 @@
</Compile>
<Compile Include="Commands\SimpleCommand.cs" />
<Compile Include="Search\AbstractSearchStrategy.cs" />
<Compile Include="SolutionWriter.cs" />
<Compile Include="TaskHelper.cs" />
<Compile Include="TextView\EditorCommands.cs" />
<Compile Include="TextView\FoldingCommands.cs" />
<Compile Include="Commands\SaveCodeContextMenuEntry.cs" />
<Compile Include="TextView\XmlDocRenderer.cs" />
<Compile Include="Analyzers\AnalyzeCommand.cs" />
<Compile Include="Analyzers\Builtin\FieldAccessAnalyzer.cs" />

8
ILSpy/Languages/CSharpLanguage.cs

@ -34,6 +34,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax; @@ -34,6 +34,7 @@ using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.TreeNodes;
@ -343,12 +344,12 @@ namespace ICSharpCode.ILSpy @@ -343,12 +344,12 @@ namespace ICSharpCode.ILSpy
}
}
public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
var module = assembly.GetPEFileOrNull();
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
var decompiler = new ILSpyWholeProjectDecompiler(assembly, options);
decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken);
return decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken);
} else {
AddReferenceAssemblyWarningMessage(module, output);
AddReferenceWarningMessage(module, output);
@ -389,7 +390,7 @@ namespace ICSharpCode.ILSpy @@ -389,7 +390,7 @@ namespace ICSharpCode.ILSpy
}
if (metadata.IsAssembly) {
var asm = metadata.GetAssemblyDefinition();
if (asm.HashAlgorithm != System.Reflection.AssemblyHashAlgorithm.None)
if (asm.HashAlgorithm != AssemblyHashAlgorithm.None)
output.WriteLine("// Hash algorithm: " + asm.HashAlgorithm.ToString().ToUpper());
if (!asm.PublicKey.IsNil) {
output.Write("// Public key: ");
@ -415,6 +416,7 @@ namespace ICSharpCode.ILSpy @@ -415,6 +416,7 @@ namespace ICSharpCode.ILSpy
}
WriteCode(output, options.DecompilerSettings, st, decompiler.TypeSystem);
}
return null;
}
}

5
ILSpy/Languages/ILLanguage.cs

@ -27,6 +27,8 @@ using System.Reflection.Metadata.Ecma335; @@ -27,6 +27,8 @@ using System.Reflection.Metadata.Ecma335;
using System.Linq;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.Decompiler.Solution;
namespace ICSharpCode.ILSpy
{
@ -150,7 +152,7 @@ namespace ICSharpCode.ILSpy @@ -150,7 +152,7 @@ namespace ICSharpCode.ILSpy
dis.DisassembleNamespace(nameSpace, module, types.Select(t => (TypeDefinitionHandle)t.MetadataToken));
}
public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
output.WriteLine("// " + assembly.FileName);
output.WriteLine();
@ -174,6 +176,7 @@ namespace ICSharpCode.ILSpy @@ -174,6 +176,7 @@ namespace ICSharpCode.ILSpy
dis.WriteModuleContents(module);
}
}
return null;
}
}
}

6
ILSpy/Languages/Language.cs

@ -23,6 +23,7 @@ using System.Reflection.PortableExecutable; @@ -23,6 +23,7 @@ using System.Reflection.PortableExecutable;
using System.Text;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
@ -131,11 +132,11 @@ namespace ICSharpCode.ILSpy @@ -131,11 +132,11 @@ namespace ICSharpCode.ILSpy
WriteCommentLine(output, nameSpace);
}
public virtual void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
public virtual ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{
WriteCommentLine(output, assembly.FileName);
var asm = assembly.GetPEFileOrNull();
if (asm == null) return;
if (asm == null) return null;
var metadata = asm.Metadata;
if (metadata.IsAssembly) {
var name = metadata.GetAssemblyDefinition();
@ -147,6 +148,7 @@ namespace ICSharpCode.ILSpy @@ -147,6 +148,7 @@ namespace ICSharpCode.ILSpy
} else {
WriteCommentLine(output, metadata.GetString(metadata.GetModuleDefinition().Name));
}
return null;
}
public virtual void WriteCommentLine(ITextOutput output, string comment)

1
ILSpy/MainWindow.xaml

@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
Executed="RefreshCommandExecuted" />
<CommandBinding
Command="Save"
CanExecute="SaveCommandCanExecute"
Executed="SaveCommandExecuted" />
<CommandBinding
Command="BrowseBack"

15
ILSpy/MainWindow.xaml.cs

@ -38,6 +38,7 @@ using ICSharpCode.Decompiler.Documentation; @@ -38,6 +38,7 @@ using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.ILSpy.Controls;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.TreeView;
@ -898,15 +899,15 @@ namespace ICSharpCode.ILSpy @@ -898,15 +899,15 @@ namespace ICSharpCode.ILSpy
decompilationTask = decompilerTextView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, new DecompilationOptions() { TextViewState = state });
}
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
{
if (this.SelectedNodes.Count() == 1) {
if (this.SelectedNodes.Single().Save(this.TextView))
return;
e.Handled = true;
e.CanExecute = SaveCodeContextMenuEntry.CanExecute(SelectedNodes.ToList());
}
this.TextView.SaveToDisk(this.CurrentLanguage,
this.SelectedNodes,
new DecompilationOptions() { FullDecompilation = true });
void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
SaveCodeContextMenuEntry.Execute(SelectedNodes.ToList());
}
public void RefreshDecompiledView()

18
ILSpy/Properties/Resources.Designer.cs generated

@ -303,6 +303,24 @@ namespace ICSharpCode.ILSpy.Properties { @@ -303,6 +303,24 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to The directory is not empty. File will be overwritten.\r\nAre you sure you want to continue?.
/// </summary>
public static string AssemblySaveCodeDirectoryNotEmpty {
get {
return ResourceManager.GetString("AssemblySaveCodeDirectoryNotEmpty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Project Directory not empty.
/// </summary>
public static string AssemblySaveCodeDirectoryNotEmptyTitle {
get {
return ResourceManager.GetString("AssemblySaveCodeDirectoryNotEmptyTitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Automatically check for updates every week.
/// </summary>

6
ILSpy/Properties/Resources.resx

@ -732,4 +732,10 @@ @@ -732,4 +732,10 @@
<data name="DecompilerSettings.AllowExtensionMethodSyntaxOnRef" xml:space="preserve">
<value>Use 'ref' extension methods</value>
</data>
<data name="AssemblySaveCodeDirectoryNotEmpty" xml:space="preserve">
<value>The directory is not empty. File will be overwritten.\r\nAre you sure you want to continue?</value>
</data>
<data name="AssemblySaveCodeDirectoryNotEmptyTitle" xml:space="preserve">
<value>Project Directory not empty</value>
</data>
</root>

182
ILSpy/SolutionWriter.cs

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy.TextView;
namespace ICSharpCode.ILSpy
{
/// <summary>
/// An utility class that creates a Visual Studio solution containing projects for the
/// decompiled assemblies.
/// </summary>
internal class SolutionWriter
{
/// <summary>
/// Creates a Visual Studio solution that contains projects with decompiled code
/// of the specified <paramref name="assemblies"/>. The solution file will be saved
/// to the <paramref name="solutionFilePath"/>. The directory of this file must either
/// be empty or not exist.
/// </summary>
/// <param name="textView">A reference to the <see cref="DecompilerTextView"/> instance.</param>
/// <param name="solutionFilePath">The target file path of the solution file.</param>
/// <param name="assemblies">The assembly nodes to decompile.</param>
///
/// <exception cref="ArgumentException">Thrown when <paramref name="solutionFilePath"/> is null,
/// an empty or a whitespace string.</exception>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="textView"/>> or
/// <paramref name="assemblies"/> is null.</exception>
public static void CreateSolution(DecompilerTextView textView, string solutionFilePath, Language language, IEnumerable<LoadedAssembly> assemblies)
{
if (textView == null) {
throw new ArgumentNullException(nameof(textView));
}
if (string.IsNullOrWhiteSpace(solutionFilePath)) {
throw new ArgumentException("The solution file path cannot be null or empty.", nameof(solutionFilePath));
}
if (assemblies == null) {
throw new ArgumentNullException(nameof(assemblies));
}
var writer = new SolutionWriter(solutionFilePath);
textView
.RunWithCancellation(ct => writer.CreateSolution(assemblies, language, ct))
.Then(output => textView.ShowText(output))
.HandleExceptions();
}
readonly string solutionFilePath;
readonly string solutionDirectory;
readonly ConcurrentBag<ProjectItem> projects;
readonly ConcurrentBag<string> statusOutput;
SolutionWriter(string solutionFilePath)
{
this.solutionFilePath = solutionFilePath;
solutionDirectory = Path.GetDirectoryName(solutionFilePath);
statusOutput = new ConcurrentBag<string>();
projects = new ConcurrentBag<ProjectItem>();
}
async Task<AvalonEditTextOutput> CreateSolution(IEnumerable<LoadedAssembly> assemblies, Language language, CancellationToken ct)
{
var result = new AvalonEditTextOutput();
var duplicates = new HashSet<string>();
if (assemblies.Any(asm => !duplicates.Add(asm.ShortName))) {
result.WriteLine("Duplicate assembly names selected, cannot generate a solution.");
return result;
}
Stopwatch stopwatch = Stopwatch.StartNew();
try {
await Task.Run(() => Parallel.ForEach(assemblies, n => WriteProject(n, language, solutionDirectory, ct)))
.ConfigureAwait(false);
await Task.Run(() => SolutionCreator.WriteSolutionFile(solutionFilePath, projects))
.ConfigureAwait(false);
} catch (AggregateException ae) {
if (ae.Flatten().InnerExceptions.All(e => e is OperationCanceledException)) {
result.WriteLine();
result.WriteLine("Generation was cancelled.");
return result;
}
result.WriteLine();
result.WriteLine("Failed to generate the Visual Studio Solution. Errors:");
ae.Handle(e => {
result.WriteLine(e.Message);
return true;
});
return result;
}
foreach (var item in statusOutput) {
result.WriteLine(item);
}
if (statusOutput.Count == 0) {
result.WriteLine("Successfully decompiled the following assemblies into Visual Studio projects:");
foreach (var item in assemblies.Select(n => n.Text.ToString())) {
result.WriteLine(item);
}
result.WriteLine();
if (assemblies.Count() == projects.Count) {
result.WriteLine("Created the Visual Studio Solution file.");
}
result.WriteLine();
result.WriteLine("Elapsed time: " + stopwatch.Elapsed.TotalSeconds.ToString("F1") + " seconds.");
result.WriteLine();
result.AddButton(null, "Open Explorer", delegate { Process.Start("explorer", "/select,\"" + solutionFilePath + "\""); });
}
return result;
}
void WriteProject(LoadedAssembly loadedAssembly, Language language, string targetDirectory, CancellationToken ct)
{
targetDirectory = Path.Combine(targetDirectory, loadedAssembly.ShortName);
string projectFileName = Path.Combine(targetDirectory, loadedAssembly.ShortName + language.ProjectFileExtension);
if (!Directory.Exists(targetDirectory)) {
try {
Directory.CreateDirectory(targetDirectory);
} catch (Exception e) {
statusOutput.Add($"Failed to create a directory '{targetDirectory}':{Environment.NewLine}{e}");
return;
}
}
try {
using (var projectFileWriter = new StreamWriter(projectFileName)) {
var projectFileOutput = new PlainTextOutput(projectFileWriter);
var options = new DecompilationOptions() {
FullDecompilation = true,
CancellationToken = ct,
SaveAsProjectDirectory = targetDirectory
};
var projectInfo = language.DecompileAssembly(loadedAssembly, projectFileOutput, options);
if (projectInfo != null) {
projects.Add(new ProjectItem(projectFileName, projectInfo.PlatformName, projectInfo.Guid));
}
}
} catch (Exception e) when (!(e is OperationCanceledException)) {
statusOutput.Add($"Failed to decompile the assembly '{loadedAssembly.FileName}':{Environment.NewLine}{e}");
}
}
}
}

5
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -292,9 +292,8 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -292,9 +292,8 @@ namespace ICSharpCode.ILSpy.TreeNodes
foreach (string entry in Directory.GetFileSystemEntries(options.SaveAsProjectDirectory)) {
if (!string.Equals(entry, dlg.FileName, StringComparison.OrdinalIgnoreCase)) {
var result = MessageBox.Show(
"The directory is not empty. File will be overwritten." + Environment.NewLine +
"Are you sure you want to continue?",
"Project Directory not empty",
Resources.AssemblySaveCodeDirectoryNotEmpty,
Resources.AssemblySaveCodeDirectoryNotEmptyTitle,
MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
return true; // don't save, but mark the Save operation as handled

1
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs

@ -86,7 +86,6 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -86,7 +86,6 @@ namespace ICSharpCode.ILSpy.TreeNodes
output.Unindent();
output.WriteLine();
}
}
}
}

Loading…
Cancel
Save