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

Add documents to PDB for types that have no methods with IL #56278

Merged
merged 27 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
20ddc2e
Added new information to the Custom debug information table in the PD…
samaw7 Jun 3, 2021
6efc57d
Update PortableCustomDebugInfoKinds.cs
samaw7 Jun 3, 2021
5b37062
CLeanup
davidwengier Sep 9, 2021
9221f39
VB
davidwengier Sep 9, 2021
60d40b1
Tests
davidwengier Sep 9, 2021
d6fd6b4
Rename variables
davidwengier Sep 10, 2021
0e33168
Rename and null checks
davidwengier Sep 10, 2021
c89d418
Fix null ref for some synthesized members, and add tests
davidwengier Sep 10, 2021
6c7924a
Fix rebuild tests
davidwengier Sep 10, 2021
d823b7a
Use a list instead of a MultiDictionary, and pool some collections
davidwengier Sep 12, 2021
49af95e
Fix VB
davidwengier Sep 13, 2021
1c5f450
Remove optimization
davidwengier Sep 13, 2021
d53a61f
PR feedback
davidwengier Sep 15, 2021
c90e328
Revert changed lines
davidwengier Sep 16, 2021
611bcae
ImmutableArray all the things
davidwengier Sep 23, 2021
51740d6
Fix return type
davidwengier Sep 24, 2021
cff5094
Support nested classes
davidwengier Sep 24, 2021
86389fd
Fix doc comment
davidwengier Sep 24, 2021
3b9800f
More tests
davidwengier Sep 24, 2021
92ba815
Test, and fix, line directives
davidwengier Sep 24, 2021
9ae7f82
Remove redundant checks
davidwengier Sep 24, 2021
36947a6
Fix PDB tests
davidwengier Sep 24, 2021
754b1ba
Ignore line directives and always use original locations
davidwengier Sep 26, 2021
345d514
Never emit document info for nested types, but always consider method…
davidwengier Sep 26, 2021
ddedd69
PR feedback
davidwengier Sep 27, 2021
8d2c248
Update src/Compilers/VisualBasic/Portable/Emit/PEModuleBuilder.vb
davidwengier Sep 27, 2021
35d3832
Merge remote-tracking branch 'upstream/main' into MoreCustomDebugInfo
davidwengier Sep 27, 2021
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
102 changes: 102 additions & 0 deletions src/Compilers/CSharp/Portable/Emitter/Model/PEModuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,108 @@ private void ValidateReferencedAssembly(AssemblySymbol assembly, AssemblyReferen
return null;
}

public sealed override MultiDictionary<Cci.ITypeDefinition, Cci.DebugSourceDocument> GetTypeToDebugDocumentMap(EmitContext context)
{
var result = new MultiDictionary<Cci.ITypeDefinition, Cci.DebugSourceDocument>(Cci.SymbolEquivalentEqualityComparer.Instance);

var namespacesAndTypesToProcess = new Stack<NamespaceOrTypeSymbol>();
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
namespacesAndTypesToProcess.Push(SourceModule.GlobalNamespace);
while (namespacesAndTypesToProcess.Count > 0)
{
var symbol = namespacesAndTypesToProcess.Pop();

switch (symbol.Kind)
{
case SymbolKind.Namespace:
var location = GetSmallestSourceLocationOrNull(symbol);

// filtering out synthesized symbols not having real source
// locations such as anonymous types, etc...
if (location != null)
{
foreach (var member in symbol.GetMembers())
{
switch (member.Kind)
{
case SymbolKind.Namespace:
case SymbolKind.NamedType:
namespacesAndTypesToProcess.Push((NamespaceOrTypeSymbol)member);
break;
default:
throw ExceptionUtilities.UnexpectedValue(member.Kind);
}
}
}
break;
case SymbolKind.NamedType:
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
var methodDocumentList = GetDocumentsForMethods(symbol, context, out var allMethodsHaveIL);

// If all of the methods have IL then the document data we already have is enough, but if not
// then we want to add document info for the type.
if (!allMethodsHaveIL)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
{
var typeDefinition = (Cci.ITypeDefinition)symbol.GetCciAdapter();

foreach (var loc in symbol.Locations)
{
if (!loc.IsInSource)
{
continue;
}

var span = loc.GetLineSpan();
var debugDocument = DebugDocumentsBuilder.TryGetDebugDocument(span.Path, basePath: null);

// We don't need to duplicate the data, if the method debug info would already contain this document
if (debugDocument is not null && !methodDocumentList.Contains(debugDocument))
{
result.Add(typeDefinition, debugDocument);
}
}
}
break;
default:
throw ExceptionUtilities.UnexpectedValue(symbol.Kind);
}
}
return result;
}

/// <summary>
/// Gets a list of documents from the method definitions in this type, and also outputs whether there are
/// any methods that don't have document info (ie don't have any IL)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
private static HashSet<Cci.DebugSourceDocument> GetDocumentsForMethods(NamespaceOrTypeSymbol typeSymbol, EmitContext context, out bool allMethodsHaveIL)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
{
var documentList = new HashSet<Cci.DebugSourceDocument>();

var typeDef = (Cci.ITypeDefinition)typeSymbol.GetCciAdapter();
var typeMethods = typeDef.GetMethods(context);

var foundMethodWithIL = false;
var foundMethodWithoutIL = false;
foreach (var method in typeMethods)
{
if (Cci.Extensions.HasBody(method) &&
method.GetBody(context) is { } body &&
!body.SequencePoints.IsEmpty)
{
foundMethodWithIL = true;

foreach (var point in body.SequencePoints)
{
documentList.Add(point.Document);
}
}
else
{
foundMethodWithoutIL = true;
}
}
allMethodsHaveIL = foundMethodWithIL && !foundMethodWithoutIL;
return documentList;
}

public sealed override MultiDictionary<Cci.DebugSourceDocument, Cci.DefinitionWithLocation> GetSymbolToLocationMap()
{
var result = new MultiDictionary<Cci.DebugSourceDocument, Cci.DefinitionWithLocation>();
Expand Down
57 changes: 57 additions & 0 deletions src/Compilers/CSharp/Test/Emit/PDB/CSharpPDBTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@

#nullable disable

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Xml;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
Expand Down Expand Up @@ -67,5 +73,56 @@ public static bool CheckIfSpanWithinSequencePoints(TextSpan span, string source,

return false;
}

public static void TestTypeDocuments(string[] sources, params (string typeName, string documentName)[] expected)
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
{
var trees = sources.Select((s, i) => SyntaxFactory.ParseSyntaxTree(s, path: $"{i + 1}.cs", encoding: Encoding.UTF8)).ToArray();
var compilation = CreateCompilation(trees, options: TestOptions.DebugDll);

var pdbStream = new MemoryStream();
var pe = compilation.EmitToArray(EmitOptions.Default.WithDebugInformationFormat(DebugInformationFormat.PortablePdb), pdbStream: pdbStream);
pdbStream.Position = 0;

var metadata = ModuleMetadata.CreateFromImage(pe);
var metadataReader = metadata.GetMetadataReader();

var provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
davidwengier marked this conversation as resolved.
Show resolved Hide resolved
var pdbReader = provider.GetMetadataReader();

var actual = from handle in pdbReader.CustomDebugInformation
let entry = pdbReader.GetCustomDebugInformation(handle)
where pdbReader.GetGuid(entry.Kind).Equals(PortableCustomDebugInfoKinds.TypeDocument)
select (typeName: GetTypeName(entry.Parent), documentName: GetDocumentNames(entry.Value));

AssertEx.Equal(expected, actual, itemSeparator: ", ", itemInspector: i => $"(\"{i.typeName}\", \"{i.documentName}\")");

string GetTypeName(EntityHandle handle)
{
var typeHandle = (TypeDefinitionHandle)handle;
var type = metadataReader.GetTypeDefinition(typeHandle);
return metadataReader.GetString(type.Name);
}

string GetDocumentNames(BlobHandle value)
{
var result = new List<string>();

var reader = pdbReader.GetBlobReader(value);
while (reader.RemainingBytes > 0)
{
var documentRow = reader.ReadCompressedInteger();
if (documentRow > 0)
{
var doc = pdbReader.GetDocument(MetadataTokens.DocumentHandle(documentRow));
result.Add(pdbReader.GetString(doc.Name));
}
}

// Order can be different in net472 vs net5 :(
result.Sort();

return string.Join(", ", result);
}
}
}
}
Loading