File: PDB\MethodDebugInfo.Native.cs
Web Access
Project: src\src\ExpressionEvaluator\Core\Source\ExpressionCompiler\Microsoft.CodeAnalysis.ExpressionCompiler.csproj (Microsoft.CodeAnalysis.ExpressionEvaluator.ExpressionCompiler)
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.ExpressionEvaluator
{
    internal partial class MethodDebugInfo<TTypeSymbol, TLocalSymbol>
    {
        private readonly struct LocalNameAndScope : IEquatable<LocalNameAndScope>
        {
            internal readonly string LocalName;
            internal readonly int ScopeStart;
            internal readonly int ScopeEnd;
 
            internal LocalNameAndScope(string localName, int scopeStart, int scopeEnd)
            {
                LocalName = localName;
                ScopeStart = scopeStart;
                ScopeEnd = scopeEnd;
            }
 
            public bool Equals(LocalNameAndScope other)
            {
                return ScopeStart == other.ScopeStart &&
                    ScopeEnd == other.ScopeEnd &&
                    string.Equals(LocalName, other.LocalName, StringComparison.Ordinal);
            }
 
            public override bool Equals(object obj)
            {
                throw new NotImplementedException();
            }
 
            public override int GetHashCode()
            {
                return Hash.Combine(
                    Hash.Combine(ScopeStart, ScopeEnd),
                    LocalName.GetHashCode());
            }
        }
 
        internal const int S_OK = 0x0;
        internal const int E_FAIL = unchecked((int)0x80004005);
        internal const int E_NOTIMPL = unchecked((int)0x80004001);
        private static readonly IntPtr s_ignoreIErrorInfo = new IntPtr(-1);
 
        public static unsafe MethodDebugInfo<TTypeSymbol, TLocalSymbol> ReadMethodDebugInfo(
            ISymUnmanagedReader3? symReader,
            EESymbolProvider<TTypeSymbol, TLocalSymbol>? symbolProvider, // TODO: only null in DTEE case where we looking for default namespace
            int methodToken,
            int methodVersion,
            int ilOffset,
            bool isVisualBasicMethod)
        {
            // no symbols
            if (symReader == null)
            {
                return None;
            }
 
            if (symReader is ISymUnmanagedReader5 symReader5)
            {
                int hr = symReader5.GetPortableDebugMetadataByVersion(methodVersion, out byte* metadata, out int size);
                ThrowExceptionForHR(hr);
 
                if (hr == S_OK)
                {
                    var mdReader = new MetadataReader(metadata, size);
                    try
                    {
                        return ReadFromPortable(mdReader, methodToken, ilOffset, symbolProvider, isVisualBasicMethod);
                    }
                    catch (BadImageFormatException)
                    {
                        // bad CDI, ignore
                        return None;
                    }
                }
            }
 
            var allScopes = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
            var containingScopes = ArrayBuilder<ISymUnmanagedScope>.GetInstance();
 
            try
            {
                var symMethod = symReader.GetMethodByVersion(methodToken, methodVersion);
                symMethod?.GetAllScopes(allScopes, containingScopes, ilOffset, isScopeEndInclusive: isVisualBasicMethod);
 
                ImmutableArray<ImmutableArray<ImportRecord>> importRecordGroups;
                ImmutableArray<ExternAliasRecord> externAliasRecords;
                string defaultNamespaceName;
 
                if (isVisualBasicMethod)
                {
                    ReadVisualBasicImportsDebugInfo(
                        symReader,
                        methodToken,
                        methodVersion,
                        out importRecordGroups,
                        out defaultNamespaceName);
 
                    externAliasRecords = ImmutableArray<ExternAliasRecord>.Empty;
                }
                else
                {
                    RoslynDebug.AssertNotNull(symbolProvider);
 
                    ReadCSharpNativeImportsInfo(
                        symReader,
                        symbolProvider,
                        methodToken,
                        methodVersion,
                        out importRecordGroups,
                        out externAliasRecords);
 
                    defaultNamespaceName = "";
                }
 
                // VB should read hoisted scope information from local variables:
                var hoistedLocalScopeRecords = isVisualBasicMethod ? default : ImmutableArray<HoistedLocalScopeRecord>.Empty;
 
                ImmutableDictionary<int, ImmutableArray<bool>>? dynamicLocalMap = null;
                ImmutableDictionary<string, ImmutableArray<bool>>? dynamicLocalConstantMap = null;
                ImmutableDictionary<int, ImmutableArray<string?>>? tupleLocalMap = null;
                ImmutableDictionary<LocalNameAndScope, ImmutableArray<string?>>? tupleLocalConstantMap = null;
 
                byte[]? customDebugInfo = GetCustomDebugInfoBytes(symReader, methodToken, methodVersion);
                if (customDebugInfo != null)
                {
                    if (!isVisualBasicMethod)
                    {
                        var customDebugInfoRecord = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.StateMachineHoistedLocalScopes);
                        if (!customDebugInfoRecord.IsDefault)
                        {
                            hoistedLocalScopeRecords = CustomDebugInfoReader.DecodeStateMachineHoistedLocalScopesRecord(customDebugInfoRecord)
                                .SelectAsArray(s => new HoistedLocalScopeRecord(s.StartOffset, s.Length));
                        }
 
                        GetCSharpDynamicLocalInfo(
                            customDebugInfo,
                            allScopes,
                            out dynamicLocalMap,
                            out dynamicLocalConstantMap);
                    }
 
                    GetTupleElementNamesLocalInfo(
                        customDebugInfo,
                        out tupleLocalMap,
                        out tupleLocalConstantMap);
                }
 
                var constantsBuilder = ArrayBuilder<TLocalSymbol>.GetInstance();
                if (symbolProvider != null) // TODO
                {
                    GetConstants(constantsBuilder, symbolProvider, containingScopes, dynamicLocalConstantMap, tupleLocalConstantMap);
                }
 
                var reuseSpan = GetReuseSpan(allScopes, ilOffset, isVisualBasicMethod);
 
                // containingDocumentName is not set since ISymUnmanagedMethod.GetDocumentsForMethod()
                // may fail (see https://github.com/dotnet/roslyn/issues/66260). The result is that
                // symbols from file-local types will not bind successfully in the EE.
                return new MethodDebugInfo<TTypeSymbol, TLocalSymbol>(
                    hoistedLocalScopeRecords,
                    importRecordGroups,
                    externAliasRecords,
                    dynamicLocalMap,
                    tupleLocalMap,
                    defaultNamespaceName,
                    containingScopes.GetLocalNames(),
                    constantsBuilder.ToImmutableAndFree(),
                    reuseSpan,
                    containingDocumentName: null,
                    isPrimaryConstructor: false);
            }
            catch (InvalidOperationException)
            {
                // bad CDI, ignore
                return None;
            }
            finally
            {
                allScopes.Free();
                containingScopes.Free();
            }
        }
 
        private static void ThrowExceptionForHR(int hr)
        {
            // E_FAIL indicates "no info".
            // E_NOTIMPL indicates a lack of ISymUnmanagedReader support (in a particular implementation).
            if (hr < 0 && hr != E_FAIL && hr != E_NOTIMPL)
            {
                Marshal.ThrowExceptionForHR(hr, s_ignoreIErrorInfo);
            }
        }
 
        /// <summary>
        /// Get the blob of binary custom debug info for a given method.
        /// </summary>
        private static byte[]? GetCustomDebugInfoBytes(ISymUnmanagedReader3 reader, int methodToken, int methodVersion)
        {
            try
            {
                return reader.GetCustomDebugInfo(methodToken, methodVersion);
            }
            catch (ArgumentOutOfRangeException)
            {
                // Sometimes the debugger returns the HRESULT for ArgumentOutOfRangeException, rather than E_FAIL,
                // for methods without custom debug info (https://github.com/dotnet/roslyn/issues/4138).
                return null;
            }
        }
 
        /// <summary>
        /// Get the (unprocessed) import strings for a given method.
        /// </summary>
        /// <remarks>
        /// Doesn't consider forwarding.
        /// 
        /// CONSIDER: Dev12 doesn't just check the root scope - it digs around to find the best
        /// match based on the IL offset and then walks up to the root scope (see PdbUtil::GetScopeFromOffset).
        /// However, it's not clear that this matters, since imports can't be scoped in VB.  This is probably
        /// just based on the way they were extracting locals and constants based on a specific scope.
        /// 
        /// Returns empty array if there are no import strings for the specified method.
        /// </remarks>
        private static ImmutableArray<string> GetImportStrings(ISymUnmanagedReader reader, int methodToken, int methodVersion)
        {
            var method = reader.GetMethodByVersion(methodToken, methodVersion);
            if (method == null)
            {
                // In rare circumstances (only bad PDBs?) GetMethodByVersion can return null.
                // If there's no debug info for the method, then no import strings are available.
                return ImmutableArray<string>.Empty;
            }
 
            var rootScope = method.GetRootScope();
            if (rootScope == null)
            {
                Debug.Assert(false, "Expected a root scope.");
                return ImmutableArray<string>.Empty;
            }
 
            var childScopes = rootScope.GetChildren();
            if (childScopes.Length == 0)
            {
                // It seems like there should always be at least one child scope, but we've
                // seen PDBs where that is not the case.
                return ImmutableArray<string>.Empty;
            }
 
            // As in NamespaceListWrapper::Init, we only consider namespaces in the first
            // child of the root scope.
            var firstChildScope = childScopes[0];
 
            var namespaces = firstChildScope.GetNamespaces();
            if (namespaces.Length == 0)
            {
                // It seems like there should always be at least one namespace (i.e. the global
                // namespace), but we've seen PDBs where that is not the case.
                return ImmutableArray<string>.Empty;
            }
 
            return ImmutableArray.CreateRange(namespaces.Select(n => n.GetName()));
        }
 
        private static void ReadCSharpNativeImportsInfo(
            ISymUnmanagedReader3 reader,
            EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
            int methodToken,
            int methodVersion,
            out ImmutableArray<ImmutableArray<ImportRecord>> importRecordGroups,
            out ImmutableArray<ExternAliasRecord> externAliasRecords)
        {
            ImmutableArray<string> externAliasStrings;
 
            var importStringGroups = CustomDebugInfoReader.GetCSharpGroupedImportStrings(
                methodToken,
                KeyValuePairUtil.Create(reader, methodVersion),
                getMethodCustomDebugInfo: (token, arg) => GetCustomDebugInfoBytes(arg.Key, token, arg.Value),
                getMethodImportStrings: (token, arg) => GetImportStrings(arg.Key, token, arg.Value),
                externAliasStrings: out externAliasStrings);
 
            Debug.Assert(importStringGroups.IsDefault == externAliasStrings.IsDefault);
 
            ArrayBuilder<ImmutableArray<ImportRecord>>? importRecordGroupBuilder = null;
            ArrayBuilder<ExternAliasRecord>? externAliasRecordBuilder = null;
            if (!importStringGroups.IsDefault)
            {
                importRecordGroupBuilder = ArrayBuilder<ImmutableArray<ImportRecord>>.GetInstance(importStringGroups.Length);
                foreach (var importStringGroup in importStringGroups)
                {
                    var groupBuilder = ArrayBuilder<ImportRecord>.GetInstance(importStringGroup.Length);
                    foreach (var importString in importStringGroup)
                    {
                        if (TryCreateImportRecordFromCSharpImportString(symbolProvider, importString, out var record))
                        {
                            groupBuilder.Add(record);
                        }
                        else
                        {
                            Debug.WriteLine($"Failed to parse import string {importString}");
                        }
                    }
                    importRecordGroupBuilder.Add(groupBuilder.ToImmutableAndFree());
                }
 
                if (!externAliasStrings.IsDefault)
                {
                    externAliasRecordBuilder = ArrayBuilder<ExternAliasRecord>.GetInstance(externAliasStrings.Length);
                    foreach (var externAliasString in externAliasStrings)
                    {
                        if (!CustomDebugInfoReader.TryParseCSharpImportString(externAliasString, out var alias, out var externAlias, out var target, out var kind))
                        {
                            Debug.WriteLine($"Unable to parse extern alias '{externAliasString}'");
                            continue;
                        }
 
                        Debug.Assert(kind == ImportTargetKind.Assembly, "Programmer error: How did a non-assembly get in the extern alias list?");
                        RoslynDebug.Assert(alias != null); // Name of the extern alias.
                        RoslynDebug.Assert(externAlias == null); // Not used.
                        RoslynDebug.Assert(target != null); // Name of the target assembly.
 
                        if (!AssemblyIdentity.TryParseDisplayName(target, out var targetIdentity))
                        {
                            Debug.WriteLine($"Unable to parse target of extern alias '{externAliasString}'");
                            continue;
                        }
 
                        externAliasRecordBuilder.Add(new ExternAliasRecord(alias, targetIdentity));
                    }
                }
            }
 
            importRecordGroups = importRecordGroupBuilder?.ToImmutableAndFree() ?? ImmutableArray<ImmutableArray<ImportRecord>>.Empty;
            externAliasRecords = externAliasRecordBuilder?.ToImmutableAndFree() ?? ImmutableArray<ExternAliasRecord>.Empty;
        }
 
        private static bool TryCreateImportRecordFromCSharpImportString(EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider, string importString, out ImportRecord record)
        {
            string? targetString;
            if (CustomDebugInfoReader.TryParseCSharpImportString(importString, out var alias, out var externAlias, out targetString, out var targetKind))
            {
                ITypeSymbolInternal? type = null;
                if (targetKind == ImportTargetKind.Type)
                {
                    type = symbolProvider.GetTypeSymbolForSerializedType(targetString);
                    targetString = null;
                }
 
                record = new ImportRecord(
                    targetKind: targetKind,
                    alias: alias,
                    targetType: type,
                    targetString: targetString,
                    targetAssembly: null,
                    targetAssemblyAlias: externAlias);
 
                return true;
            }
 
            record = default;
            return false;
        }
 
        /// <exception cref="InvalidOperationException">Bad data.</exception>
        private static void GetCSharpDynamicLocalInfo(
            byte[] customDebugInfo,
            IEnumerable<ISymUnmanagedScope> scopes,
            out ImmutableDictionary<int, ImmutableArray<bool>>? dynamicLocalMap,
            out ImmutableDictionary<string, ImmutableArray<bool>>? dynamicLocalConstantMap)
        {
            dynamicLocalMap = null;
            dynamicLocalConstantMap = null;
 
            var record = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.DynamicLocals);
            if (record.IsDefault)
            {
                return;
            }
 
            var localKindsByName = PooledDictionary<string, LocalKind>.GetInstance();
            GetLocalKindByName(localKindsByName, scopes);
 
            ImmutableDictionary<int, ImmutableArray<bool>>.Builder? localBuilder = null;
            ImmutableDictionary<string, ImmutableArray<bool>>.Builder? constantBuilder = null;
 
            var dynamicLocals = CustomDebugInfoReader.DecodeDynamicLocalsRecord(record);
            foreach (var dynamicLocal in dynamicLocals)
            {
                int slot = dynamicLocal.SlotId;
                var flags = dynamicLocal.Flags;
                if (slot == 0)
                {
                    LocalKind kind;
                    var name = dynamicLocal.LocalName;
                    localKindsByName.TryGetValue(name, out kind);
                    switch (kind)
                    {
                        case LocalKind.DuplicateName:
                            // Drop locals with ambiguous names.
                            continue;
                        case LocalKind.ConstantName:
                            constantBuilder ??= ImmutableDictionary.CreateBuilder<string, ImmutableArray<bool>>();
                            constantBuilder[name] = flags;
                            continue;
                    }
                }
                localBuilder ??= ImmutableDictionary.CreateBuilder<int, ImmutableArray<bool>>();
                localBuilder[slot] = flags;
            }
 
            if (localBuilder != null)
            {
                dynamicLocalMap = localBuilder.ToImmutable();
            }
 
            if (constantBuilder != null)
            {
                dynamicLocalConstantMap = constantBuilder.ToImmutable();
            }
 
            localKindsByName.Free();
        }
 
        private enum LocalKind { DuplicateName, VariableName, ConstantName }
 
        /// <summary>
        /// Dynamic CDI encodes slot id and name for each dynamic local variable, but only name for a constant. 
        /// Constants have slot id set to 0. As a result there is a potential for ambiguity. If a variable in a slot 0
        /// and a constant defined anywhere in the method body have the same name we can't say which one 
        /// the dynamic flags belong to (if there is a dynamic record for at least one of them).
        /// 
        /// This method returns the local kind (variable, constant, or duplicate) based on name.
        /// </summary>
        private static void GetLocalKindByName(Dictionary<string, LocalKind> localNames, IEnumerable<ISymUnmanagedScope> scopes)
        {
            Debug.Assert(localNames.Count == 0);
 
            var localSlot0 = scopes.SelectMany(scope => scope.GetLocals()).FirstOrDefault(variable => variable.GetSlot() == 0);
            if (localSlot0 != null)
            {
                localNames.Add(localSlot0.GetName(), LocalKind.VariableName);
            }
 
            foreach (var scope in scopes)
            {
                foreach (var constant in scope.GetConstants())
                {
                    string name = constant.GetName();
                    localNames[name] = localNames.ContainsKey(name) ? LocalKind.DuplicateName : LocalKind.ConstantName;
                }
            }
        }
 
        private static void GetTupleElementNamesLocalInfo(
            byte[] customDebugInfo,
            out ImmutableDictionary<int, ImmutableArray<string?>>? tupleLocalMap,
            out ImmutableDictionary<LocalNameAndScope, ImmutableArray<string?>>? tupleLocalConstantMap)
        {
            tupleLocalMap = null;
            tupleLocalConstantMap = null;
 
            var record = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.TupleElementNames);
            if (record.IsDefault)
            {
                return;
            }
 
            ImmutableDictionary<int, ImmutableArray<string?>>.Builder? localBuilder = null;
            ImmutableDictionary<LocalNameAndScope, ImmutableArray<string?>>.Builder? constantBuilder = null;
 
            var tuples = CustomDebugInfoReader.DecodeTupleElementNamesRecord(record);
            foreach (var tuple in tuples)
            {
                var slotIndex = tuple.SlotIndex;
                var elementNames = tuple.ElementNames;
                if (slotIndex < 0)
                {
                    constantBuilder ??= ImmutableDictionary.CreateBuilder<LocalNameAndScope, ImmutableArray<string?>>();
                    var localAndScope = new LocalNameAndScope(tuple.LocalName, tuple.ScopeStart, tuple.ScopeEnd);
                    constantBuilder[localAndScope] = elementNames;
                }
                else
                {
                    localBuilder ??= ImmutableDictionary.CreateBuilder<int, ImmutableArray<string?>>();
                    localBuilder[slotIndex] = elementNames;
                }
            }
 
            if (localBuilder != null)
            {
                tupleLocalMap = localBuilder.ToImmutable();
            }
 
            if (constantBuilder != null)
            {
                tupleLocalConstantMap = constantBuilder.ToImmutable();
            }
        }
 
        private static void ReadVisualBasicImportsDebugInfo(
            ISymUnmanagedReader reader,
            int methodToken,
            int methodVersion,
            out ImmutableArray<ImmutableArray<ImportRecord>> importRecordGroups,
            out string defaultNamespaceName)
        {
            importRecordGroups = ImmutableArray<ImmutableArray<ImportRecord>>.Empty;
 
            var importStrings = CustomDebugInfoReader.GetVisualBasicImportStrings(
                methodToken,
                KeyValuePairUtil.Create(reader, methodVersion),
                (token, arg) => GetImportStrings(arg.Key, token, arg.Value));
 
            if (importStrings.IsDefault)
            {
                defaultNamespaceName = "";
                return;
            }
 
            string? lazyDefaultNamespaceName = null;
            var projectLevelImportRecords = ArrayBuilder<ImportRecord>.GetInstance();
            var fileLevelImportRecords = ArrayBuilder<ImportRecord>.GetInstance();
 
            foreach (var importString in importStrings)
            {
                RoslynDebug.AssertNotNull(importString);
 
                if (importString is ['*', ..])
                {
                    string? alias = null;
                    string? target = null;
 
                    if (!CustomDebugInfoReader.TryParseVisualBasicImportString(importString, out alias, out target, out var kind, out var scope))
                    {
                        Debug.WriteLine($"Unable to parse import string '{importString}'");
                        continue;
                    }
                    else if (kind == ImportTargetKind.Defunct)
                    {
                        continue;
                    }
 
                    Debug.Assert(alias == null); // The default namespace is never aliased.
                    Debug.Assert(target != null);
                    Debug.Assert(kind == ImportTargetKind.DefaultNamespace);
 
                    // We only expect to see one of these, but it looks like ProcedureContext::LoadImportsAndDefaultNamespaceNormal
                    // implicitly uses the last one if there are multiple.
                    Debug.Assert(lazyDefaultNamespaceName == null);
 
                    lazyDefaultNamespaceName = target;
                }
                else
                {
                    ImportRecord importRecord;
                    VBImportScopeKind scope = 0;
 
                    if (TryCreateImportRecordFromVisualBasicImportString(importString, out importRecord, out scope))
                    {
                        if (scope == VBImportScopeKind.Project)
                        {
                            projectLevelImportRecords.Add(importRecord);
                        }
                        else
                        {
                            Debug.Assert(scope == VBImportScopeKind.File || scope == VBImportScopeKind.Unspecified);
                            fileLevelImportRecords.Add(importRecord);
                        }
                    }
                    else
                    {
                        Debug.WriteLine($"Failed to parse import string {importString}");
                    }
                }
            }
 
            importRecordGroups = ImmutableArray.Create(
                fileLevelImportRecords.ToImmutableAndFree(),
                projectLevelImportRecords.ToImmutableAndFree());
 
            defaultNamespaceName = lazyDefaultNamespaceName ?? "";
        }
 
        private static bool TryCreateImportRecordFromVisualBasicImportString(string importString, out ImportRecord record, out VBImportScopeKind scope)
        {
            ImportTargetKind targetKind;
            string alias;
            string targetString;
            if (CustomDebugInfoReader.TryParseVisualBasicImportString(importString, out alias, out targetString, out targetKind, out scope))
            {
                record = new ImportRecord(
                    targetKind: targetKind,
                    alias: alias,
                    targetType: null,
                    targetString: targetString,
                    targetAssembly: null,
                    targetAssemblyAlias: null);
 
                return true;
            }
 
            record = default;
            return false;
        }
 
        private static ILSpan GetReuseSpan(ArrayBuilder<ISymUnmanagedScope> scopes, int ilOffset, bool isEndInclusive)
        {
            return MethodContextReuseConstraints.CalculateReuseSpan(
                ilOffset,
                ILSpan.MaxValue,
                scopes.Select(scope => new ILSpan((uint)scope.GetStartOffset(), (uint)(scope.GetEndOffset() + (isEndInclusive ? 1 : 0)))));
        }
 
        private static void GetConstants(
            ArrayBuilder<TLocalSymbol> builder,
            EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
            ArrayBuilder<ISymUnmanagedScope> scopes,
            ImmutableDictionary<string, ImmutableArray<bool>>? dynamicLocalConstantMap,
            ImmutableDictionary<LocalNameAndScope, ImmutableArray<string?>>? tupleLocalConstantMap)
        {
            foreach (var scope in scopes)
            {
                foreach (var constant in scope.GetConstants())
                {
                    string name = constant.GetName();
                    object rawValue = constant.GetValue();
                    var signature = constant.GetSignature().ToImmutableArray();
 
                    TTypeSymbol type;
                    try
                    {
                        type = symbolProvider.DecodeLocalVariableType(signature);
                    }
                    catch (Exception e) when (e is UnsupportedSignatureContent || e is BadImageFormatException)
                    {
                        // ignore 
                        continue;
                    }
 
                    if (type.Kind == SymbolKind.ErrorType)
                    {
                        continue;
                    }
 
                    ConstantValue constantValue = PdbHelpers.GetSymConstantValue(type, rawValue);
 
                    // TODO (https://github.com/dotnet/roslyn/issues/1815): report error properly when the symbol is used
                    if (constantValue.IsBad)
                    {
                        continue;
                    }
 
                    var dynamicFlags = default(ImmutableArray<bool>);
                    dynamicLocalConstantMap?.TryGetValue(name, out dynamicFlags);
 
                    var tupleElementNames = default(ImmutableArray<string?>);
                    if (tupleLocalConstantMap != null)
                    {
                        int scopeStart = scope.GetStartOffset();
                        int scopeEnd = scope.GetEndOffset();
                        tupleLocalConstantMap.TryGetValue(new LocalNameAndScope(name, scopeStart, scopeEnd), out tupleElementNames);
                    }
 
                    builder.Add(symbolProvider.GetLocalConstant(name, type, constantValue, dynamicFlags, tupleElementNames));
                }
            }
        }
 
        /// <summary>
        /// Returns symbols for the locals emitted in the original method,
        /// based on the local signatures from the IL and the names and
        /// slots from the PDB. The actual locals are needed to ensure the
        /// local slots in the generated method match the original.
        /// </summary>
        public static void GetLocals(
            ArrayBuilder<TLocalSymbol> builder,
            EESymbolProvider<TTypeSymbol, TLocalSymbol> symbolProvider,
            ImmutableArray<string> names,
            ImmutableArray<LocalInfo<TTypeSymbol>> localInfo,
            ImmutableDictionary<int, ImmutableArray<bool>>? dynamicLocalMap,
            ImmutableDictionary<int, ImmutableArray<string?>>? tupleLocalConstantMap)
        {
            if (localInfo.Length == 0)
            {
                // When debugging a .dmp without a heap, localInfo will be empty although
                // names may be non-empty if there is a PDB. Since there's no type info, the
                // locals are dropped. Note this means the local signature of any generated
                // method will not match the original signature, so new locals will overlap
                // original locals. That is ok since there is no live process for the debugger
                // to update (any modified values exist in the debugger only).
                return;
            }
 
            Debug.Assert(localInfo.Length >= names.Length);
 
            for (int i = 0; i < localInfo.Length; i++)
            {
                string? name = (i < names.Length) ? names[i] : null;
 
                var dynamicFlags = default(ImmutableArray<bool>);
                dynamicLocalMap?.TryGetValue(i, out dynamicFlags);
 
                var tupleElementNames = default(ImmutableArray<string?>);
                tupleLocalConstantMap?.TryGetValue(i, out tupleElementNames);
 
                builder.Add(symbolProvider.GetLocalVariable(name, i, localInfo[i], dynamicFlags, tupleElementNames));
            }
        }
    }
}