| File: DebugExtensions.cs | Web Access |
| Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Legacy\Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal enum HResultValidationMode { /// <summary> /// HRESULTs must match exactly. /// </summary> Exact, /// <summary> /// Success HRESULTs must match exactly, but any two failing HRESULTs (negative values) are considered equivalent. /// This is the recommended default because the cDAC and native DAC may use different exception types for the /// same invalid input (e.g., InvalidOperationException vs E_INVALIDARG), producing different failing HRESULTs. /// </summary> AllowDivergentFailures, /// <summary> /// Allows divergent success HRESULTs, but failing HRESULTs must match exactly. /// </summary> AllowDivergentSuccess, /// <summary> /// Like <see cref="AllowDivergentFailures"/>, but also allows the cDAC to succeed when the native DAC fails. /// The native DAC's MetaSig constructor traverses MethodDesc -> Module -> MDImport -> signature blob via /// DAC host pointers, and any intermediate read can throw under EX_TRY on certain frames (e.g., EH dispatch). /// The cDAC reads the same metadata through contracts (EcmaMetadata -> PEImage -> metadata blob) which uses /// a different pointer traversal path that doesn't hit the same failure. /// </summary> AllowCdacSuccess, } internal static class DebugExtensions { #if DEBUG private const int LastExceptionRingSize = 4; [ThreadStatic] private static Exception?[]? _debugLastExceptions; [ThreadStatic] private static int _debugLastExceptionsNextIndex; static DebugExtensions() { AppDomain.CurrentDomain.FirstChanceException += static (_, e) => { Exception?[] ring = _debugLastExceptions ??= new Exception?[LastExceptionRingSize]; ring[_debugLastExceptionsNextIndex] = e.Exception; _debugLastExceptionsNextIndex = (_debugLastExceptionsNextIndex + 1) % LastExceptionRingSize; }; } private static Exception? FindMatchingException(int hr) { Exception?[]? ring = _debugLastExceptions; if (ring is null) return null; // Walk from newest to oldest. int next = _debugLastExceptionsNextIndex; for (int i = 0; i < LastExceptionRingSize; i++) { int idx = (next - 1 - i + LastExceptionRingSize) % LastExceptionRingSize; Exception? ex = ring[idx]; if (ex is not null && ex.HResult == hr) return ex; } return null; } private static void ClearLastExceptions() { Exception?[]? ring = _debugLastExceptions; if (ring is not null) Array.Clear(ring); _debugLastExceptionsNextIndex = 0; } #endif extension(Debug) { [Conditional("DEBUG")] internal static void ValidateHResult( int cdacHr, int dacHr, HResultValidationMode mode = HResultValidationMode.AllowDivergentFailures, [CallerFilePath] string? filePath = null, [CallerLineNumber] int lineNumber = 0) { bool match = mode switch { HResultValidationMode.Exact => cdacHr == dacHr, HResultValidationMode.AllowDivergentFailures => cdacHr == dacHr || (cdacHr < 0 && dacHr < 0), HResultValidationMode.AllowDivergentSuccess => cdacHr == dacHr || (cdacHr >= 0 && dacHr >= 0), HResultValidationMode.AllowCdacSuccess => cdacHr == dacHr || (cdacHr < 0 && dacHr < 0) || (cdacHr >= 0 && dacHr < 0), _ => cdacHr == dacHr, }; if (!match) { string message = $"HResult mismatch - cDAC: 0x{unchecked((uint)cdacHr):X8}, DAC: 0x{unchecked((uint)dacHr):X8} ({Path.GetFileName(filePath)}:{lineNumber})"; #if DEBUG if (cdacHr < 0) { Exception? ex = FindMatchingException(cdacHr); if (ex is not null) { message += $"{Environment.NewLine}---- cDAC exception ----{Environment.NewLine}{ex}{Environment.NewLine}---- end cDAC exception ----"; } } #endif Debug.Assert(false, message); } #if DEBUG ClearLastExceptions(); #endif } } }