diff --git a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs index 800c6d401b9da648fd880dd59f28251ca37c2132..be94d1fd731da59310e85dfe0daeabf93fb4336d 100644 --- a/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs +++ b/src/ExpressionEvaluator/Core/Source/ExpressionCompiler/PDB/MethodDebugInfo.Native.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; +using Microsoft.CodeAnalysis.Collections; using Microsoft.DiaSymReader; namespace Microsoft.CodeAnalysis.ExpressionEvaluator @@ -264,7 +265,7 @@ private static bool TryCreateImportRecordFromCSharpImportString(EESymbolProvider .SelectAsArray(s => new HoistedLocalScopeRecord(s.StartOffset, s.EndOffset - s.StartOffset + 1)); } - CustomDebugInfoReader.GetCSharpDynamicLocalInfo( + GetCSharpDynamicLocalInfo( customDebugInfoBytes, methodToken, methodVersion, @@ -273,6 +274,128 @@ private static bool TryCreateImportRecordFromCSharpImportString(EESymbolProvider out dynamicLocalConstantMap); } + /// Bad data. + private static void GetCSharpDynamicLocalInfo( + byte[] customDebugInfo, + int methodToken, + int methodVersion, + IEnumerable scopes, + out ImmutableDictionary> dynamicLocalMap, + out ImmutableDictionary> dynamicLocalConstantMap) + { + dynamicLocalMap = ImmutableDictionary>.Empty; + dynamicLocalConstantMap = ImmutableDictionary>.Empty; + + var record = CustomDebugInfoReader.TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.DynamicLocals); + if (record.IsDefault) + { + return; + } + + ImmutableDictionary>.Builder localBuilder = null; + ImmutableDictionary>.Builder constantBuilder = null; + + var dynamicLocals = RemoveAmbiguousLocals(CustomDebugInfoReader.DecodeDynamicLocalsRecord(record), scopes); + foreach (var dynamicLocal in dynamicLocals) + { + int slot = dynamicLocal.SlotId; + var flags = GetFlags(dynamicLocal); + if (slot < 0) + { + constantBuilder = constantBuilder ?? ImmutableDictionary.CreateBuilder>(); + constantBuilder[dynamicLocal.Name] = flags; + } + else + { + localBuilder = localBuilder ?? ImmutableDictionary.CreateBuilder>(); + localBuilder[slot] = flags; + } + } + + if (localBuilder != null) + { + dynamicLocalMap = localBuilder.ToImmutable(); + } + + if (constantBuilder != null) + { + dynamicLocalConstantMap = constantBuilder.ToImmutable(); + } + } + + private static ImmutableArray GetFlags(DynamicLocalInfo bucket) + { + int flagCount = bucket.FlagCount; + ulong flags = bucket.Flags; + var builder = ArrayBuilder.GetInstance(flagCount); + for (int i = 0; i < flagCount; i++) + { + builder.Add((flags & (1u << i)) != 0); + } + return builder.ToImmutableAndFree(); + } + + /// + /// 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 removes ambiguous dynamic records. + /// + private static ImmutableArray RemoveAmbiguousLocals( + ImmutableArray dynamicLocals, + IEnumerable scopes) + { + const byte DuplicateName = 0; + const byte VariableName = 1; + const byte ConstantName = 2; + + var localNames = PooledDictionary.GetInstance(); + + var firstLocal = scopes.SelectMany(scope => scope.GetLocals()).FirstOrDefault(variable => variable.GetSlot() == 0); + if (firstLocal != null) + { + localNames.Add(firstLocal.GetName(), VariableName); + } + + foreach (var scope in scopes) + { + foreach (var constant in scope.GetConstants()) + { + string name = constant.GetName(); + localNames[name] = localNames.ContainsKey(name) ? DuplicateName : ConstantName; + } + } + + var builder = ArrayBuilder.GetInstance(); + foreach (var dynamicLocal in dynamicLocals) + { + int slot = dynamicLocal.SlotId; + var name = dynamicLocal.Name; + if (slot == 0) + { + byte localOrConstant; + localNames.TryGetValue(name, out localOrConstant); + if (localOrConstant == DuplicateName) + { + continue; + } + + if (localOrConstant == ConstantName) + { + slot = -1; + } + } + + builder.Add(new DynamicLocalInfo(dynamicLocal.FlagCount, dynamicLocal.Flags, slot, name)); + } + + var result = builder.ToImmutableAndFree(); + localNames.Free(); + return result; + } + private static void ReadVisualBasicImportsDebugInfo( ISymUnmanagedReader reader, int methodToken, diff --git a/src/Test/PdbUtilities/Pdb/PdbToXml.cs b/src/Test/PdbUtilities/Pdb/PdbToXml.cs index 43c63d760436a97f90d1edc24fc44bc4174325ef..f4c6692e4a254e39178a21d9d8c5bdef5c400bbc 100644 --- a/src/Test/PdbUtilities/Pdb/PdbToXml.cs +++ b/src/Test/PdbUtilities/Pdb/PdbToXml.cs @@ -453,12 +453,12 @@ private void WriteDynamicLocalsCustomDebugInfo(CustomDebugInfoRecord record) _writer.WriteStartElement("dynamicLocals"); - var buckets = CDI.DecodeDynamicLocalsRecord(record.Data); + var dynamicLocals = CDI.DecodeDynamicLocalsRecord(record.Data); - foreach (DynamicLocalBucket bucket in buckets) + foreach (DynamicLocalInfo dynamicLocal in dynamicLocals) { - ulong flags = bucket.Flags; - int flagCount = bucket.FlagCount; + ulong flags = dynamicLocal.Flags; + int flagCount = dynamicLocal.FlagCount; PooledStringBuilder pooled = PooledStringBuilder.GetInstance(); StringBuilder flagsBuilder = pooled.Builder; @@ -470,8 +470,8 @@ private void WriteDynamicLocalsCustomDebugInfo(CustomDebugInfoRecord record) _writer.WriteStartElement("bucket"); _writer.WriteAttributeString("flagCount", CultureInvariantToString(flagCount)); _writer.WriteAttributeString("flags", pooled.ToStringAndFree()); - _writer.WriteAttributeString("slotId", CultureInvariantToString(bucket.SlotId)); - _writer.WriteAttributeString("localName", bucket.Name); + _writer.WriteAttributeString("slotId", CultureInvariantToString(dynamicLocal.SlotId)); + _writer.WriteAttributeString("localName", dynamicLocal.Name); _writer.WriteEndElement(); //bucket } diff --git a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs index 69bbaf9a3e3e8dadf8dc76844c54eae95eacea7d..bcc402d147304e3eb9f89d9fb1ff5ee824a7f451 100644 --- a/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs +++ b/src/Test/PdbUtilities/Shared/CustomDebugInfoReader.cs @@ -226,12 +226,12 @@ public static string DecodeForwardIteratorRecord(ImmutableArray bytes) /// Exposed for . /// /// Bad data. - public static ImmutableArray DecodeDynamicLocalsRecord(ImmutableArray bytes) + public static ImmutableArray DecodeDynamicLocalsRecord(ImmutableArray bytes) { int offset = 0; int bucketCount = ReadInt32(bytes, ref offset); - var builder = ArrayBuilder.GetInstance(bucketCount); + var builder = ArrayBuilder.GetInstance(bucketCount); for (int i = 0; i < bucketCount; i++) { const int FlagBytesCount = 64; @@ -269,8 +269,7 @@ public static ImmutableArray DecodeDynamicLocalsRecord(Immut var name = pooled.ToStringAndFree(); - var bucket = new DynamicLocalBucket(flagCount, flags, slotId, name); - builder.Add(bucket); + builder.Add(new DynamicLocalInfo(flagCount, flags, slotId, name)); } return builder.ToImmutableAndFree(); @@ -475,140 +474,6 @@ public static ImmutableArray GetVisualBasicImportStrings(this ISymUnmana return importStrings; } - /// Bad data. - public static void GetCSharpDynamicLocalInfo( - byte[] customDebugInfo, - int methodToken, - int methodVersion, - IEnumerable scopes, - out ImmutableDictionary> dynamicLocalMap, - out ImmutableDictionary> dynamicLocalConstantMap) - { - dynamicLocalMap = ImmutableDictionary>.Empty; - dynamicLocalConstantMap = ImmutableDictionary>.Empty; - - var record = TryGetCustomDebugInfoRecord(customDebugInfo, CustomDebugInfoKind.DynamicLocals); - if (record.IsDefault) - { - return; - } - - ImmutableDictionary>.Builder localBuilder = null; - ImmutableDictionary>.Builder constantBuilder = null; - - var buckets = RemoveAmbiguousLocals(DecodeDynamicLocalsRecord(record), scopes); - foreach (var bucket in buckets) - { - var slot = bucket.SlotId; - var flags = GetFlags(bucket); - if (slot < 0) - { - constantBuilder = constantBuilder ?? ImmutableDictionary.CreateBuilder>(); - constantBuilder[bucket.Name] = flags; - } - else - { - localBuilder = localBuilder ?? ImmutableDictionary.CreateBuilder>(); - localBuilder[slot] = flags; - } - } - - if (localBuilder != null) - { - dynamicLocalMap = localBuilder.ToImmutable(); - } - - if (constantBuilder != null) - { - dynamicLocalConstantMap = constantBuilder.ToImmutable(); - } - } - - /// - /// If there are dynamic locals or constants associated with SlotId == 0, check all locals and - /// constants with SlotId == 0 for duplicate names and discard duplicates since we - /// cannot determine which local or constant the dynamic info is associated with. - /// - private static ImmutableArray RemoveAmbiguousLocals( - ImmutableArray locals, - IEnumerable scopes) - { - const byte DuplicateName = 0; - const byte VariableName = 1; - const byte ConstantName = 2; - - var localNames = PooledDictionary.GetInstance(); - var firstLocal = GetFirstLocal(scopes); - if (firstLocal != null) - { - localNames.Add(firstLocal.GetName(), VariableName); - } - - foreach (var scope in scopes) - { - foreach (var constant in scope.GetConstants()) - { - var name = constant.GetName(); - localNames[name] = localNames.ContainsKey(name) ? DuplicateName : ConstantName; - } - } - - var builder = ArrayBuilder.GetInstance(); - foreach (var local in locals) - { - int slot = local.SlotId; - var name = local.Name; - if (slot == 0) - { - byte localOrConstant; - localNames.TryGetValue(name, out localOrConstant); - if (localOrConstant == DuplicateName) - { - continue; - } - - if (localOrConstant == ConstantName) - { - slot = -1; - } - } - - builder.Add(new DynamicLocalBucket(local.FlagCount, local.Flags, slot, name)); - } - - var result = builder.ToImmutableAndFree(); - localNames.Free(); - return result; - } - - private static ISymUnmanagedVariable GetFirstLocal(IEnumerable scopes) - { - foreach (var scope in scopes) - { - foreach (var local in scope.GetLocals()) - { - if (local.GetSlot() == 0) - { - return local; - } - } - } - - return null; - } - - private static ImmutableArray GetFlags(DynamicLocalBucket bucket) - { - int flagCount = bucket.FlagCount; - ulong flags = bucket.Flags; - var builder = ArrayBuilder.GetInstance(flagCount); - for (int i = 0; i < flagCount; i++) - { - builder.Add((flags & (1u << i)) != 0); - } - return builder.ToImmutableAndFree(); - } - private static void CheckVersion(byte globalVersion, int methodToken) { if (globalVersion != CDI.CdiVersion) @@ -1032,14 +897,14 @@ public StateMachineHoistedLocalScope(int startoffset, int endOffset) } } - internal struct DynamicLocalBucket + internal struct DynamicLocalInfo { public readonly int FlagCount; public readonly ulong Flags; public readonly int SlotId; public readonly string Name; - public DynamicLocalBucket(int flagCount, ulong flags, int slotId, string name) + public DynamicLocalInfo(int flagCount, ulong flags, int slotId, string name) { this.FlagCount = flagCount; this.Flags = flags;