| File: CachingContractRegistry.cs | Web Access |
| Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader\Microsoft.Diagnostics.DataContractReader.csproj (Microsoft.Diagnostics.DataContractReader) |
// 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 System.Diagnostics.CodeAnalysis; using Microsoft.Diagnostics.DataContractReader.Contracts; namespace Microsoft.Diagnostics.DataContractReader; /// <summary> /// Contract registry that resolves contracts by (type, version) lookup. /// Has no knowledge of any specific contracts — all implementations are /// registered from outside via <see cref="Register{TContract}"/>. /// </summary> internal sealed class CachingContractRegistry : ContractRegistry { public delegate bool TryGetContractVersionDelegate(string contractName, [NotNullWhen(true)] out string? version); private readonly Dictionary<Type, IContract> _contracts = []; private readonly Dictionary<(Type, string), Func<Target, IContract>> _creators = []; private readonly HashSet<(Type, string)> _unsupportedVersions = []; private readonly Target _target; private readonly TryGetContractVersionDelegate _tryGetContractVersion; public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryGetContractVersion, params Action<ContractRegistry>[] contractRegistrations) { _target = target; _tryGetContractVersion = tryGetContractVersion; foreach (Action<ContractRegistry> register in contractRegistrations) { register(this); } } public override void Register<TContract>(string version, Func<Target, TContract> creator) { _creators[(typeof(TContract), version)] = t => creator(t); } public override void RegisterUnsupported<TContract>(string version) { _unsupportedVersions.Add((typeof(TContract), version)); } public override bool TryGetContract<TContract>([NotNullWhen(true)] out TContract contract, [NotNullWhen(false)] out System.Exception? failureException) { contract = default!; failureException = null; if (_contracts.TryGetValue(typeof(TContract), out IContract? cached)) { contract = (TContract)cached; return true; } if (!TryResolveCreator(typeof(TContract), TContract.Name, out Func<Target, IContract>? creator, out failureException)) { return false; } contract = (TContract)creator(_target); if (_contracts.TryAdd(typeof(TContract), contract)) { return true; } contract = (TContract)_contracts[typeof(TContract)]; return true; } public override bool TryValidate<TContract>([NotNullWhen(false)] out System.Exception? failureException) { failureException = null; // An already-instantiated contract is, by definition, supported. if (_contracts.ContainsKey(typeof(TContract))) { return true; } // Presence-only validation: confirm this cDAC can provide the contract at the version the // target advertises, but never invoke the creator. Instantiating a contract can read target // memory and chain into other contracts, and a partial capture (for example a minidump) may // legitimately be missing the data a given contract needs while still being fully usable for // others (stack walks and similar). Reading target memory here could spuriously fail // validation and reject an otherwise serviceable dump, so resolve the creator without running it. return TryResolveCreator(typeof(TContract), TContract.Name, out _, out failureException); } /// <summary> /// Classifies whether a registered creator exists for the target-advertised version of a /// contract, without invoking it. Shared by <see cref="TryGetContract{TContract}(out TContract, out System.Exception?)"/> /// and <see cref="TryValidate{TContract}(out System.Exception?)"/>. /// </summary> private bool TryResolveCreator( Type contractType, string contractName, [NotNullWhen(true)] out Func<Target, IContract>? creator, [NotNullWhen(false)] out System.Exception? failureException) { creator = null; failureException = null; if (!_tryGetContractVersion(contractName, out string? version)) { if (_creators.TryGetValue((contractType, string.Empty), out creator)) { return true; } failureException = new ContractMissingException(contractName); return false; } if (!_creators.TryGetValue((contractType, version), out creator)) { failureException = _unsupportedVersions.Contains((contractType, version)) ? new ContractObsoleteException(contractName, version) : new ContractUnrecognizedException(contractName, version); return false; } return true; } public override void Flush(FlushScope scope) { foreach (IContract contract in _contracts.Values) { contract.Flush(scope); } } }