File: Data\DacEnumerableHash.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.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.Diagnostics.DataContractReader.Data;

/// <summary>
/// Parses hash tables that are implemented by DacEnumerableHash defined in dacenumerablehash.h
/// Requires the following datadescriptor fields on the passed type:
/// Buckets - Pointer to array of VolatileEntry pointers
/// Count - Count of elements
/// VolatileEntryValue - Offset of the value in the VolatileEntry struct
/// VolatileEntryNextEntry - Offset of the next entry pointer in the VolatileEntry struct
/// </summary>
internal sealed class DacEnumerableHash
{
    private const int SLOT_LENGTH = 0;
    private const int SKIP_SPECIAL_SLOTS = 3;
    private const int END_SENTINEL = 0x1;

    private readonly Target _target;
    private readonly Target.TypeInfo _type;

    public DacEnumerableHash(Target target, TargetPointer address, Target.TypeInfo type)
    {
        // init fields
        _target = target;
        _type = type;

        Buckets = _target.ReadPointerField(address, _type, nameof(Buckets));
        Count = _target.ReadField<uint>(address, _type, nameof(Count));

        // read items in the hash table
        uint length = GetLength();

        List<TargetPointer> entries = [];
        for (int i = 0; i < length; i++)
        {
            // indexes 0, 1, 2 have special purposes. buckets start at SKIP_SPECIAL_SLOTS
            int bucketOffset = i + SKIP_SPECIAL_SLOTS;
            TargetPointer chainElement = _target.ReadPointer(Buckets + (ulong)(bucketOffset * _target.PointerSize));
            List<TargetPointer> elements = ReadChain(chainElement);
            entries.AddRange(elements);
        }

        Debug.Assert(Count == entries.Count);

        Entries = entries;
    }

    public TargetPointer Buckets { get; init; }
    public uint Count { get; init; }

    public IReadOnlyList<TargetPointer> Entries { get; init; }

    internal sealed class VolatileEntry
    {
        public VolatileEntry(Target target, TargetPointer address, Target.TypeInfo type)
        {
            // offsets are stored on the parent type
            VolatileEntryValue = address + (ulong)type.Fields[nameof(VolatileEntryValue)].Offset;
            VolatileEntryNextEntry = target.ReadPointerField(address, type, nameof(VolatileEntryNextEntry));
        }

        public TargetPointer VolatileEntryValue { get; init; }
        public TargetPointer VolatileEntryNextEntry { get; init; }
    }

    private uint GetLength()
    {
        // First pointer is a size_t length
        TargetPointer length = _target.ReadPointer(Buckets + (ulong)(SLOT_LENGTH * _target.PointerSize));
        return (uint)length;
    }


    private static bool IsEndSentinel(TargetPointer value)
    {
        return ((ulong)value & END_SENTINEL) == END_SENTINEL;
    }

    private List<TargetPointer> ReadChain(TargetPointer chainElement)
    {
        List<TargetPointer> elements = [];

        while (!IsEndSentinel(chainElement))
        {
            VolatileEntry volatileEntry = new(_target, chainElement, _type);
            elements.Add(volatileEntry.VolatileEntryValue);

            chainElement = volatileEntry.VolatileEntryNextEntry;
        }

        return elements;
    }
}