File: Contracts\StackWalk\GC\GcSignatureTypeProvider.cs
Web Access
Project: src\src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\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.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using Microsoft.Diagnostics.DataContractReader.SignatureHelpers;

namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers;

/// <summary>
/// Classification of a signature type for GC scanning purposes.
/// </summary>
internal enum GcTypeKind
{
    /// <summary>Not a GC reference (primitives, pointers).</summary>
    None,
    /// <summary>Object reference (class, string, array).</summary>
    Ref,
    /// <summary>Interior pointer (byref).</summary>
    Interior,
    /// <summary>Value type that may contain embedded GC references.</summary>
    Other,
}

/// <summary>
/// Generic context used to resolve <c>ELEMENT_TYPE_VAR</c> and <c>ELEMENT_TYPE_MVAR</c>
/// while decoding a method signature for GC scanning. <see cref="ClassContext"/> is the
/// owning type's <see cref="TypeHandle"/> (used for VAR), and <see cref="MethodContext"/>
/// is the owning method's <see cref="MethodDescHandle"/> (used for MVAR).
/// </summary>
internal readonly record struct GcSignatureContext(TypeHandle ClassContext, MethodDescHandle MethodContext);

/// <summary>
/// Classifies signature types for GC scanning purposes.
/// Implements <see cref="IRuntimeSignatureTypeProvider{TType, TGenericContext}"/> which
/// is a superset of SRM's <see cref="ISignatureTypeProvider{TType, TGenericContext}"/>,
/// adding support for <c>ELEMENT_TYPE_INTERNAL</c>.
/// </summary>
/// <remarks>
/// The provider is scoped to a single module: <c>GetTypeFromDefinition</c> and
/// <c>GetTypeFromReference</c> resolve TypeDef/TypeRef tokens via the module's
/// lookup tables so enums (and other runtime-normalized value types) are classified
/// using the actual <see cref="CorElementType"/>, matching native
/// <c>SigPointer::PeekElemTypeNormalized</c>.
/// </remarks>
internal sealed class GcSignatureTypeProvider
    : IRuntimeSignatureTypeProvider<GcTypeKind, GcSignatureContext>
{
    private readonly Target _target;
    private readonly ModuleHandle _moduleHandle;

    public GcSignatureTypeProvider(Target target, ModuleHandle moduleHandle)
    {
        _target = target;
        _moduleHandle = moduleHandle;
    }

    public GcTypeKind GetPrimitiveType(PrimitiveTypeCode typeCode)
        => typeCode switch
        {
            PrimitiveTypeCode.String or PrimitiveTypeCode.Object => GcTypeKind.Ref,
            PrimitiveTypeCode.TypedReference => GcTypeKind.Other,
            _ => GcTypeKind.None,
        };

    public GcTypeKind GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
        => ClassifyTokenLookup(_target.Contracts.Loader.GetLookupTables(_moduleHandle).TypeDefToMethodTable, MetadataTokens.GetToken(handle), rawTypeKind);

    public GcTypeKind GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
        => ClassifyTokenLookup(_target.Contracts.Loader.GetLookupTables(_moduleHandle).TypeRefToMethodTable, MetadataTokens.GetToken(handle), rawTypeKind);

    public GcTypeKind GetTypeFromSpecification(MetadataReader reader, GcSignatureContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
        => rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref;

    public GcTypeKind GetSZArrayType(GcTypeKind elementType) => GcTypeKind.Ref;
    public GcTypeKind GetArrayType(GcTypeKind elementType, ArrayShape shape) => GcTypeKind.Ref;
    public GcTypeKind GetByReferenceType(GcTypeKind elementType) => GcTypeKind.Interior;
    public GcTypeKind GetPointerType(GcTypeKind elementType) => GcTypeKind.None;

    public GcTypeKind GetGenericInstantiation(GcTypeKind genericType, ImmutableArray<GcTypeKind> typeArguments)
        => genericType;

    public GcTypeKind GetGenericMethodParameter(GcSignatureContext genericContext, int index)
    {
        try
        {
            ReadOnlySpan<TypeHandle> instantiation = _target.Contracts.RuntimeTypeSystem.GetGenericMethodInstantiation(genericContext.MethodContext);
            if ((uint)index >= (uint)instantiation.Length)
                return GcTypeKind.Ref;
            return ClassifyTypeHandle(instantiation[index]);
        }
        catch
        {
            return GcTypeKind.Ref;
        }
    }

    public GcTypeKind GetGenericTypeParameter(GcSignatureContext genericContext, int index)
    {
        try
        {
            IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
            TypeHandle classCtx = genericContext.ClassContext;

            if (rts.IsArray(classCtx, out _))
            {
                // Match native SigTypeContext::InitTypeContext (typectxt.cpp): arrays use
                // the element type as their class instantiation. RuntimeTypeSystem.GetInstantiation
                // returns an empty span for arrays, so consult GetTypeParam directly (the
                // managed equivalent of MethodTable::GetArrayInstantiation).
                Debug.Assert(index == 0, "Array class context has a 1-element instantiation; index > 0 indicates a malformed signature.");
                if (index != 0)
                    return GcTypeKind.Ref;
                return ClassifyTypeHandle(rts.GetTypeParam(classCtx));
            }

            ReadOnlySpan<TypeHandle> instantiation = rts.GetInstantiation(classCtx);
            if ((uint)index >= (uint)instantiation.Length)
                return GcTypeKind.Ref;
            return ClassifyTypeHandle(instantiation[index]);
        }
        catch
        {
            return GcTypeKind.Ref;
        }
    }

    public GcTypeKind GetFunctionPointerType(MethodSignature<GcTypeKind> signature) => GcTypeKind.None;
    public GcTypeKind GetModifiedType(GcTypeKind modifier, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType;
    public GcTypeKind GetInternalModifiedType(TargetPointer typeHandlePointer, GcTypeKind unmodifiedType, bool isRequired) => unmodifiedType;
    public GcTypeKind GetPinnedType(GcTypeKind elementType) => elementType;

    public GcTypeKind GetInternalType(TargetPointer typeHandlePointer)
    {
        if (typeHandlePointer == TargetPointer.Null)
            return GcTypeKind.None;

        try
        {
            return ClassifyTypeHandle(_target.Contracts.RuntimeTypeSystem.GetTypeHandle(typeHandlePointer));
        }
        catch
        {
            return GcTypeKind.Ref;
        }
    }

    /// <summary>
    /// Resolve a TypeDef/TypeRef token via the module's lookup tables and classify the
    /// resulting <see cref="TypeHandle"/>. Falls back to a <paramref name="rawTypeKind"/>-based
    /// classification when the type has not been loaded.
    /// </summary>
    private GcTypeKind ClassifyTokenLookup(TargetPointer lookupTable, int token, byte rawTypeKind)
    {
        try
        {
            TargetPointer typeHandlePtr = _target.Contracts.Loader.GetModuleLookupMapElement(lookupTable, (uint)token, out _);
            if (typeHandlePtr == TargetPointer.Null)
                return rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref;

            return ClassifyTypeHandle(_target.Contracts.RuntimeTypeSystem.GetTypeHandle(typeHandlePtr));
        }
        catch
        {
            return rawTypeKind == (byte)SignatureTypeKind.ValueType ? GcTypeKind.Other : GcTypeKind.Ref;
        }
    }

    /// <summary>
    /// Classify a resolved <see cref="TypeHandle"/>. Mirrors native
    /// <c>SigPointer::PeekElemTypeNormalized</c> + <c>gElementTypeInfo[etype].m_gc</c>:
    /// enums collapse to their underlying primitive (<see cref="GcTypeKind.None"/>) so
    /// they are skipped during stack scanning, matching native behavior.
    /// </summary>
    private GcTypeKind ClassifyTypeHandle(TypeHandle typeHandle)
    {
        if (typeHandle.Address == TargetPointer.Null)
            return GcTypeKind.Ref;

        IRuntimeTypeSystem rts = _target.Contracts.RuntimeTypeSystem;
        CorElementType corType = rts.GetSignatureCorElementType(typeHandle);

        switch (corType)
        {
            case CorElementType.Void:
            case CorElementType.Boolean:
            case CorElementType.Char:
            case CorElementType.I1:
            case CorElementType.U1:
            case CorElementType.I2:
            case CorElementType.U2:
            case CorElementType.I4:
            case CorElementType.U4:
            case CorElementType.I8:
            case CorElementType.U8:
            case CorElementType.R4:
            case CorElementType.R8:
            case CorElementType.I:
            case CorElementType.U:
            case CorElementType.FnPtr:
            case CorElementType.Ptr:
                return GcTypeKind.None;

            case CorElementType.Byref:
                return GcTypeKind.Interior;

            case CorElementType.ValueType:
                // Native PeekElemTypeNormalized resolves enums to their underlying primitive
                // CorElementType, which classifies as TYPE_GC_NONE in gElementTypeInfo.
                return rts.IsEnum(typeHandle) ? GcTypeKind.None : GcTypeKind.Other;

            default:
                return GcTypeKind.Ref;
        }
    }
}