|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.PooledObjects;
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
internal partial class MethodDebugInfo<TTypeSymbol, TLocalSymbol>
{
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
public static MethodDebugInfo<TTypeSymbol, TLocalSymbol> ReadFromPortable(
MetadataReader reader,
int methodToken,
int ilOffset,
EESymbolProvider<TTypeSymbol, TLocalSymbol>? symbolProvider,
bool isVisualBasicMethod)
{
ImmutableDictionary<int, ImmutableArray<bool>>? dynamicLocalMap;
ImmutableDictionary<int, ImmutableArray<string?>>? tupleLocalMap;
ImmutableArray<ImmutableArray<ImportRecord>> importGroups;
ImmutableArray<ExternAliasRecord> externAliases;
ImmutableArray<string> localVariableNames;
ImmutableArray<TLocalSymbol> localConstants;
ILSpan reuseSpan;
var methodHandle = GetDeltaRelativeMethodDefinitionHandle(reader, methodToken);
// TODO: only null in DTEE case where we looking for default namespace
if (symbolProvider != null)
{
ReadLocalScopeInformation(
reader,
methodHandle,
ilOffset,
symbolProvider,
isVisualBasicMethod,
out importGroups,
out externAliases,
out localVariableNames,
out dynamicLocalMap,
out tupleLocalMap,
out localConstants,
out reuseSpan);
}
else
{
dynamicLocalMap = null;
tupleLocalMap = null;
importGroups = ImmutableArray<ImmutableArray<ImportRecord>>.Empty;
externAliases = ImmutableArray<ExternAliasRecord>.Empty;
localVariableNames = ImmutableArray<string>.Empty;
localConstants = ImmutableArray<TLocalSymbol>.Empty;
reuseSpan = ILSpan.MaxValue;
}
ReadMethodCustomDebugInformation(reader, methodHandle, out var hoistedLocalScopes, out var defaultNamespace, out bool isPrimaryConstructor);
var documentHandle = reader.GetMethodDebugInformation(methodHandle).Document;
string? documentName = null;
if (!documentHandle.IsNil)
{
var document = reader.GetDocument(documentHandle);
documentName = reader.GetString(document.Name);
}
return new MethodDebugInfo<TTypeSymbol, TLocalSymbol>(
hoistedLocalScopes,
importGroups,
externAliases,
dynamicLocalMap,
tupleLocalMap,
defaultNamespace,
localVariableNames,
localConstants,
reuseSpan,
documentName,
isPrimaryConstructor: isPrimaryConstructor);
}
/// <summary>
/// Maps global method token to a handle local to the current delta PDB.
/// Debug tables referring to methods currently use local handles, not global handles.
/// See https://github.com/dotnet/roslyn/issues/16286
/// </summary>
private static MethodDefinitionHandle GetDeltaRelativeMethodDefinitionHandle(MetadataReader reader, int methodToken)
{
var globalHandle = (MethodDefinitionHandle)MetadataTokens.EntityHandle(methodToken);
if (reader.GetTableRowCount(TableIndex.EncMap) == 0)
{
return globalHandle;
}
var globalDebugHandle = globalHandle.ToDebugInformationHandle();
int rowId = 1;
foreach (var handle in reader.GetEditAndContinueMapEntries())
{
if (handle.Kind == HandleKind.MethodDebugInformation)
{
if (handle == globalDebugHandle)
{
return MetadataTokens.MethodDefinitionHandle(rowId);
}
rowId++;
}
}
// compiler generated invalid EncMap table:
throw new BadImageFormatException();
}
private static void ReadLocalScopeInformation(
MetadataReader reader,
MethodDefinitionHandle methodHandle,
int ilOffset,
EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
bool isVisualBasicMethod,
out ImmutableArray<ImmutableArray<ImportRecord>> importGroups,
out ImmutableArray<ExternAliasRecord> externAliases,
out ImmutableArray<string> localVariableNames,
out ImmutableDictionary<int, ImmutableArray<bool>>? dynamicLocalMap,
out ImmutableDictionary<int, ImmutableArray<string?>>? tupleLocalMap,
out ImmutableArray<TLocalSymbol> localConstants,
out ILSpan reuseSpan)
{
var localVariableNamesBuilder = ArrayBuilder<string>.GetInstance();
var localConstantsBuilder = ArrayBuilder<TLocalSymbol>.GetInstance();
ImmutableDictionary<int, ImmutableArray<bool>>.Builder? lazyDynamicLocalsBuilder = null;
ImmutableDictionary<int, ImmutableArray<string?>>.Builder? lazyTupleLocalsBuilder = null;
var innerMostImportScope = default(ImportScopeHandle);
uint reuseSpanStart = 0;
uint reuseSpanEnd = uint.MaxValue;
try
{
foreach (var scopeHandle in reader.GetLocalScopes(methodHandle))
{
try
{
var scope = reader.GetLocalScope(scopeHandle);
if (ilOffset < scope.StartOffset)
{
// scopes are sorted by StartOffset, hence all scopes that follow can't contain ilOffset
reuseSpanEnd = Math.Min(reuseSpanEnd, (uint)scope.StartOffset);
break;
}
if (ilOffset >= scope.EndOffset)
{
// ilOffset is not in this scope, go to next one
reuseSpanStart = Math.Max(reuseSpanStart, (uint)scope.EndOffset);
continue;
}
// reuse span is a subspan of the inner-most scope containing the IL offset:
reuseSpanStart = Math.Max(reuseSpanStart, (uint)scope.StartOffset);
reuseSpanEnd = Math.Min(reuseSpanEnd, (uint)scope.EndOffset);
// imports (use the inner-most):
innerMostImportScope = scope.ImportScope;
// locals (from all contained scopes):
foreach (var variableHandle in scope.GetLocalVariables())
{
var variable = reader.GetLocalVariable(variableHandle);
if ((variable.Attributes & LocalVariableAttributes.DebuggerHidden) != 0)
{
continue;
}
localVariableNamesBuilder.SetItem(variable.Index, reader.GetString(variable.Name));
var dynamicFlags = ReadDynamicCustomDebugInformation(reader, variableHandle);
if (!dynamicFlags.IsDefault)
{
lazyDynamicLocalsBuilder ??= ImmutableDictionary.CreateBuilder<int, ImmutableArray<bool>>();
lazyDynamicLocalsBuilder[variable.Index] = dynamicFlags;
}
var tupleElementNames = ReadTupleCustomDebugInformation(reader, variableHandle);
if (!tupleElementNames.IsDefault)
{
lazyTupleLocalsBuilder ??= ImmutableDictionary.CreateBuilder<int, ImmutableArray<string?>>();
lazyTupleLocalsBuilder[variable.Index] = tupleElementNames;
}
}
// constants (from all contained scopes):
foreach (var constantHandle in scope.GetLocalConstants())
{
var constant = reader.GetLocalConstant(constantHandle);
var sigReader = reader.GetBlobReader(constant.Signature);
symbolProvider.DecodeLocalConstant(ref sigReader, out var typeSymbol, out var value);
var name = reader.GetString(constant.Name);
var dynamicFlags = ReadDynamicCustomDebugInformation(reader, constantHandle);
var tupleElementNames = ReadTupleCustomDebugInformation(reader, constantHandle);
localConstantsBuilder.Add(symbolProvider.GetLocalConstant(name, typeSymbol, value, dynamicFlags, tupleElementNames));
}
}
catch (Exception e) when (e is UnsupportedSignatureContent || e is BadImageFormatException)
{
// ignore scopes with invalid data
}
}
}
finally
{
localVariableNames = localVariableNamesBuilder.ToImmutableAndFree();
localConstants = localConstantsBuilder.ToImmutableAndFree();
dynamicLocalMap = lazyDynamicLocalsBuilder?.ToImmutable();
tupleLocalMap = lazyTupleLocalsBuilder?.ToImmutable();
reuseSpan = new ILSpan(reuseSpanStart, reuseSpanEnd);
}
var importGroupsBuilder = ArrayBuilder<ImmutableArray<ImportRecord>>.GetInstance();
var externAliasesBuilder = ArrayBuilder<ExternAliasRecord>.GetInstance();
if (!innerMostImportScope.IsNil)
{
PopulateImports(reader, innerMostImportScope, symbolProvider, isVisualBasicMethod, importGroupsBuilder, externAliasesBuilder);
}
importGroups = importGroupsBuilder.ToImmutableAndFree();
externAliases = externAliasesBuilder.ToImmutableAndFree();
}
private static string ReadUtf8String(MetadataReader reader, BlobHandle handle)
{
var bytes = reader.GetBlobBytes(handle);
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
/// <summary>
/// Read UTF-8 string with null terminator.
/// </summary>
private static string ReadUtf8String(ref BlobReader reader)
{
var builder = ArrayBuilder<byte>.GetInstance();
while (true)
{
var b = reader.ReadByte();
if (b == 0)
{
break;
}
builder.Add(b);
}
var bytes = builder.ToArrayAndFree();
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
}
private static void PopulateImports(
MetadataReader reader,
ImportScopeHandle handle,
EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
bool isVisualBasicMethod,
ArrayBuilder<ImmutableArray<ImportRecord>> importGroupsBuilder,
ArrayBuilder<ExternAliasRecord> externAliasesBuilder)
{
var importGroupBuilder = ArrayBuilder<ImportRecord>.GetInstance();
while (!handle.IsNil)
{
var importScope = reader.GetImportScope(handle);
try
{
PopulateImports(reader, importScope, symbolProvider, importGroupBuilder, externAliasesBuilder);
}
catch (BadImageFormatException)
{
// ignore invalid imports
}
// Portable PDBs represent project-level scope as the root of the chain of scopes.
// This scope might contain aliases for assembly references, but is not considered
// to be part of imports groups.
if (isVisualBasicMethod || !importScope.Parent.IsNil)
{
importGroupsBuilder.Add(importGroupBuilder.ToImmutable());
importGroupBuilder.Clear();
}
else
{
// C# currently doesn't support global imports in PDBs
// https://github.com/dotnet/roslyn/issues/21862
Debug.Assert(importGroupBuilder.Count == 0);
}
handle = importScope.Parent;
}
importGroupBuilder.Free();
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static void PopulateImports(
MetadataReader reader,
ImportScope importScope,
EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
ArrayBuilder<ImportRecord> importGroupBuilder,
ArrayBuilder<ExternAliasRecord> externAliasesBuilder)
{
foreach (ImportDefinition import in importScope.GetImports())
{
switch (import.Kind)
{
case ImportDefinitionKind.ImportNamespace:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Namespace,
targetString: ReadUtf8String(reader, import.TargetNamespace)));
break;
case ImportDefinitionKind.ImportAssemblyNamespace:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Namespace,
targetString: ReadUtf8String(reader, import.TargetNamespace),
targetAssembly: symbolProvider.GetReferencedAssembly(import.TargetAssembly)));
break;
case ImportDefinitionKind.ImportType:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Type,
targetType: symbolProvider.GetType(import.TargetType)));
break;
case ImportDefinitionKind.ImportXmlNamespace:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.XmlNamespace,
alias: ReadUtf8String(reader, import.Alias),
targetString: ReadUtf8String(reader, import.TargetNamespace)));
break;
case ImportDefinitionKind.ImportAssemblyReferenceAlias:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Assembly,
alias: ReadUtf8String(reader, import.Alias)));
break;
case ImportDefinitionKind.AliasAssemblyReference:
externAliasesBuilder.Add(new ExternAliasRecord(
alias: ReadUtf8String(reader, import.Alias),
targetAssembly: symbolProvider.GetReferencedAssembly(import.TargetAssembly)));
break;
case ImportDefinitionKind.AliasNamespace:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Namespace,
alias: ReadUtf8String(reader, import.Alias),
targetString: ReadUtf8String(reader, import.TargetNamespace)));
break;
case ImportDefinitionKind.AliasAssemblyNamespace:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Namespace,
alias: ReadUtf8String(reader, import.Alias),
targetString: ReadUtf8String(reader, import.TargetNamespace),
targetAssembly: symbolProvider.GetReferencedAssembly(import.TargetAssembly)));
break;
case ImportDefinitionKind.AliasType:
importGroupBuilder.Add(new ImportRecord(
ImportTargetKind.Type,
alias: ReadUtf8String(reader, import.Alias),
targetType: symbolProvider.GetType(import.TargetType)));
break;
}
}
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static void ReadMethodCustomDebugInformation(
MetadataReader reader,
MethodDefinitionHandle methodHandle,
out ImmutableArray<HoistedLocalScopeRecord> hoistedLocalScopes,
out string defaultNamespace,
out bool isPrimaryConstructor)
{
hoistedLocalScopes = TryGetCustomDebugInformation(reader, methodHandle, PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes, out var info)
? DecodeHoistedLocalScopes(reader.GetBlobReader(info.Value))
: ImmutableArray<HoistedLocalScopeRecord>.Empty;
// TODO: consider looking this up once per module (not for every method)
defaultNamespace = TryGetCustomDebugInformation(reader, EntityHandle.ModuleDefinition, PortableCustomDebugInfoKinds.DefaultNamespace, out info)
? DecodeDefaultNamespace(reader.GetBlobReader(info.Value))
: "";
isPrimaryConstructor = TryGetCustomDebugInformation(reader, methodHandle, PortableCustomDebugInfoKinds.PrimaryConstructorInformationBlob, out _);
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static ImmutableArray<bool> ReadDynamicCustomDebugInformation(MetadataReader reader, EntityHandle variableOrConstantHandle)
{
if (TryGetCustomDebugInformation(reader, variableOrConstantHandle, PortableCustomDebugInfoKinds.DynamicLocalVariables, out var info))
{
return DecodeDynamicFlags(reader.GetBlobReader(info.Value));
}
return default;
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static ImmutableArray<string?> ReadTupleCustomDebugInformation(MetadataReader reader, EntityHandle variableOrConstantHandle)
{
if (TryGetCustomDebugInformation(reader, variableOrConstantHandle, PortableCustomDebugInfoKinds.TupleElementNames, out var info))
{
return DecodeTupleElementNames(reader.GetBlobReader(info.Value));
}
return default;
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static bool TryGetCustomDebugInformation(MetadataReader reader, EntityHandle handle, Guid kind, out CustomDebugInformation customDebugInfo)
{
bool foundAny = false;
customDebugInfo = default;
foreach (var infoHandle in reader.GetCustomDebugInformation(handle))
{
var info = reader.GetCustomDebugInformation(infoHandle);
var id = reader.GetGuid(info.Kind);
if (id == kind)
{
if (foundAny)
{
throw new BadImageFormatException();
}
customDebugInfo = info;
foundAny = true;
}
}
return foundAny;
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static ImmutableArray<bool> DecodeDynamicFlags(BlobReader reader)
{
var builder = ImmutableArray.CreateBuilder<bool>(reader.Length * 8);
while (reader.RemainingBytes > 0)
{
int b = reader.ReadByte();
for (int i = 1; i < 0x100; i <<= 1)
{
builder.Add((b & i) != 0);
}
}
return builder.MoveToImmutable();
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static ImmutableArray<string?> DecodeTupleElementNames(BlobReader reader)
{
var builder = ArrayBuilder<string?>.GetInstance();
while (reader.RemainingBytes > 0)
{
var value = ReadUtf8String(ref reader);
builder.Add(value.Length == 0 ? null : value);
}
return builder.ToImmutableAndFree();
}
/// <exception cref="BadImageFormatException">Invalid data format.</exception>
private static ImmutableArray<HoistedLocalScopeRecord> DecodeHoistedLocalScopes(BlobReader reader)
{
var result = ArrayBuilder<HoistedLocalScopeRecord>.GetInstance();
do
{
int startOffset = reader.ReadInt32();
int length = reader.ReadInt32();
result.Add(new HoistedLocalScopeRecord(startOffset, length));
}
while (reader.RemainingBytes > 0);
return result.ToImmutableAndFree();
}
private static string DecodeDefaultNamespace(BlobReader reader)
{
return reader.ReadUTF8(reader.Length);
}
}
}
|