File: ClrDataExceptionState.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.Runtime.InteropServices.Marshalling;
using Microsoft.Diagnostics.DataContractReader.Contracts;

namespace Microsoft.Diagnostics.DataContractReader.Legacy;

[GeneratedComClass]
public sealed unsafe partial class ClrDataExceptionState : IXCLRDataExceptionState
{
    private readonly Target _target;
    private readonly TargetPointer _threadAddress;
    private readonly uint _flags;
    private readonly TargetPointer _exceptionInfoAddress;
    private readonly TargetPointer _thrownObjectHandle;
    private readonly TargetPointer _previousExInfoAddress;
    private readonly IXCLRDataExceptionState? _legacyImpl;

    public ClrDataExceptionState(
        Target target,
        TargetPointer threadAddress,
        uint flags,
        TargetPointer exceptionInfoAddress,
        TargetPointer thrownObjectHandle,
        TargetPointer previousExInfoAddress,
        IXCLRDataExceptionState? legacyImpl)
    {
        _target = target;
        _threadAddress = threadAddress;
        _flags = flags;
        _exceptionInfoAddress = exceptionInfoAddress;
        _thrownObjectHandle = thrownObjectHandle;
        _previousExInfoAddress = previousExInfoAddress;
        _legacyImpl = legacyImpl;
    }

    int IXCLRDataExceptionState.GetFlags(uint* flags)
    {
        int hr = HResults.S_OK;
        try
        {
            if (flags is null)
                throw new ArgumentNullException(nameof(flags));

            *flags = _flags;
            if (_previousExInfoAddress != TargetPointer.Null)
                *flags |= (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_NESTED;
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            uint legacyFlags;
            int hrLocal = _legacyImpl.GetFlags(&legacyFlags);
            Debug.ValidateHResult(hr, hrLocal);
            if (hr == HResults.S_OK)
                Debug.Assert(*flags == legacyFlags, $"cDAC flags: {*flags:x}, DAC flags: {legacyFlags:x}");
        }
#endif

        return hr;
    }

    int IXCLRDataExceptionState.GetPrevious(DacComNullableByRef<IXCLRDataExceptionState> exState)
    {
        int hr = HResults.S_OK, hrLocal = HResults.S_OK;
        IXCLRDataExceptionState? legacyPrevious = null;

        if (_legacyImpl is not null)
        {
            DacComNullableByRef<IXCLRDataExceptionState> legacyPreviousOut = new(isNullRef: false);
            hrLocal = _legacyImpl.GetPrevious(legacyPreviousOut);
            legacyPrevious = legacyPreviousOut.Interface;
        }
        try
        {
            if (_previousExInfoAddress == TargetPointer.Null)
            {
                hr = HResults.S_FALSE;
            }
            else
            {
                _target.Contracts.Exception.GetNestedExceptionInfo(
                    _previousExInfoAddress,
                    out TargetPointer nextNestedException,
                    out TargetPointer prevExThrownObjectHandle);
                exState.Interface = new ClrDataExceptionState(
                    _target,
                    _threadAddress,
                    (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_DEFAULT,
                    _previousExInfoAddress,
                    prevExThrownObjectHandle,
                    nextNestedException,
                    legacyPrevious
                );
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif

        return hr;
    }

    int IXCLRDataExceptionState.GetManagedObject(DacComNullableByRef<IXCLRDataValue> value)
    {
        int hr = HResults.S_OK, hrLocal = HResults.S_OK;
        IXCLRDataValue? legacyValue = null;

        if (_legacyImpl is not null && LegacyFallbackHelper.CanFallback())
        {
            DacComNullableByRef<IXCLRDataValue> legacyValueOut = new(value.IsNullRef);
            hrLocal = _legacyImpl.GetManagedObject(legacyValueOut);
            legacyValue = legacyValueOut.Interface;
        }

        try
        {
            if (_thrownObjectHandle == TargetPointer.Null)
                throw new ArgumentException();

            TargetPointer exceptionObject;
            try
            {
                exceptionObject = _target.ReadPointer(_thrownObjectHandle);
            }
            catch (VirtualReadException)
            {
                throw new ArgumentException();
            }

            IObject objectContract = _target.Contracts.Object;
            ulong objectSize = objectContract.GetSize(exceptionObject);
            ITypeHandle typeHandle = _target.Contracts.RuntimeTypeSystem.GetTypeHandle(
                objectContract.GetMethodTableAddress(exceptionObject));
            value.Interface = new ClrDataValue(
                _target,
                _threadAddress,
                (uint)ClrDataValueFlag.DEFAULT,
                typeHandle,
                exceptionObject,
                [
                    new NativeVarLocation
                    {
                        AddressOrValue = exceptionObject.ToClrDataAddress(_target),
                        Size = objectSize,
                        IsRegisterValue = false,
                    },
                ],
                legacyValue);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif

        return hr;
    }

    int IXCLRDataExceptionState.GetBaseType(/*CLRDataBaseExceptionType*/ uint* type) => HResults.E_NOTIMPL;

    int IXCLRDataExceptionState.GetCode(uint* code) => HResults.E_NOTIMPL;

    int IXCLRDataExceptionState.GetString(uint bufLen, uint* strLen, char* str)
    {
        int hr = HResults.S_OK;
        try
        {
            TargetPointer exceptionObject = _target.ReadPointer(_thrownObjectHandle);
            ExceptionData exceptionData = _target.Contracts.Exception.GetExceptionData(exceptionObject);
            if (exceptionData.Message == TargetPointer.Null)
            {
                if (strLen is not null)
                {
                    *strLen = 0;
                }

                if (bufLen >= 1)
                {
                    if (str is null)
                    {
                        hr = HResults.E_INVALIDARG;
                    }
                    else
                    {
                        str[0] = '\0';
                    }
                }
            }
            else
            {
                string message = _target.Contracts.Object.GetStringValue(exceptionData.Message);
                OutputBufferHelpers.CopyStringToBuffer(str, bufLen, strLen, message);
                if (str is not null && bufLen < (uint)(message.Length + 1))
                    hr = HResults.S_FALSE;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            char[] strLocal = new char[Math.Max((int)bufLen, 1)];
            uint legacyStrLen;
            int hrLocal;
            fixed (char* strLocalPtr = strLocal)
            {
                hrLocal = _legacyImpl.GetString(bufLen, &legacyStrLen, str is null ? null : strLocalPtr);
            }
            Debug.ValidateHResult(hr, hrLocal);
            if (hr >= 0)
            {
                Debug.Assert(strLen == null || *strLen == legacyStrLen);
                int cmpLen = Math.Min((int)legacyStrLen, (int)bufLen) - 1;
                Debug.Assert(str == null || cmpLen <= 0 || new ReadOnlySpan<char>(strLocal, 0, cmpLen).SequenceEqual(new ReadOnlySpan<char>(str, cmpLen)));
            }
        }
#endif

        return hr;
    }

    int IXCLRDataExceptionState.Request(uint reqCode, uint inBufferSize, byte* inBuffer, uint outBufferSize, byte* outBuffer)
    {
        int hr = HResults.E_INVALIDARG;

        if (reqCode == (uint)CLRDataGeneralRequest.CLRDATA_REQUEST_REVISION)
        {
            if (inBufferSize == 0 && inBuffer is null && outBufferSize == sizeof(uint) && outBuffer is not null)
            {
                *(uint*)outBuffer = 2;
                hr = HResults.S_OK;
            }
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            byte[] localBuffer = new byte[(int)outBufferSize];
            fixed (byte* localOutBuffer = localBuffer)
            {
                int hrLocal = _legacyImpl.Request(reqCode, inBufferSize, inBuffer, outBufferSize, localOutBuffer);
                Debug.ValidateHResult(hr, hrLocal);
                if (hr == HResults.S_OK && reqCode == (uint)CLRDataGeneralRequest.CLRDATA_REQUEST_REVISION)
                {
                    Debug.Assert(outBufferSize == sizeof(uint) && outBuffer is not null);
                    uint legacyRevision = *(uint*)localOutBuffer;
                    uint revision = *(uint*)outBuffer;
                    Debug.Assert(revision == legacyRevision);
                }
            }
        }
#endif
        return hr;
    }

    int IXCLRDataExceptionState.IsSameState(EXCEPTION_RECORD64* exRecord, uint contextSize, byte* cxRecord)
    {
        int hr = IsSameState2((uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE, exRecord);
#if DEBUG
        if (_legacyImpl is not null)
        {
            int hrLocal = _legacyImpl.IsSameState(exRecord, contextSize, cxRecord);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    int IXCLRDataExceptionState.IsSameState2(uint flags, EXCEPTION_RECORD64* exRecord, uint contextSize, byte* cxRecord)
    {
        int hr = IsSameState2(flags, exRecord);
#if DEBUG
        if (_legacyImpl is not null)
        {
            int hrLocal = _legacyImpl.IsSameState2(flags, exRecord, contextSize, cxRecord);
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }

    private int IsSameState2(uint flags, EXCEPTION_RECORD64* exRecord)
    {
        int hr = HResults.S_FALSE;
        try
        {
            if ((flags & ~(uint)(CLRDataExceptionSameFlag.CLRDATA_EXSAME_SECOND_CHANCE | CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE)) != 0)
                throw new ArgumentException();

            if ((_flags & (uint)CLRDataExceptionStateFlag.CLRDATA_EXCEPTION_PARTIAL) != 0)
            {
                if ((flags & (uint)CLRDataExceptionSameFlag.CLRDATA_EXSAME_FIRST_CHANCE) != 0)
                    hr = HResults.S_OK;
            }
            else
            {
                if (exRecord is null)
                    throw new NullReferenceException();

                TargetPointer exceptionRecord;
                if (_exceptionInfoAddress != TargetPointer.Null)
                {
                    Target.TypeInfo exceptionInfoType = _target.GetTypeInfo(DataType.ExceptionInfo);
                    exceptionRecord = _target.ReadPointer(
                        _exceptionInfoAddress + (ulong)exceptionInfoType.Fields["ExceptionRecord"].Offset);
                }
                else
                {
                    ThreadData threadData = _target.Contracts.Thread.GetThreadData(_threadAddress);
                    exceptionRecord = threadData.OSExceptionRecord;
                }

                TargetPointer exceptionAddress = _target.ReadPointer(
                    exceptionRecord + (ulong)(sizeof(uint) * 2 + _target.PointerSize));
                TargetPointer requestedAddress = new(
                    _target.PointerSize == sizeof(ulong)
                        ? exRecord->ExceptionAddress
                        : (uint)exRecord->ExceptionAddress);

                if (exceptionAddress == requestedAddress)
                    hr = HResults.S_OK;
            }
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }

        return hr;
    }

    int IXCLRDataExceptionState.GetTask(DacComNullableByRef<IXCLRDataTask> task)
    {
        int hr = HResults.S_OK, hrLocal = HResults.S_OK;
        IXCLRDataTask? legacyTask = null;

        if (_legacyImpl is not null)
        {
            DacComNullableByRef<IXCLRDataTask> legacyTaskOut = new(isNullRef: false);
            hrLocal = _legacyImpl.GetTask(legacyTaskOut);
            legacyTask = legacyTaskOut.Interface;
        }
        try
        {
            task.Interface = new ClrDataTask(_threadAddress, _target, legacyTask);
        }
        catch (System.Exception ex)
        {
            hr = ex.HResult;
        }
#if DEBUG
        if (_legacyImpl is not null)
        {
            Debug.ValidateHResult(hr, hrLocal);
        }
#endif
        return hr;
    }
}