| File: Contracts\StackWalk\GC\GcSignatureTypeProvider.cs | Web Access |
| Project: 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; } } }