Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for decompilation based on ILSpy (similar to VS) #1751

Merged
merged 28 commits into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
16f1795
first stab at decompile
filipw Dec 29, 2019
7a83d99
added feature toggle
filipw Mar 28, 2020
702cfc0
reconcile some code between MetadataHelper and DecompilationHelper
filipw Mar 28, 2020
5ff6f41
file renames
filipw Mar 28, 2020
abf65fb
simplify code
filipw Mar 28, 2020
019e4f5
updated to ICSharpCode.Decompiler Version="5.0.2.5153" and reference …
filipw Mar 28, 2020
059a9e6
switch to reflection instead of duplicating code from Roslyn
filipw Mar 28, 2020
4d48d0a
some cleanup
filipw Mar 29, 2020
11fd23c
decompile off by default
filipw Mar 29, 2020
7c622e6
removed direct dependency on ilspy
filipw Mar 29, 2020
78a2244
conditional compile for net472
filipw Mar 29, 2020
f1f49b1
added factory
filipw Mar 30, 2020
a89d502
further clean up
filipw Mar 30, 2020
2afb2a1
get rid of metadata in the names where not necessary
filipw Mar 30, 2020
c46be53
moved to internalized OmniSharpCSharpDecompiledSourceService.cs
filipw Mar 31, 2020
d0df013
thread safety
filipw Mar 31, 2020
9b63a96
removed conditional compilation
filipw Mar 31, 2020
ffb80d2
added some tests
filipw Mar 31, 2020
b259809
updated tests
filipw Apr 1, 2020
ad88e3a
fixed timeouts
filipw Apr 1, 2020
247efac
PR feedback
filipw Apr 4, 2020
c88ae36
clean up .editorconfig
filipw Apr 4, 2020
7144eba
use Lazy
filipw Apr 4, 2020
d086582
Merge branch 'master' into feature/decompile
filipw Apr 6, 2020
2648893
timeout increase in tests
filipw Apr 6, 2020
1736d0e
aligned timeouts
filipw Apr 6, 2020
6348355
Update src/OmniSharp.Roslyn.CSharp/Services/Decompilation/OmniSharpCS…
filipw Apr 9, 2020
0136a96
Update src/OmniSharp.Roslyn.CSharp/Services/ExternalFeaturesHostServi…
filipw Apr 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<PackageReference Update="Dotnet.Script.DependencyModel" Version="0.51.0" />
<PackageReference Update="Dotnet.Script.DependencyModel.NuGet" Version="0.51.0" />

<PackageReference Update="ICSharpCode.Decompiler" Version="5.0.2.5153" />
<PackageReference Update="McMaster.Extensions.CommandLineUtils" Version="2.2.4" />

<PackageReference Update="Microsoft.AspNetCore.Diagnostics" Version="2.1.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;
using OmniSharp.Cake.Extensions;
using OmniSharp.Extensions;
using OmniSharp.Mef;
using OmniSharp.Models.GotoDefinition;
using OmniSharp.Models.Metadata;
Expand All @@ -20,15 +20,15 @@ public class GotoDefinitionHandler : CakeRequestHandler<GotoDefinitionRequest, G
{
private const int MethodLineOffset = 3;
private const int PropertyLineOffset = 7;
private readonly MetadataHelper _metadataHelper;
private readonly MetadataExternalSourceService _metadataExternalSourceService;

[ImportingConstructor]
public GotoDefinitionHandler(
OmniSharpWorkspace workspace,
MetadataHelper metadataHelper)
MetadataExternalSourceService metadataExternalSourceService)
: base(workspace)
{
_metadataHelper = metadataHelper ?? throw new ArgumentNullException(nameof(metadataHelper));
_metadataExternalSourceService = metadataExternalSourceService ?? throw new ArgumentNullException(nameof(metadataExternalSourceService));
}

protected override async Task<GotoDefinitionResponse> TranslateResponse(GotoDefinitionResponse response, GotoDefinitionRequest request)
Expand Down Expand Up @@ -109,14 +109,14 @@ private async Task<GotoDefinitionResponse> GetAliasFromMetadataAsync(GotoDefinit
return response;
}
var cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(request.Timeout));
var (metadataDocument, _) = await _metadataHelper.GetAndAddDocumentFromMetadata(document.Project, symbol, cancellationSource.Token);
var (metadataDocument, _) = await _metadataExternalSourceService.GetAndAddExternalSymbolDocument(document.Project, symbol, cancellationSource.Token);
if (metadataDocument == null)
{
return response;
}

cancellationSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(request.Timeout));
var metadataLocation = await _metadataHelper.GetSymbolLocationFromMetadata(symbol, metadataDocument, cancellationSource.Token);
var metadataLocation = await _metadataExternalSourceService.GetExternalSymbolLocation(symbol, metadataDocument, cancellationSource.Token);
var lineSpan = metadataLocation.GetMappedLineSpan();

response = new GotoDefinitionResponse
Expand All @@ -127,7 +127,7 @@ private async Task<GotoDefinitionResponse> GetAliasFromMetadataAsync(GotoDefinit
{
AssemblyName = symbol.ContainingAssembly.Name,
ProjectName = document.Project.Name,
TypeName = _metadataHelper.GetSymbolName(symbol)
TypeName = symbol.GetSymbolName()
},
};

Expand Down
3 changes: 0 additions & 3 deletions src/OmniSharp.Host/CompositionHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ public CompositionHost Build()
var config = new ContainerConfiguration();

var fileSystemWatcher = new ManualFileSystemWatcher();
var metadataHelper = new MetadataHelper(assemblyLoader);

var logger = loggerFactory.CreateLogger<CompositionHostBuilder>();

// We must register an MSBuild instance before composing MEF to ensure that
Expand All @@ -79,7 +77,6 @@ public CompositionHost Build()
.WithProvider(MefValueProvider.From(assemblyLoader))
.WithProvider(MefValueProvider.From(analyzerAssemblyLoader))
.WithProvider(MefValueProvider.From(dotNetCliService))
.WithProvider(MefValueProvider.From(metadataHelper))
.WithProvider(MefValueProvider.From(msbuildLocator))
.WithProvider(MefValueProvider.From(eventEmitter));

Expand Down
3 changes: 2 additions & 1 deletion src/OmniSharp.Roslyn.CSharp/OmniSharp.Roslyn.CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" PrivateAssets="All" />
<PackageReference Include="System.Reactive" />
<PackageReference Include="ICSharpCode.Decompiler" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="Microsoft.VisualStudio.CodingConventions" />
</ItemGroup>

</Project>
126 changes: 126 additions & 0 deletions src/OmniSharp.Roslyn.CSharp/Services/Decompilation/AssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// 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.Linq;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.Metadata;
using Microsoft.CodeAnalysis.Shared.Extensions;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;

namespace OmniSharp.Roslyn.CSharp.Services.Decompilation
{
internal class AssemblyResolver : IAssemblyResolver
{
private readonly Compilation _parentCompilation;
private readonly Dictionary<string, List<IAssemblySymbol>> _cache = new Dictionary<string, List<IAssemblySymbol>>();
private readonly ILogger _logger;

public AssemblyResolver(Compilation parentCompilation, ILoggerFactory loggerFactory)
{
_parentCompilation = parentCompilation;
_logger = loggerFactory.CreateLogger<AssemblyResolver>();
BuildReferenceCache();

void BuildReferenceCache()
{
foreach (var reference in parentCompilation.Assembly.Modules.First().ReferencedAssemblySymbols)
{
if (!_cache.TryGetValue(reference.Identity.Name, out var list))
{
list = new List<IAssemblySymbol>();
_cache.Add(reference.Identity.Name, list);
}

list.Add(reference);
}
}
}

public PEFile Resolve(IAssemblyReference name)
{
Log("Resolving: {0}", name.FullName);

// First, find the correct list of assemblies by name
if (!_cache.TryGetValue(name.Name, out var assemblies))
{
Log("Could not find by name: {0}", name.FullName);
return null;
}

// If we have only one assembly available, just use it.
// This is necessary, because in most cases there is only one assembly,
// but still might have a version different from what the decompiler asks for.
if (assemblies.Count == 1)
{
Log("Found single assembly: {0}", assemblies[0]);
if (assemblies[0].Identity.Version != name.Version)
{
Log("Mismatch in assembly versions. Expectted: {0}, Got: {1}", name.Version, assemblies[0].Identity.Version);
}
return MakePEFile(assemblies[0]);
}

// There are multiple assemblies
Log("Found: {0} assemblies for: {1}", assemblies.Count, name.Name);

// Get an exact match or highest version match from the list
IAssemblySymbol highestVersion = null;
IAssemblySymbol exactMatch = null;

var publicKeyTokenOfName = name.PublicKeyToken ?? Array.Empty<byte>();

foreach (var assembly in assemblies)
{
Log(assembly.Identity.GetDisplayName());
var version = assembly.Identity.Version;
var publicKeyToken = assembly.Identity.PublicKey;
if (version == name.Version && publicKeyToken.SequenceEqual(publicKeyTokenOfName))
{
exactMatch = assembly;
Log("Found exact match: {0}", assembly);
}
else if (highestVersion == null || highestVersion.Identity.Version < version)
{
highestVersion = assembly;
Log("Found higher version match: {0}", assembly);
}
}

var chosen = exactMatch ?? highestVersion;
Log("Chosen version match: {0}", chosen);
return MakePEFile(chosen);

PEFile MakePEFile(IAssemblySymbol assembly)
{
// reference assemblies should be fine here, we only need the metadata of references.
var reference = _parentCompilation.GetMetadataReference(assembly);
Log("Loading from: {0}", ((PortableExecutableReference)reference).FilePath);
return new PEFile(((PortableExecutableReference)reference).FilePath, PEStreamOptions.PrefetchMetadata);
}
}

public PEFile ResolveModule(PEFile mainModule, string moduleName)
{
Log("Resolving module {0}, of {1}", moduleName, mainModule.FullName);

// Primitive implementation to support multi-module assemblies
// where all modules are located next to the main module.
var baseDirectory = Path.GetDirectoryName(mainModule.FileName);
var moduleFileName = Path.Combine(baseDirectory, moduleName);
if (!File.Exists(moduleFileName))
{
Log("Module {0} not found", moduleFileName);
return null;
}

Log("Loading from {0}", moduleFileName);
return new PEFile(moduleFileName, PEStreamOptions.PrefetchMetadata);
}

private void Log(string format, params object[] args)
=> _logger.LogTrace(format, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions;
using OmniSharp.Services;
using OmniSharp.Utilities;
using System;
using System.Collections.Generic;
using System.Composition;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace OmniSharp.Roslyn.CSharp.Services.Decompilation
{
// due to dependency on Microsoft.CodeAnalysis.Editor.CSharp
// this class supports only net472
[Export(typeof(DecompilationExternalSourceService)), Shared]
public class DecompilationExternalSourceService : BaseExternalSourceService, IExternalSourceService
{
private const string DecompiledKey = "$Decompiled$";
private readonly ILoggerFactory _loggerFactory;
private readonly Lazy<OmniSharpCSharpDecompiledSourceService> _service;

[ImportingConstructor]
public DecompilationExternalSourceService(IAssemblyLoader loader, ILoggerFactory loggerFactory, OmniSharpWorkspace omniSharpWorkspace) : base(loader)
{
_loggerFactory = loggerFactory;
_service = new Lazy<OmniSharpCSharpDecompiledSourceService>(() => new OmniSharpCSharpDecompiledSourceService(omniSharpWorkspace.Services.GetLanguageServices(LanguageNames.CSharp), _loader, _loggerFactory));
}

public async Task<(Document document, string documentPath)> GetAndAddExternalSymbolDocument(Project project, ISymbol symbol, CancellationToken cancellationToken)
{
var fileName = symbol.GetFilePathForExternalSymbol(project);

Project decompilationProject;

// since submission projects cannot have new documents added to it
// we will use a separate project to hold decompiled documents
if (project.IsSubmission)
{
decompilationProject = project.Solution.Projects.FirstOrDefault(x => x.Name == DecompiledKey);
if (decompilationProject == null)
{
decompilationProject = project.Solution.AddProject(DecompiledKey, $"{DecompiledKey}.dll", LanguageNames.CSharp)
.WithCompilationOptions(project.CompilationOptions)
.WithMetadataReferences(project.MetadataReferences);
}
}
else
{
// for regular projects we will use current project to store decompiled docs
decompilationProject = project;
}

if (!_cache.TryGetValue(fileName, out var document))
{
var topLevelSymbol = symbol.GetTopLevelContainingNamedType();
var temporaryDocument = decompilationProject.AddDocument(fileName, string.Empty);

var compilation = await decompilationProject.GetCompilationAsync();
document = await _service.Value.AddSourceToAsync(temporaryDocument, compilation, topLevelSymbol, cancellationToken);

_cache.TryAdd(fileName, document);
}

return (document, fileName);
}
}
}
Loading