File: Contracts\StackWalk\FrameHandling\MethodGCRefMapLookup.cs
Web Access
Project: Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts)
// 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.Collections.Generic;
using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers;

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

// DispatchMap uses the MSB-first integer encoding from decodemd.cpp, not GCRefMap's bitstream.
internal struct DispatchMapDecoder(Target target, TargetPointer address)
{
    private TargetPointer _address = address;
    private int _remainingBits;
    private byte _currentByte;

    private uint ReadBit()
    {
        if (_remainingBits == 0)
        {
            _currentByte = target.Read<byte>(_address);
            _address = new(checked(_address.Value + 1));
            _remainingBits = 8;
        }
        return (uint)(_currentByte >> --_remainingBits) & 1;
    }

    internal uint Read()
    {
        ReadOnlySpan<uint> bases = [0, 1, 3, 11, 139, 32907];
        int ones = 0;
        while (ReadBit() != 0)
        {
            if (++ones == bases.Length)
                throw new InvalidOperationException("Invalid DispatchMap integer prefix.");
        }
        uint payload = 0;
        for (int bits = (1 << ones) - 1; bits > 0; bits--)
            payload = (payload << 1) | ReadBit();
        return checked(bases[ones] + payload);
    }
}

internal sealed class MethodGCRefMapLookup(Target target, FrameHelpers frames)
{
    private const uint HasDispatchMapSlot = 0x4;
    private readonly IRuntimeTypeSystem _rts = target.Contracts.RuntimeTypeSystem;

    internal TargetPointer Find(MethodDescHandle method, TargetPointer receiver, bool dispatch)
    {
        TargetPointer map = dispatch ? GetShareableGCRefMap(method.Address) : frames.GetGCRefMapFromMethodDesc(method.Address);
        if (map != TargetPointer.Null)
            return map;

        if (_rts.GetGenericMethodInstantiation(method).Length != 0 ||
            (!dispatch && _rts.GetGenericContextLoc(method) is GenericContextLoc.InstArgMethodDesc or GenericContextLoc.InstArgMethodTable))
            return TargetPointer.Null;

        Data.MethodDesc data = target.ProcessedData.GetOrAdd<Data.MethodDesc>(method.Address);
        if (((MethodDescFlags_1.MethodDescFlags)data.Flags & MethodDescFlags_1.MethodDescFlags.Static) != 0)
            return TargetPointer.Null;

        TargetPointer owner = _rts.GetMethodTable(method);
        ushort slot = _rts.GetSlotNumber(method);
        map = FindGCRefMapForSlot(owner, slot);
        if (map != TargetPointer.Null)
            return map;
        if (receiver == TargetPointer.Null)
            receiver = owner;
        if (IsInterface(GetMethodTable(receiver)))
            return TargetPointer.Null;
        MethodDescHandle implementation = method;
        if (IsInterface(GetMethodTable(owner)))
        {
            TargetPointer resolved = ResolveInterfaceTarget(owner, slot, receiver);
            if (resolved == TargetPointer.Null)
                return TargetPointer.Null;
            implementation = _rts.GetMethodDescHandle(resolved);
            map = FindGCRefMapForSlot(_rts.GetMethodTable(implementation), _rts.GetSlotNumber(implementation));
            if (map != TargetPointer.Null)
                return map;
        }

        for (TargetPointer current = receiver; current != TargetPointer.Null; current = GetMethodTable(current).ParentMethodTable)
        {
            Data.MethodTable mt = GetMethodTable(current);
            TargetPointer dispatchMap = GetDispatchMap(mt);
            if (dispatchMap == TargetPointer.Null)
                continue;

            DispatchMapDecoder decoder = new(target, dispatchMap);
            uint header = decoder.Read();
            if ((header & 1) != 0)
            {
                uint count = decoder.Read();
                for (uint i = 0; i < count; i++)
                {
                    ushort source = checked((ushort)decoder.Read());
                    ushort destination = checked((ushort)decoder.Read());
                    if (!Matches(receiver, source, implementation) && !Matches(receiver, destination, implementation))
                        continue;
                    map = FindGCRefMapForSlot(current, source);
                    if (map == TargetPointer.Null)
                        map = FindGCRefMapForSlot(current, destination);
                    if (map != TargetPointer.Null)
                        return map;
                }
            }

            foreach ((uint typeID, ushort source, ushort destination) in GetInterfaceEntries(mt))
            {
                if (!Matches(receiver, destination, implementation))
                    continue;
                map = FindGCRefMapForSlot(GetInterface(mt, typeID), source);
                if (map != TargetPointer.Null)
                    return map;
            }
        }
        return TargetPointer.Null;
    }

    private TargetPointer GetInterface(Data.MethodTable mt, uint typeID)
    {
        TargetPointer interfaceMap = mt.InterfaceMap ??
            throw new InvalidOperationException("MethodTable.InterfaceMap is required for virtual GC reference maps.");
        return target.ReadPointer(interfaceMap + checked((ulong)(typeID - 1) * (ulong)target.PointerSize));
    }

    private TargetPointer ResolveInterfaceTarget(TargetPointer owner, ushort slot, TargetPointer receiver)
    {
        for (TargetPointer current = receiver; current != TargetPointer.Null; current = GetMethodTable(current).ParentMethodTable)
        {
            Data.MethodTable mt = GetMethodTable(current);
            foreach ((uint typeID, ushort source, ushort destination) in GetInterfaceEntries(mt))
            {
                if (source != slot || GetInterface(mt, typeID) != owner)
                    continue;
                TargetPointer implementation = _rts.GetMethodDescForSlot(_rts.GetTypeHandle(receiver), destination);
                if (implementation != TargetPointer.Null)
                    return implementation;
            }
        }
        return TargetPointer.Null;
    }

    private IEnumerable<(uint TypeID, ushort Source, ushort Target)> GetInterfaceEntries(Data.MethodTable mt)
    {
        TargetPointer map = GetDispatchMap(mt);
        if (map == TargetPointer.Null)
            yield break;
        DispatchMapDecoder decoder = new(target, map);
        uint header = decoder.Read();
        uint numTypes = header >> 1;
        if ((header & 1) != 0)
        {
            uint aliases = decoder.Read();
            for (uint i = 0; i < aliases; i++)
            {
                decoder.Read();
                decoder.Read();
            }
        }
        long typeID = -1;
        for (uint type = 0; type < numTypes; type++)
        {
            typeID = checked(typeID + decoder.Read() + 1);
            uint count = decoder.Read();
            uint hasNegatives = decoder.Read();
            if (hasNegatives > 1 || typeID <= 0 || typeID > mt.NumInterfaces || count == 0)
                throw new InvalidOperationException("Invalid DispatchMap interface header.");
            long source = -1;
            long destination = -1;
            for (uint entry = 0; entry < count; entry++)
            {
                source = checked(source + decoder.Read() + 1);
                uint delta = decoder.Read();
                long signedDelta = hasNegatives == 0 ? delta : (delta & 1) != 0 ? (delta + 1L) / 2 : -(long)delta / 2;
                destination = checked(destination + signedDelta + 1);
                yield return (checked((uint)typeID), checked((ushort)source), checked((ushort)destination));
            }
        }
    }

    private Data.MethodTable GetMethodTable(TargetPointer address)
        => target.ProcessedData.GetOrAdd<Data.MethodTable>(address);

    private static bool IsInterface(Data.MethodTable mt)
        => new MethodTableFlags_1 { MTFlags = mt.MTFlags }.IsInterface;

    private bool Matches(TargetPointer receiver, ushort source, MethodDescHandle implementation)
    {
        TargetPointer candidate = _rts.GetMethodDescForSlot(_rts.GetTypeHandle(receiver), source);
        if (candidate == TargetPointer.Null)
            return false;
        if (candidate == implementation.Address)
            return true;

        MethodDescHandle method = _rts.GetMethodDescHandle(candidate);
        ITypeHandle owner = _rts.GetTypeHandle(_rts.GetMethodTable(method));
        ITypeHandle implementationOwner = _rts.GetTypeHandle(_rts.GetMethodTable(implementation));
        if (_rts.GetCanonicalMethodTable(owner) != _rts.GetCanonicalMethodTable(implementationOwner) ||
            _rts.GetModule(owner) != _rts.GetModule(implementationOwner) ||
            _rts.GetMethodToken(method) != _rts.GetMethodToken(implementation))
            return false;

        const AsyncMethodFlags VariantMask = AsyncMethodFlags.IsAsyncVariant | AsyncMethodFlags.ReturnDroppingThunk;
        return (_rts.GetAsyncMethodFlags(method) & VariantMask) == (_rts.GetAsyncMethodFlags(implementation) & VariantMask);
    }

    private TargetPointer GetDispatchMap(Data.MethodTable mt)
    {
        if ((mt.MTFlags2 & HasDispatchMapSlot) == 0)
        {
            TargetPointer canonical = _rts.GetCanonicalMethodTable(_rts.GetTypeHandle(mt.Address));
            if (canonical == mt.Address)
                return TargetPointer.Null;
            mt = GetMethodTable(canonical);
        }
        if ((mt.MTFlags2 & HasDispatchMapSlot) == 0)
            return TargetPointer.Null;
        uint size = target.GetTypeInfo(DataType.MethodTableAuxiliaryData).Size ??
            throw new InvalidOperationException("MethodTableAuxiliaryData size is required for dispatch map lookup.");
        return mt.AuxiliaryData + size;
    }

    private TargetPointer FindGCRefMapForSlot(TargetPointer owner, ushort slot)
    {
        Data.MethodTable mt = GetMethodTable(owner);
        while (true)
        {
            foreach (TargetPointer candidate in _rts.GetIntroducedMethodDescs(_rts.GetTypeHandle(mt.Address)))
            {
                MethodDescHandle method = _rts.GetMethodDescHandle(candidate);
                if (_rts.GetSlotNumber(method) != slot || _rts.IsUnboxingStub(method))
                    continue;
                TargetPointer map = GetShareableGCRefMap(candidate);
                if (map != TargetPointer.Null)
                    return map;
            }
            if (IsInterface(mt) || slot >= mt.NumVirtuals || mt.ParentMethodTable == TargetPointer.Null)
                return TargetPointer.Null;
            mt = GetMethodTable(mt.ParentMethodTable);
            if (slot >= mt.NumVirtuals)
                return TargetPointer.Null;
        }
    }

    private TargetPointer GetShareableGCRefMap(TargetPointer method)
    {
        MethodDescHandle md = _rts.GetMethodDescHandle(method);
        if (_rts.GetGenericMethodInstantiation(md).Length != 0 ||
            _rts.GetGenericContextLoc(md) is GenericContextLoc.InstArgMethodDesc or GenericContextLoc.InstArgMethodTable)
            return TargetPointer.Null;
        return frames.GetGCRefMapFromMethodDesc(method);
    }
}