Skip to content

Commit

Permalink
Merge pull request #29149 from siegfriedpammer/dev/siegfriedpammer/de…
Browse files Browse the repository at this point in the history
…compiler

Improved User Experience with "Navigate to decompiled sources"
  • Loading branch information
sharwell authored Nov 2, 2018
2 parents 821a54c + 2def6a1 commit 8411c17
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 228 deletions.
71 changes: 71 additions & 0 deletions src/EditorFeatures/CSharp/DecompiledSource/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.Metadata;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource
{
internal class AssemblyResolver : IAssemblyResolver
{
private readonly Compilation parentCompilation;
private static readonly Version zeroVersion = new Version(0, 0, 0, 0);

public AssemblyResolver(Compilation parentCompilation)
{
this.parentCompilation = parentCompilation;
}

public PEFile Resolve(IAssemblyReference name)
{
foreach (var assembly in parentCompilation.GetReferencedAssemblySymbols())
{
// First, find the correct IAssemblySymbol by name and PublicKeyToken.
if (assembly.Identity.Name != name.Name
|| !assembly.Identity.PublicKeyToken.SequenceEqual(name.PublicKeyToken ?? Array.Empty<byte>()))
{
continue;
}

// Normally we skip versions that do not match, except if the reference is "mscorlib" (see comments below)
// or if the name.Version is '0.0.0.0'. This is because we require the metadata of all transitive references
// and modules, to achieve best decompilation results.
// In the case of .NET Standard projects for example, the 'netstandard' reference contains no references
// with actual versions. All versions are '0.0.0.0', therefore we have to ignore those version numbers,
// and can just use the references provided by Roslyn instead.
if (assembly.Identity.Version != name.Version && name.Version != zeroVersion
&& !string.Equals("mscorlib", assembly.Identity.Name, StringComparison.OrdinalIgnoreCase))
{
// MSBuild treats mscorlib special for the purpose of assembly resolution/unification, where all
// versions of the assembly are considered equal. The same policy is adopted here.
continue;
}

// reference assemblies should be fine here, we only need the metadata of references.
var reference = parentCompilation.GetMetadataReference(assembly);
return new PEFile(reference.Display, PEStreamOptions.PrefetchMetadata);
}

// not found
return null;
}

public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
// Primitive implementation to support multi-module assemblies
// where all modules are located next to the main module.
string baseDirectory = Path.GetDirectoryName(mainModule.FileName);
string moduleFileName = Path.Combine(baseDirectory, moduleName);
if (!File.Exists(moduleFileName))
{
return null;
}

return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.DocumentationComments;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource
{
internal class CSharpDecompiledSourceService : IDecompiledSourceService
{
private readonly HostLanguageServices provider;
private static readonly FileVersionInfo decompilerVersion = FileVersionInfo.GetVersionInfo(typeof(CSharpDecompiler).Assembly.Location);

public CSharpDecompiledSourceService(HostLanguageServices provider)
{
this.provider = provider;
}

public async Task<Document> AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken)
{
// Get the name of the type the symbol is in
var containingOrThis = symbol.GetContainingTypeOrThis();
var fullName = GetFullReflectionName(containingOrThis);

var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);

string assemblyLocation = null;
var isReferenceAssembly = symbol.ContainingAssembly.GetAttributes().Any(attribute => attribute.AttributeClass.Name == nameof(ReferenceAssemblyAttribute)
&& attribute.AttributeClass.ToNameDisplayString() == typeof(ReferenceAssemblyAttribute).FullName);
if (isReferenceAssembly)
{
try
{
var fullAssemblyName = symbol.ContainingAssembly.Identity.GetDisplayName();
GlobalAssemblyCache.Instance.ResolvePartialName(fullAssemblyName, out assemblyLocation, preferredCulture: CultureInfo.CurrentCulture);
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
}
}

if (assemblyLocation == null)
{
var reference = compilation.GetMetadataReference(symbol.ContainingAssembly);
assemblyLocation = (reference as PortableExecutableReference)?.FilePath;
if (assemblyLocation == null)
{
throw new NotSupportedException(EditorFeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret);
}
}

// Decompile
document = PerformDecompilation(document, fullName, compilation, assemblyLocation);

document = await AddAssemblyInfoRegionAsync(document, symbol, cancellationToken);

// Convert XML doc comments to regular comments, just like MAS
var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>();
document = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken);

var node = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

// Apply formatting rules
document = await Formatter.FormatAsync(
document, SpecializedCollections.SingletonEnumerable(node.FullSpan),
options: null, Formatter.GetDefaultFormattingRules(document), cancellationToken).ConfigureAwait(false);

return document;
}

private Document PerformDecompilation(Document document, string fullName, Compilation compilation, string assemblyLocation)
{
// Load the assembly.
var file = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage);

// Initialize a decompiler with default settings.
var decompiler = new CSharpDecompiler(file, new AssemblyResolver(compilation), new DecompilerSettings());
// Escape invalid identifiers to prevent Roslyn from failing to parse the generated code.
// (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.)
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());

var fullTypeName = new FullTypeName(fullName);

// Try to decompile; if an exception is thrown the caller will handle it
var text = decompiler.DecompileTypeAsString(fullTypeName);
return document.WithText(SourceText.From(text));
}

private async Task<Document> AddAssemblyInfoRegionAsync(Document document, ISymbol symbol, CancellationToken cancellationToken)
{
string assemblyInfo = MetadataAsSourceHelpers.GetAssemblyInfo(symbol.ContainingAssembly);
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
string assemblyPath = MetadataAsSourceHelpers.GetAssemblyDisplay(compilation, symbol.ContainingAssembly);

var regionTrivia = SyntaxFactory.RegionDirectiveTrivia(true)
.WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) });

var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.WithLeadingTrivia(new[]
{
SyntaxFactory.Trivia(regionTrivia),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Comment("// " + assemblyPath),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Comment($"// Decompiled with ICSharpCode.Decompiler {decompilerVersion.FileVersion}"),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.CarriageReturnLineFeed
});

return document.WithSyntaxRoot(newRoot);
}

private async Task<Document> ConvertDocCommentsToRegularCommentsAsync(Document document, IDocumentationCommentFormattingService docCommentFormattingService, CancellationToken cancellationToken)
{
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var newSyntaxRoot = DocCommentConverter.ConvertToRegularComments(syntaxRoot, docCommentFormattingService, cancellationToken);

return document.WithSyntaxRoot(newSyntaxRoot);
}

private string GetFullReflectionName(INamedTypeSymbol containingType)
{
var stack = new Stack<string>();
stack.Push(containingType.MetadataName);
var ns = containingType.ContainingNamespace;
do
{
stack.Push(ns.Name);
ns = ns.ContainingNamespace;
}
while (ns != null && !ns.IsGlobalNamespace);

return string.Join(".", stack);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;

namespace Microsoft.CodeAnalysis.Editor.CSharp.DecompiledSource
{
[ExportLanguageServiceFactory(typeof(IDecompiledSourceService), LanguageNames.CSharp), Shared]
internal partial class CSharpDecompiledSourceServiceFactory : ILanguageServiceFactory
{
public ILanguageService CreateLanguageService(HostLanguageServices provider)
{
return new CSharpDecompiledSourceService(provider);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
<PackageReference Include="Microsoft.VisualStudio.Language.Intellisense" Version="$(MicrosoftVisualStudioLanguageIntellisenseVersion)" />
<PackageReference Include="ICSharpCode.Decompiler" Version="$(ICSharpCodeDecompilerVersion)" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="CSharpEditorResources.resx">
Expand Down
22 changes: 22 additions & 0 deletions src/EditorFeatures/Core/IDecompiledSourceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;

namespace Microsoft.CodeAnalysis.Editor
{
internal interface IDecompiledSourceService : ILanguageService
{
/// <summary>
/// Generates formatted source code containing general information about the symbol's
/// containing assembly and the decompiled source code which the given ISymbol is or is a part of
/// into the given document
/// </summary>
/// <param name="document">The document to generate source into</param>
/// <param name="symbol">The symbol to generate source for</param>
/// <param name="cancellationToken">To cancel document operations</param>
/// <returns>The updated document</returns>
Task<Document> AddSourceToAsync(Document document, ISymbol symbol, CancellationToken cancellationToken);
}
}
Loading

0 comments on commit 8411c17

Please sign in to comment.