Skip to content

Commit d53c92e

Browse files
committed
step into decompiled methods from debugger
1 parent 5ce0fe8 commit d53c92e

File tree

10 files changed

+506
-11
lines changed

10 files changed

+506
-11
lines changed

src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,48 @@ public async Task<ImmutableArray<IdeReferenceLocationResult>> GetIdeReferenceLoc
991991
return changedFilesWithText;
992992
}
993993

994+
public async Task<string?> GetMetadataAsSourceFromDebuggingAssemblyAndType(string typeName, string assemblyName, Guid mvid, string userCodeCallingAssemblyPath, CancellationToken cancellationToken = default)
995+
{
996+
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(FindAllSymbolReferences)}");
997+
await _solutionLoadedTcs.Task;
998+
999+
var callingProject = _workspace!.CurrentSolution.Projects.SingleOrDefault(p => p.OutputFilePath == userCodeCallingAssemblyPath);
1000+
if (callingProject is null) return null;
1001+
1002+
var compilation = await callingProject.GetRequiredCompilationAsync(cancellationToken);
1003+
var symbols = compilation.GetTypesByMetadataName(typeName);
1004+
1005+
var symbol = symbols.SingleOrDefault(s =>
1006+
{
1007+
if (Path.GetFileNameWithoutExtension(s.ContainingModule.Name) != assemblyName) return false;
1008+
1009+
var containingAssembly = s.ContainingAssembly;
1010+
var metadataReference = compilation.GetMetadataReference(containingAssembly) as PortableExecutableReference;
1011+
if (metadataReference is null) return false;
1012+
var referenceAssemblyFilePath = metadataReference.FilePath;
1013+
// Runtime loads implementation assemblies, but Roslyn only has reference assemblies, so try to resolve the implementation assembly
1014+
if (referenceAssemblyFilePath is not null && MetadataAsSourceHelpers.IsReferenceAssembly(containingAssembly))
1015+
{
1016+
if (_implementationAssemblyLookupService.TryFindImplementationAssemblyPath(referenceAssemblyFilePath, out var implementationAssemblyLocation))
1017+
{
1018+
// TODO: Do we need to follow type forwards here?
1019+
// read the metadata from the implementation assembly, instead of the reference assembly, to get the correct MVID for comparison
1020+
metadataReference = MetadataReference.CreateFromFile(implementationAssemblyLocation);
1021+
}
1022+
}
1023+
if (metadataReference.GetMetadata() is not AssemblyMetadata assemblyMetadata) return false;
1024+
return assemblyMetadata.GetMvid() == mvid;
1025+
});
1026+
if (symbol is null) return null;
1027+
1028+
var options = MetadataAsSourceOptions.Default;// with { NavigateToSourceLinkAndEmbeddedSources = false };
1029+
var metadataAsSourceFile = await _metadataAsSourceFileService.GetGeneratedFileAsync(_workspace, callingProject, symbol, false, options, cancellationToken);
1030+
var metadataAsSourceWorkspace = _metadataAsSourceFileService.TryGetWorkspace();
1031+
var documentId = metadataAsSourceWorkspace!.CurrentSolution.GetDocumentIdsWithFilePath(metadataAsSourceFile.FilePath).SingleOrDefault();
1032+
var document = metadataAsSourceWorkspace.CurrentSolution.GetDocument(documentId);
1033+
return document?.FilePath;
1034+
}
1035+
9941036
public async Task<string?> WriteSourceFromMetadataAsSourceWorkspaceToDisk(string filePath, CancellationToken cancellationToken = default)
9951037
{
9961038
await _solutionLoadedTcs.Task;

src/SharpIDE.Application/Features/Analysis/SharpIdeMetadataAsSourceService.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,15 @@ public class SharpIdeMetadataAsSourceService(RoslynAnalysis roslynAnalysis)
3131
return metadataAsSourceSharpIdeFile;
3232
}
3333

34+
public async Task<SharpIdeFile?> CreateSharpIdeFileForMetadataAsSourceForTypeFromDebuggingAsync(string typeName, string assemblyPath, Guid mvid, string userCodeCallingAssemblyPath)
35+
{
36+
var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
37+
var filePath = await _roslynAnalysis.GetMetadataAsSourceFromDebuggingAssemblyAndType(typeName, assemblyName, mvid, userCodeCallingAssemblyPath);
38+
if (filePath is null) return null;
39+
var fileFromCache = _metadataAsSourceFileCache.GetValueOrDefault(filePath);
40+
if (fileFromCache is not null) return fileFromCache;
41+
var metadataAsSourceSharpIdeFile = new SharpIdeFile(filePath, Path.GetFileName(filePath), Path.GetExtension(filePath), null!, [], true);
42+
_metadataAsSourceFileCache[filePath] = metadataAsSourceSharpIdeFile;
43+
return metadataAsSourceSharpIdeFile;
44+
}
3445
}

src/SharpIDE.Application/Features/Analysis/WorkspaceServices/DecompileWholeAssemblyToProjectMetadataAsSourceFileProvider.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
using System.Reflection.Metadata;
55
using System.Reflection.PortableExecutable;
66
using System.Text;
7+
using ICSharpCode.Decompiler;
8+
using ICSharpCode.Decompiler.CSharp;
9+
using ICSharpCode.Decompiler.CSharp.Transforms;
710
using ICSharpCode.Decompiler.Metadata;
11+
using ICSharpCode.Decompiler.TypeSystem;
812
using Microsoft.CodeAnalysis;
913
using Microsoft.CodeAnalysis.Debugging;
1014
using Microsoft.CodeAnalysis.ErrorReporting;
@@ -18,6 +22,7 @@
1822
using Microsoft.CodeAnalysis.Text;
1923
using Roslyn.Utilities;
2024
using Document = Microsoft.CodeAnalysis.Document;
25+
using ISymbol = Microsoft.CodeAnalysis.ISymbol;
2126

2227
namespace SharpIDE.Application.Features.Analysis.WorkspaceServices;
2328

@@ -116,8 +121,7 @@ internal sealed class DecompileWholeAssemblyToProjectMetadataAsSourceFileProvide
116121
decompiledFiles = await GetSourceFilesFromSymbolCachePdb(refInfo.metadataReference, refInfo.assemblyLocation, assemblyKey, cancellationToken);
117122
if (decompiledFiles is null)
118123
{
119-
decompiledFiles = await DecompileAssemblyToMemoryAsync(
120-
compilation, refInfo.metadataReference, refInfo.assemblyLocation, cancellationToken).ConfigureAwait(false);
124+
decompiledFiles = await DecompileAssemblyToMemoryAsync(compilation, refInfo.metadataReference, refInfo.assemblyLocation, assemblyKey, cancellationToken);
121125
}
122126

123127
telemetryMessage?.SetDecompiled(decompiledFiles is not null);
@@ -180,7 +184,7 @@ internal sealed class DecompileWholeAssemblyToProjectMetadataAsSourceFileProvide
180184
if (metadataReference is null || assemblyLocation is null) return null;
181185

182186
// TBD
183-
var symbolCacheFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp", "SharpIdeSymbolCache");
187+
var symbolCacheFolderPath = SharpIdeDecompilationConstants.SymbolCachePath;
184188

185189
var assemblyName = Path.GetFileNameWithoutExtension(assemblyLocation);
186190
var mvid = assemblyKey.Mvid;
@@ -245,12 +249,14 @@ internal sealed class DecompileWholeAssemblyToProjectMetadataAsSourceFileProvide
245249
}
246250

247251
/// <summary>
248-
/// Decompiles all types in the assembly to a dictionary of relative path -> source code.
252+
/// Decompiles all types in the assembly to a dictionary of relative path -> source code,
253+
/// and writes a portable PDB with embedded sources to the symbol cache.
249254
/// </summary>
250255
private static Task<Dictionary<string, string>?> DecompileAssemblyToMemoryAsync(
251256
Compilation compilation,
252257
MetadataReference? metadataReference,
253258
string? assemblyLocation,
259+
UniqueAssemblyKey assemblyKey,
254260
CancellationToken cancellationToken)
255261
{
256262
return Task.Run(Dictionary<string, string>? () =>
@@ -270,8 +276,30 @@ internal sealed class DecompileWholeAssemblyToProjectMetadataAsSourceFileProvide
270276

271277
using (file)
272278
{
273-
var decompiler = new WholeAssemblyDecompiler(resolver);
274-
return decompiler.DecompileToMemory(file, cancellationToken);
279+
var settings = new DecompilerSettings();
280+
var ts = new DecompilerTypeSystem(file, resolver, settings);
281+
var decompiler = new CSharpDecompiler(ts, settings)
282+
{
283+
AstTransforms = {
284+
new TransformFieldAndConstructorInitializers(),
285+
new AddXmlDocumentationTransform(),
286+
new EscapeInvalidIdentifiers(),
287+
new FixNameCollisions(),
288+
new RemoveCLSCompliantAttribute(),
289+
new ReplaceMethodCallsWithOperators()
290+
}
291+
};
292+
293+
// Determine PDB cache path.
294+
var assemblyName = assemblyLocation is not null ? Path.GetFileNameWithoutExtension(assemblyLocation) : file.Name;
295+
var pdbDir = Path.Combine(SharpIdeDecompilationConstants.SymbolCachePath, assemblyName, assemblyKey.Mvid.ToString());
296+
var pdbPath = Path.Combine(pdbDir, $"{assemblyName}.decompiled.pdb");
297+
Directory.CreateDirectory(pdbDir);
298+
299+
using var pdbStream = new FileStream(pdbPath, FileMode.Create, FileAccess.Write, FileShare.None);
300+
var sourceFiles = PortablePdbWriter2.WritePdb(file, decompiler, settings, pdbStream, noLogo: true);
301+
302+
return sourceFiles.Count > 0 ? sourceFiles : null;
275303
}
276304
}, cancellationToken);
277305
}

0 commit comments

Comments
 (0)