提交 6eddf132 编写于 作者: T Tomáš Matoušek

Merge pull request #4451 from tmat/Ranges

[PortablePDB] Implement GetOffset, GetRanges, FindClosestLine
......@@ -129,19 +129,31 @@
<HintPath>..\..\..\packages\xunit.extensibility.core.2.1.0-beta2-build2981\lib\portable-net45+dnxcore50+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Async.dll">
<LogicalName>Async.dll</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Async.pdb">
<LogicalName>Async.pdb</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Async.dllx">
<LogicalName>Async.dllx</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Async.pdbx">
<LogicalName>Async.pdbx</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Documents.dll">
<LogicalName>Documents.dll</LogicalName>
<EmbeddedResource Include="Resources\Documents.dllx">
<LogicalName>Documents.dllx</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Documents.pdbx">
<LogicalName>Documents.pdbx</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Documents.dll">
<LogicalName>Documents.dll</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Documents.pdb">
<LogicalName>Documents.pdb</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\Scopes.dll">
<LogicalName>Scopes.dll</LogicalName>
</EmbeddedResource>
......
csc /target:library /debug+ /features:pdb=portable /optimize- /features:deterministic Async.cs
copy /y Async.pdb Async.pdbx
del Async.pdb
copy /y Async.dll Async.dllx
csc /target:library /debug+ /optimize- /features:deterministic Async.cs
csc /target:library /debug+ /features:pdb=portable /optimize- /features:deterministic Documents.cs
csc /target:library /debug+ /features:pdb=portable /optimize- /features:deterministic Documents.cs
copy /y Documents.pdb Documents.pdbx
del Documents.pdb
copy /y Documents.dll Documents.dllx
csc /target:library /debug+ /optimize- /features:deterministic Documents.cs
......@@ -30,5 +30,9 @@ public void M()
Console.WriteLine(5);
#line 110 ":6.cs"
Console.WriteLine(6);
#line 120 "C:\a\b\X.cs"
Console.WriteLine(7);
#line 130 "C:\a\B\x.cs"
Console.WriteLine(8);
}
}
......@@ -3,145 +3,145 @@
partial class C
{
int i = F() // 6
+ // (7)
F(); // (8)
int i = F() // 5
+ // (6)
F(); // (7)
public C() // 10
{ // 11
F(); // 12
} // 13
public C() // 9
{ // 10
F(); // 11
} // 12
int j = F(); // 15
int j = F(); // 14
public static int F()
{ // 18
{ // 17
#line 10 "C:\MethodBoundaries1.cs"
F(); // 11
F(); // 10
#line 5 "C:\MethodBoundaries1.cs"
F(); // 6
F(); // 5
F(); // 7
F(); // 8
F(); // 9
#line 5 "C:\MethodBoundaries1.cs"
F(); // 6
F(); // 5
#line 1 "C:\MethodBoundaries2.cs"
F(); // 2-2
F(); // 2-1
#line 20 "C:\MethodBoundaries1.cs"
F(); // 21
F(); // 20
return 1; // 23
} // 24
return 1; // 22
} // 23
public static int G()
#line 4 "C:\MethodBoundaries1.cs"
{ // 5
F( // 6
// (7)
); // (8)
return 1; // 9
} // 10
{ // 4
F( // 5
// (6)
); // (7)
return 1; // 8
} // 9
#line 5 "C:\MethodBoundaries2.cs"
public static int E0() => F(); // 6
public static int E0() => F(); // 5
#line 7 "C:\MethodBoundaries2.cs"
public static int E1() => F(); // 8
public static int E1() => F(); // 7
public static int H()
#line 4 "C:\MethodBoundaries2.cs"
{ // 5
F( // 6
{ // 4
F( // 5
// (6)
// (7)
// (8)
); // (9)
return 1; // 10
} // 11
); // (8)
return 1; // 9
} // 10
#line 6 "C:\MethodBoundaries2.cs"
public static int E2() => F(); // 7
public static int E2() => F(); // 6
public static int E3() =>
#line 8 "C:\MethodBoundaries2.cs"
F() + // 9
F(); // (10)
F() + // 8
F(); // (9)
public static int E4() =>
#line 9 "C:\MethodBoundaries2.cs"
F(); // 10
F(); // 9
// Overlapping sequence point spans from different methods.
public static void J1()
#line 13 "C:\MethodBoundaries2.cs"
{ // 14
F(); // 15
} // 16
{ // 13
F(); // 14
} // 15
public static int I()
#line 11 "C:\MethodBoundaries2.cs"
{ // 12
F( // 13
{ // 11
F( // 12
// (13) overlaps with J1
// (14) overlaps with J1
// (15) overlaps with J1
// (16) overlaps with J1
// (16) overlaps with J2
// (17) overlaps with J2
// (18) overlaps with J2
// (18)
// (19)
// (20)
// (21)
); // (22)
return 1; // 23
} // 24
); // (21)
return 1; // 22
} // 23
public static void J2()
#line 16 "C:\MethodBoundaries2.cs"
{ // 17
F(); // 18
{ // 16
F(); // 17
#line 28 "C:\MethodBoundaries2.cs"
} // 29
} // 28
public static void K1()
#line 1 "C:\MethodBoundaries3.cs"
{ // 2 K1
F( // 3 K1
{ // 1 K1
F( // 2 K1
// (3) K1, K2
// (4) K1, K2
// (5) K1, K2
// (5) K1, K2, K3
// (6) K1, K2, K3
// (7) K1, K2, K3
// (7) K1, K2, K3, K4
// (8) K1, K2, K3, K4
// (9) K1, K2, K3, K4
// (9) K1, K2, K3
// (10) K1, K2, K3
// (11) K1, K2, K3
); // (12) K1, K2
} // 13 K1
); // (11) K1, K2
} // 12 K1
public static void K2()
#line 3 "C:\MethodBoundaries3.cs"
{ // 4
F( // 5
{ // 3
F( // 4
// (5)
// (6)
// (7)
// (8)
// (9)
// (10)
); // (11)
} // 12
// (9)
); // (10)
} // 11
public static void K3()
#line 5 "C:\MethodBoundaries3.cs"
{ // 6
F( // 7
{ // 5
F( // 6
// (7)
// (8)
// (9)
); // (10)
} // 11
); // (9)
} // 10
public static void K4() =>
#line 7 "C:\MethodBoundaries3.cs"
F( // 8
); // (9)
F( // 7
); // (8)
}
\ No newline at end of file
......@@ -14,10 +14,10 @@ public class SymBinderTests
[Fact]
public void GetReaderForFile()
{
var importer = new SymMetadataImport(new MemoryStream(TestResources.Documents.Dll));
var importer = new SymMetadataImport(new MemoryStream(TestResources.Documents.PortableDll));
string filePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
File.WriteAllBytes(filePath, TestResources.Documents.Pdb);
File.WriteAllBytes(filePath, TestResources.Documents.PortablePdb);
string searchPath = null;
......@@ -26,7 +26,7 @@ public void GetReaderForFile()
int actualCount;
Assert.Equal(HResult.S_OK, symReader.GetDocuments(0, out actualCount, null));
Assert.Equal(11, actualCount);
Assert.Equal(13, actualCount);
Assert.Equal(HResult.S_FALSE, ((ISymUnmanagedDispose)symReader).Destroy());
Assert.Equal(HResult.S_OK, ((ISymUnmanagedDispose)symReader).Destroy());
......@@ -39,8 +39,8 @@ public void GetReaderForFile()
[Fact]
public void GetReaderFromStream()
{
var importer = new SymMetadataImport(new MemoryStream(TestResources.Documents.Dll));
var stream = new MemoryStream(TestResources.Documents.Pdb);
var importer = new SymMetadataImport(new MemoryStream(TestResources.Documents.PortableDll));
var stream = new MemoryStream(TestResources.Documents.PortablePdb);
var wrapper = new ComStreamWrapper(stream);
ISymUnmanagedReader symReader;
......@@ -48,7 +48,7 @@ public void GetReaderFromStream()
int actualCount;
Assert.Equal(HResult.S_OK, symReader.GetDocuments(0, out actualCount, null));
Assert.Equal(11, actualCount);
Assert.Equal(13, actualCount);
Assert.Equal(HResult.S_FALSE, ((ISymUnmanagedDispose)symReader).Destroy());
Assert.Equal(HResult.S_OK, ((ISymUnmanagedDispose)symReader).Destroy());
......
......@@ -14,9 +14,9 @@ public class SymReaderTests
[Fact]
public unsafe void TestMetadataHeaders1()
{
fixed (byte* pdbPtr = TestResources.Documents.Pdb)
fixed (byte* pdbPtr = TestResources.Documents.PortablePdb)
{
var pdbReader = new MetadataReader(pdbPtr, TestResources.Documents.Pdb.Length);
var pdbReader = new MetadataReader(pdbPtr, TestResources.Documents.PortablePdb.Length);
Assert.Equal("PDB v0.1", pdbReader.MetadataVersion);
Assert.Equal(MetadataKind.Ecma335, pdbReader.MetadataKind);
Assert.False(pdbReader.IsAssembly);
......@@ -27,16 +27,16 @@ public unsafe void TestMetadataHeaders1()
[Fact]
public void TestGetDocuments1()
{
var symReader = CreateSymReaderFromResource(TestResources.Documents.DllAndPdb);
var symReader = CreateSymReaderFromResource(TestResources.Documents.PortableDllAndPdb);
int actualCount;
Assert.Equal(HResult.S_OK, symReader.GetDocuments(0, out actualCount, null));
Assert.Equal(11, actualCount);
Assert.Equal(13, actualCount);
var actualDocuments = new ISymUnmanagedDocument[actualCount];
int actualCount2;
Assert.Equal(HResult.S_OK, symReader.GetDocuments(actualCount, out actualCount2, actualDocuments));
Assert.Equal(11, actualCount2);
Assert.Equal(13, actualCount2);
ValidateDocument(actualDocuments[0],
url: @"C:\Documents.cs",
......@@ -53,14 +53,17 @@ public void TestGetDocuments1()
ValidateDocument(actualDocuments[8], url: @"C:\a\B\c\4.cs", algorithmId: null, checksum: null);
ValidateDocument(actualDocuments[9], url: @"C:\*\5.cs", algorithmId: null, checksum: null);
ValidateDocument(actualDocuments[10], url: @":6.cs", algorithmId: null, checksum: null);
ValidateDocument(actualDocuments[11], url: @"C:\a\b\X.cs", algorithmId: null, checksum: null);
ValidateDocument(actualDocuments[12], url: @"C:\a\B\x.cs", algorithmId: null, checksum: null);
}
[Fact]
public void TestGetDocument1()
{
var symReader = CreateSymReaderFromResource(TestResources.Documents.DllAndPdb);
var symReader = CreateSymReaderFromResource(TestResources.Documents.PortableDllAndPdb);
TestGetDocument(symReader, @"x.cs", expectedUrl: @"C:\a\b\c\d\x.cs");
TestGetDocument(symReader, @"X.CS", expectedUrl: @"C:\a\b\c\d\x.cs");
TestGetDocument(symReader, @"X.cs", expectedUrl: @"C:\a\b\X.cs");
TestGetDocument(symReader, @"1.cs", expectedUrl: @"C:\a\b\c\d\1.cs");
TestGetDocument(symReader, @"2.cs", expectedUrl: @"C:\a\b\c\D\2.cs");
TestGetDocument(symReader, @"3.cs", expectedUrl: @"C:\a\b\C\d\3.cs");
......@@ -69,6 +72,8 @@ public void TestGetDocument1()
TestGetDocument(symReader, @"C:\*\5.cs", expectedUrl: @"C:\*\5.cs");
TestGetDocument(symReader, @"5.cs", expectedUrl: @"C:\*\5.cs");
TestGetDocument(symReader, @":6.cs", expectedUrl: @":6.cs");
TestGetDocument(symReader, @"C:\a\B\x.cs", expectedUrl: @"C:\a\B\x.cs");
TestGetDocument(symReader, @"C:\a\b\X.cs", expectedUrl: @"C:\a\b\X.cs");
}
private void TestGetDocument(ISymUnmanagedReader symReader, string name, string expectedUrl)
......@@ -91,7 +96,7 @@ private void TestGetDocument(ISymUnmanagedReader symReader, string name, string
[Fact]
public void TestSymGetAttribute()
{
var symReader = CreateSymReaderFromResource(TestResources.Documents.DllAndPdb);
var symReader = CreateSymReaderFromResource(TestResources.Documents.PortableDllAndPdb);
int actualCount;
int actualCount2;
......@@ -101,7 +106,7 @@ public void TestSymGetAttribute()
Assert.Equal(HResult.S_OK, symReader.GetSymAttribute(0, "<PortablePdbImage>", actualCount, out actualCount2, image));
Assert.Equal(actualCount, actualCount2);
AssertEx.Equal(TestResources.Documents.Pdb, image);
AssertEx.Equal(TestResources.Documents.PortablePdb, image);
}
[Fact]
......
......@@ -2,7 +2,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using Roslyn.Test.PdbUtilities;
using Roslyn.Test.Utilities;
......@@ -231,5 +233,177 @@ public static void ValidateAsyncMethod(ISymUnmanagedReader symReader, int moveNe
Assert.Equal(moveNextMethodToken, actualResumeMethod);
}
}
internal static int[] GetMethodTokensFromDocumentPosition(
ISymUnmanagedReader symReader,
ISymUnmanagedDocument symDocument,
int line,
int column)
{
Assert.True(line >= 1);
Assert.True(column >= 0);
int count;
Assert.Equal(HResult.S_OK, symReader.GetMethodsFromDocumentPosition(symDocument, line, column, 0, out count, null));
var methods = new ISymUnmanagedMethod[count];
int count2;
Assert.Equal(HResult.S_OK, symReader.GetMethodsFromDocumentPosition(symDocument, line, column, count, out count2, methods));
Assert.Equal(count, count2);
return methods.Select(m =>
{
int token;
Assert.Equal(HResult.S_OK, m.GetToken(out token));
return token;
}).ToArray();
}
internal static int[][] GetMethodTokensForEachLine(ISymUnmanagedReader symReader, ISymUnmanagedDocument symDocument, int minLine, int maxLine)
{
Assert.True(minLine >= 1);
Assert.True(maxLine >= minLine);
var result = new List<int[]>();
for (int line = minLine; line <= maxLine; line++)
{
int[] allMethodTokens = GetMethodTokensFromDocumentPosition(symReader, symDocument, line, 0);
ISymUnmanagedMethod method;
int hr = symReader.GetMethodFromDocumentPosition(symDocument, line, 1, out method);
if (hr != HResult.S_OK)
{
Assert.Equal(HResult.E_FAIL, hr);
Assert.Equal(0, allMethodTokens.Length);
}
else
{
int primaryToken;
Assert.Equal(HResult.S_OK, method.GetToken(out primaryToken));
Assert.Equal(primaryToken, allMethodTokens.First());
}
result.Add(allMethodTokens);
}
return result.ToArray();
}
public static int[] GetILOffsetForEachLine(
ISymUnmanagedReader symReader,
int methodToken,
ISymUnmanagedDocument document,
int minLine, int maxLine)
{
Assert.True(minLine >= 1);
Assert.True(maxLine >= minLine);
var result = new List<int>();
ISymUnmanagedMethod method;
Assert.Equal(HResult.S_OK, symReader.GetMethod(methodToken, out method));
for (int line = minLine; line <= maxLine; line++)
{
int offset;
int hr = method.GetOffset(document, line, 0, out offset);
if (hr != HResult.S_OK)
{
Assert.Equal(HResult.E_FAIL, hr);
offset = int.MaxValue;
}
result.Add(offset);
}
return result.ToArray();
}
public static int[][] GetILOffsetRangesForEachLine(
ISymUnmanagedReader symReader,
int methodToken,
ISymUnmanagedDocument document,
int minLine, int maxLine)
{
Assert.True(minLine >= 1);
Assert.True(maxLine >= minLine);
var result = new List<int[]>();
ISymUnmanagedMethod method;
Assert.Equal(HResult.S_OK, symReader.GetMethod(methodToken, out method));
for (int line = minLine; line <= maxLine; line++)
{
int count;
Assert.Equal(HResult.S_OK, method.GetRanges(document, line, 0, 0, out count, null));
int count2;
int[] ranges = new int[count];
Assert.Equal(HResult.S_OK, method.GetRanges(document, line, 0, count, out count2, ranges));
Assert.Equal(count, count2);
result.Add(ranges);
}
return result.ToArray();
}
public static void ValidateMethodExtent(ISymUnmanagedReader symReader, int methodDef, string documentName, int minLine, int maxLine)
{
Assert.True(minLine >= 1);
Assert.True(maxLine >= minLine);
ISymUnmanagedMethod method;
Assert.Equal(HResult.S_OK, symReader.GetMethod(methodDef, out method));
ISymUnmanagedDocument document;
Assert.Equal(HResult.S_OK, symReader.GetDocument(documentName, default(Guid), default(Guid), default(Guid), out document));
int actualMinLine, actualMaxLine;
Assert.Equal(HResult.S_OK, ((ISymEncUnmanagedMethod)method).GetSourceExtentInDocument(document, out actualMinLine, out actualMaxLine));
Assert.Equal(minLine, actualMinLine);
Assert.Equal(maxLine, actualMaxLine);
}
public static void ValidateNoMethodExtent(ISymUnmanagedReader symReader, int methodDef, string documentName)
{
ISymUnmanagedMethod method;
Assert.Equal(HResult.S_OK, symReader.GetMethod(methodDef, out method));
ISymUnmanagedDocument document;
Assert.Equal(HResult.S_OK, symReader.GetDocument(documentName, default(Guid), default(Guid), default(Guid), out document));
int actualMinLine, actualMaxLine;
Assert.Equal(HResult.E_FAIL, ((ISymEncUnmanagedMethod)method).GetSourceExtentInDocument(document, out actualMinLine, out actualMaxLine));
}
public static int[] FindClosestLineForEachLine(ISymUnmanagedDocument document, int minLine, int maxLine)
{
Assert.True(minLine >= 1);
Assert.True(maxLine >= minLine);
var result = new List<int>();
for (int line = minLine; line <= maxLine; line++)
{
int closestLine;
int hr = document.FindClosestLine(line, out closestLine);
if (hr != HResult.S_OK)
{
Assert.Equal(HResult.E_FAIL, hr);
closestLine = 0;
}
result.Add(closestLine);
}
return result.ToArray();
}
}
}
......@@ -7,12 +7,19 @@ namespace TestResources
{
public static class Documents
{
private static byte[] _portableDll;
public static byte[] PortableDll => ResourceLoader.GetOrCreateResource(ref _portableDll, nameof(Documents) + ".dllx");
private static byte[] _portablePdb;
public static byte[] PortablePdb => ResourceLoader.GetOrCreateResource(ref _portablePdb, nameof(Documents) + ".pdbx");
private static byte[] _dll;
public static byte[] Dll => ResourceLoader.GetOrCreateResource(ref _dll, nameof(Documents) + ".dll");
private static byte[] _pdb;
public static byte[] Pdb => ResourceLoader.GetOrCreateResource(ref _pdb, nameof(Documents) + ".pdbx");
public static byte[] Pdb => ResourceLoader.GetOrCreateResource(ref _pdb, nameof(Documents) + ".pdb");
public static KeyValuePair<byte[], byte[]> PortableDllAndPdb => new KeyValuePair<byte[], byte[]>(PortableDll, PortablePdb);
public static KeyValuePair<byte[], byte[]> DllAndPdb => new KeyValuePair<byte[], byte[]>(Dll, Pdb);
}
......@@ -29,12 +36,19 @@ public static class Scopes
public static class Async
{
private static byte[] _portableDll;
public static byte[] PortableDll => ResourceLoader.GetOrCreateResource(ref _portableDll, nameof(Async) + ".dllx");
private static byte[] _portablePdb;
public static byte[] PortablePdb => ResourceLoader.GetOrCreateResource(ref _portablePdb, nameof(Async) + ".pdbx");
private static byte[] _dll;
public static byte[] Dll => ResourceLoader.GetOrCreateResource(ref _dll, nameof(Async) + ".dll");
private static byte[] _pdb;
public static byte[] Pdb => ResourceLoader.GetOrCreateResource(ref _pdb, nameof(Async) + ".pdbx");
public static byte[] Pdb => ResourceLoader.GetOrCreateResource(ref _pdb, nameof(Async) + ".pdb");
public static KeyValuePair<byte[], byte[]> PortableDllAndPdb => new KeyValuePair<byte[], byte[]>(PortableDll, PortablePdb);
public static KeyValuePair<byte[], byte[]> DllAndPdb => new KeyValuePair<byte[], byte[]>(Dll, Pdb);
}
......
......@@ -4,25 +4,37 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
namespace Microsoft.DiaSymReader.PortablePdb
{
internal sealed class DocumentMap
{
private struct DocumentNameAndHandle
{
public readonly DocumentHandle Handle;
public readonly string FileName;
public DocumentNameAndHandle(DocumentHandle handle, string fileName)
{
Handle = handle;
FileName = fileName;
}
}
private readonly MetadataReader _reader;
// { last part of document name -> one or many document handles that have the part in commmon }
private readonly IReadOnlyDictionary<string, KeyValuePair<DocumentHandle, ImmutableArray<DocumentHandle>>> _map;
private readonly IReadOnlyDictionary<string, KeyValuePair<DocumentNameAndHandle, ImmutableArray<DocumentNameAndHandle>>> _map;
public DocumentMap(MetadataReader reader)
{
_reader = reader;
// group ignoring case, we will match the case within the group
_map = GetDocumentsByFileName(reader).GroupBy(StringComparer.OrdinalIgnoreCase);
}
private static IEnumerable<KeyValuePair<string, DocumentHandle>> GetDocumentsByFileName(MetadataReader reader)
private static IEnumerable<KeyValuePair<string, DocumentNameAndHandle>> GetDocumentsByFileName(MetadataReader reader)
{
foreach (var documentHandle in reader.Documents)
{
......@@ -34,7 +46,7 @@ public DocumentMap(MetadataReader reader)
continue;
}
yield return new KeyValuePair<string, DocumentHandle>(fileName, documentHandle);
yield return new KeyValuePair<string, DocumentNameAndHandle>(fileName, new DocumentNameAndHandle(documentHandle, fileName));
}
}
......@@ -83,7 +95,7 @@ internal bool TryGetDocument(string fullPath, out DocumentHandle documentHandle)
{
var fileName = FileNameUtilities.GetFileName(fullPath);
KeyValuePair<DocumentHandle, ImmutableArray<DocumentHandle>> documents;
KeyValuePair<DocumentNameAndHandle, ImmutableArray<DocumentNameAndHandle>> documents;
if (!_map.TryGetValue(fileName, out documents))
{
documentHandle = default(DocumentHandle);
......@@ -92,11 +104,11 @@ internal bool TryGetDocument(string fullPath, out DocumentHandle documentHandle)
// SymReader first attempts to find the document by the full path, then by file name with extension.
if (!documents.Key.IsNil)
if (documents.Key.FileName != null)
{
// There is only one document with the specified file name.
// SymReader returns the document regardless of whether the path matches the name.
documentHandle = documents.Key;
documentHandle = documents.Key.Handle;
return true;
}
......@@ -105,16 +117,37 @@ internal bool TryGetDocument(string fullPath, out DocumentHandle documentHandle)
// We have multiple candidates with the same file name. Find the one whose name matches the specified full path.
// If none does return the first one. It will be the one with the smallest handle, due to the multi-map construction implementation.
foreach (DocumentHandle candidateHandle in documents.Value)
// First try to find candidate whose full name is exactly matching.
foreach (DocumentNameAndHandle candidate in documents.Value)
{
if (_reader.StringComparer.Equals(_reader.GetDocument(candidate.Handle).Name, fullPath, ignoreCase: false))
{
documentHandle = candidate.Handle;
return true;
}
}
// Then try to find candidate whose full name is matching ignoring case.
foreach (DocumentNameAndHandle candidate in documents.Value)
{
if (_reader.StringComparer.Equals(_reader.GetDocument(candidate.Handle).Name, fullPath, ignoreCase: true))
{
documentHandle = candidate.Handle;
return true;
}
}
// Then try to find candidate whose file name is matching exactly.
foreach (DocumentNameAndHandle candidate in documents.Value)
{
if (_reader.StringComparer.Equals(_reader.GetDocument(candidateHandle).Name, fullPath, ignoreCase: true))
if (candidate.FileName == fileName)
{
documentHandle = candidateHandle;
documentHandle = candidate.Handle;
return true;
}
}
documentHandle = documents.Value[0];
documentHandle = documents.Value[0].Handle;
return true;
}
}
......
......@@ -46,6 +46,7 @@ private struct MethodsInDocument
// Consider: we could remove the MaxLine from this list and look it up in ExtensByMinLine
public readonly ImmutableArray<MethodLineExtent> ExtentsByMethod;
// Represents method extents partitioned into non-overlapping subsequences, each sorted by min line.
public readonly ImmutableArray<ImmutableArray<MethodLineExtent>> ExtentsByMinLine;
public MethodsInDocument(ImmutableArray<MethodLineExtent> extentsByMethod, ImmutableArray<ImmutableArray<MethodLineExtent>> extentsByMinLine)
......@@ -55,9 +56,6 @@ public MethodsInDocument(ImmutableArray<MethodLineExtent> extentsByMethod, Immut
}
}
// For each document holds on a list of method extent runs.
// Each method is represented exactly once.
// Each run is sorted first by method token and then by start line. Extents in each run are non-everlapping.
private readonly IReadOnlyDictionary<DocumentHandle, MethodsInDocument> _methodsByDocument;
public MethodMap(MetadataReader reader)
......@@ -173,6 +171,13 @@ private static ImmutableArray<ImmutableArray<MethodLineExtent>> PartitionToNonOv
foreach (var methodBodyHandle in reader.MethodBodies)
{
var methodBody = reader.GetMethodBody(methodBodyHandle);
// no debug info for the method
if (methodBody.SequencePoints.IsNil)
{
continue;
}
var methodBodyReader = reader.GetBlobReader(methodBody.SequencePoints);
// skip signature:
......@@ -233,32 +238,36 @@ public IEnumerable<MethodBodyHandle> GetMethodsContainingLine(DocumentHandle doc
return EnumerateMethodsContainingLine(methodsInDocument.ExtentsByMinLine, line);
}
private static IEnumerable<MethodBodyHandle> EnumerateMethodsContainingLine(ImmutableArray<ImmutableArray<MethodLineExtent>> runs, int line)
private static IEnumerable<MethodBodyHandle> EnumerateMethodsContainingLine(ImmutableArray<ImmutableArray<MethodLineExtent>> extents, int line)
{
foreach (var run in runs)
foreach (var subsequence in extents)
{
int index = IndexOfContainingExtent(run, line);
int closestFollowingExtent;
int index = IndexOfContainingExtent(subsequence, line, out closestFollowingExtent);
if (index >= 0)
{
yield return run[index].Method;
yield return subsequence[index].Method;
}
}
}
private static int IndexOfContainingExtent(ImmutableArray<MethodLineExtent> run, int startLine)
private static int IndexOfContainingExtent(ImmutableArray<MethodLineExtent> orderedNonOverlappingExtents, int startLine, out int closestFollowingExtent)
{
int index = run.BinarySearch(startLine, (extent, line) => extent.MinLine - line);
closestFollowingExtent = -1;
int index = orderedNonOverlappingExtents.BinarySearch(startLine, (extent, line) => extent.MinLine - line);
if (index >= 0)
{
return index;
}
int preceding = ~index - 1;
if (preceding >= 0 && startLine <= run[preceding].MaxLine)
if (preceding >= 0 && startLine <= orderedNonOverlappingExtents[preceding].MaxLine)
{
return preceding;
}
closestFollowingExtent = ~index;
return -1;
}
......@@ -294,5 +303,28 @@ internal bool TryGetMethodSourceExtent(DocumentHandle documentHandle, MethodBody
endLine = extent.MaxLine;
return true;
}
internal IEnumerable<MethodLineExtent> EnumerateContainingOrClosestFollowingMethodExtents(DocumentHandle documentHandle, int line)
{
MethodsInDocument methodsInDocument;
if (!_methodsByDocument.TryGetValue(documentHandle, out methodsInDocument))
{
yield break;
}
foreach (var subsequence in methodsInDocument.ExtentsByMinLine)
{
int closestFollowingExtent;
int index = IndexOfContainingExtent(subsequence, line, out closestFollowingExtent);
if (index >= 0)
{
yield return subsequence[index];
}
else if (closestFollowingExtent < subsequence.Length)
{
yield return subsequence[closestFollowingExtent];
}
}
}
}
}
......@@ -30,8 +30,51 @@ internal SymDocument(SymReader symReader, DocumentHandle documentHandle)
public int FindClosestLine(int line, out int closestLine)
{
// TODO:
throw new NotImplementedException();
// Find a minimal sequence point start line in this docuemnt
// that is greater than or equal to the given line.
int result = int.MaxValue;
var map = SymReader.GetMethodMap();
var mdReader = SymReader.MetadataReader;
// Note DiaSymReader searches accross all documents with the same file name in CDiaWrapper::FindClosestLineAcrossFileIDs. We don't.
foreach (var extent in map.EnumerateContainingOrClosestFollowingMethodExtents(Handle, line))
{
Debug.Assert(extent.MaxLine >= line);
// extent is further than a sequence point we already found:
if (extent.MinLine >= result)
{
continue;
}
// enumerate method sequence points:
var body = mdReader.GetMethodBody(extent.Method);
var spReader = mdReader.GetSequencePointsReader(body.SequencePoints);
while (spReader.MoveNext())
{
if (spReader.Current.IsHidden || spReader.Current.Document != Handle)
{
continue;
}
int startLine = spReader.Current.StartLine;
if (startLine >= line && startLine < result)
{
result = startLine;
}
}
}
if (result < int.MaxValue)
{
closestLine = result;
return HResult.S_OK;
}
closestLine = 0;
return HResult.E_FAIL;
}
public int GetChecksum(
......
......@@ -33,6 +33,29 @@ internal SymMethod(SymReader symReader, MethodBodyHandle handle)
BodyHandle = handle;
}
private SequencePointBlobReader GetSequencePointsReader()
{
var mdReader = SymReader.MetadataReader;
var body = mdReader.GetMethodBody(BodyHandle);
return mdReader.GetSequencePointsReader(body.SequencePoints);
}
private RootScopeData GetRootScopeData()
{
if (_lazyRootScopeData == null)
{
_lazyRootScopeData = new RootScopeData(this);
}
return _lazyRootScopeData;
}
private int GetILSize()
{
// SymWriter sets the size of the method to the end offset of the root scope in CloseMethod:
return GetRootScopeData().EndOffset;
}
#region ISymUnmanagedMethod
public int GetNamespace([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedNamespace @namespace)
......@@ -44,8 +67,43 @@ public int GetNamespace([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedNam
public int GetOffset(ISymUnmanagedDocument document, int line, int column, out int offset)
{
// TODO:
throw new NotImplementedException();
if (line <= 0)
{
offset = 0;
return HResult.E_INVALIDARG;
}
// Note that DiaSymReader completely ignores column parameter.
var symDocument = SymReader.AsSymDocument(document);
if (symDocument == null)
{
offset = 0;
return HResult.E_INVALIDARG;
}
// DiaSymReader uses DiaSession::findLinesByLinenum, which results in bad results for lines shared accross multiple methods
// and for lines outside of the current method.
var spReader = GetSequencePointsReader();
var documentHandle = symDocument.Handle;
while (spReader.MoveNext())
{
if (!spReader.Current.IsHidden &&
spReader.Current.Document == documentHandle &&
line >= spReader.Current.StartLine &&
line <= spReader.Current.EndLine)
{
// Return the first matching IL offset. In common cases there will be a single one
// since sequence points of a single method don't overlap unless forced by #line.
offset = spReader.Current.Offset;
return HResult.S_OK;
}
}
offset = 0;
return HResult.E_FAIL;
}
public int GetParameters(
......@@ -66,19 +124,65 @@ public int GetOffset(ISymUnmanagedDocument document, int line, int column, out i
out int count,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3), Out]int[] ranges)
{
// TODO:
throw new NotImplementedException();
}
if (line <= 0)
{
count = 0;
return HResult.E_INVALIDARG;
}
public int GetRootScope([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedScope scope)
{
if (_lazyRootScopeData == null)
// Note that DiaSymReader completely ignores column parameter.
var symDocument = SymReader.AsSymDocument(document);
if (symDocument == null)
{
_lazyRootScopeData = new RootScopeData(this);
count = 0;
return HResult.E_INVALIDARG;
}
// DiaSymReader uses DiaSession::findLinesByLinenum, which results in bad results for lines shared accross multiple methods.
var spReader = GetSequencePointsReader();
var documentHandle = symDocument.Handle;
bool setEndOffset = false;
int i = 0;
while (spReader.MoveNext())
{
if (setEndOffset)
{
ranges[i - 1] = spReader.Current.Offset;
setEndOffset = false;
}
if (!spReader.Current.IsHidden &&
spReader.Current.Document == documentHandle &&
line >= spReader.Current.StartLine &&
line <= spReader.Current.EndLine)
{
if (i + 1 < bufferLength)
{
ranges[i] = spReader.Current.Offset;
setEndOffset = true;
}
// pair of offsets for each sequence point
i += 2;
}
}
if (setEndOffset)
{
ranges[i - 1] = GetILSize();
}
count = i;
return HResult.S_OK;
}
public int GetRootScope([MarshalAs(UnmanagedType.Interface)]out ISymUnmanagedScope scope)
{
// SymReader always creates a new scope instance
scope = new SymScope(_lazyRootScopeData);
scope = new SymScope(GetRootScopeData());
return HResult.S_OK;
}
......@@ -91,7 +195,16 @@ public int GetScopeFromOffset(int offset, [MarshalAs(UnmanagedType.Interface)]ou
public int GetSequencePointCount(out int count)
{
return GetSequencePoints(0, out count, null, null, null, null, null, null);
var spReader = GetSequencePointsReader();
int i = 0;
while (spReader.MoveNext())
{
i++;
}
count = i;
return HResult.S_OK;
}
public int GetSequencePoints(
......@@ -104,14 +217,8 @@ public int GetSequencePointCount(out int count)
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] endLines,
[In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out]int[] endColumns)
{
// TODO: cache
var mdReader = SymReader.MetadataReader;
var body = mdReader.GetMethodBody(BodyHandle);
var spReader = mdReader.GetSequencePointsReader(body.SequencePoints);
SymDocument currentDocument = null;
var spReader = GetSequencePointsReader();
int i = 0;
while (spReader.MoveNext())
......@@ -357,7 +464,7 @@ public int GetAsyncStepInfoCount(out int count)
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] char[] name)
{
// TODO: parse sequence points -> document
throw new InvalidOperationException();
throw new NotImplementedException();
}
/// <summary>
......@@ -376,7 +483,7 @@ public int GetAsyncStepInfoCount(out int count)
out int sequencePointOffset)
{
// TODO: parse sequence points
throw new InvalidOperationException();
throw new NotImplementedException();
}
/// <summary>
......@@ -385,7 +492,7 @@ public int GetAsyncStepInfoCount(out int count)
public int GetDocumentsForMethodCount(out int count)
{
// TODO: parse sequence points
throw new InvalidOperationException();
throw new NotImplementedException();
}
/// <summary>
......@@ -397,7 +504,7 @@ public int GetDocumentsForMethodCount(out int count)
[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]ISymUnmanagedDocument[] documents)
{
// TODO: parse sequence points
throw new InvalidOperationException();
throw new NotImplementedException();
}
/// <summary>
......
......@@ -66,7 +66,7 @@ private bool IsVisualBasicAssembly()
return false;
}
private MethodMap GetMethodMap()
internal MethodMap GetMethodMap()
{
if (_pdbReader.IsDisposed)
{
......@@ -76,7 +76,7 @@ private MethodMap GetMethodMap()
return _lazyMethodMap.Value;
}
private SymDocument AsSymDocument(ISymUnmanagedDocument document)
internal SymDocument AsSymDocument(ISymUnmanagedDocument document)
{
var symDocument = document as SymDocument;
return (symDocument?.SymReader == this) ? symDocument : null;
......@@ -95,7 +95,7 @@ private SymDocument AsSymDocument(ISymUnmanagedDocument document)
if (_pdbReader.IsDisposed)
{
throw new ObjectDisposedException("SymReader");
throw new ObjectDisposedException(nameof(SymReader));
}
if (_lazyDocumentMap.Value.TryGetDocument(url, out documentHandle))
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册