File: src\libraries\System.Private.CoreLib\src\System\Runtime\InteropServices\TypeMapLazyDictionary.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// 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.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Text;
 
namespace System.Runtime.InteropServices
{
    [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
    internal static partial class TypeMapLazyDictionary
    {
        // See assemblynative.cpp for native version.
        private ref struct CallbackContext
        {
            private RuntimeAssembly? _currAssembly;
            private LazyExternalTypeDictionary? _externalTypeMap;
            private LazyProxyTypeDictionary? _proxyTypeMap;
            private ExceptionDispatchInfo? _creationException;
 
            public RuntimeAssembly CurrentAssembly
            {
                get
                {
                    // This field is set by native code.
                    Debug.Assert(_currAssembly != null);
                    return _currAssembly;
                }
            }
 
            public LazyExternalTypeDictionary ExternalTypeMap
            {
                [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
                get
                {
                    _externalTypeMap ??= new LazyExternalTypeDictionary();
                    return _externalTypeMap;
                }
            }
 
            public LazyProxyTypeDictionary ProxyTypeMap
            {
                [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
                get
                {
                    _proxyTypeMap ??= new LazyProxyTypeDictionary();
                    return _proxyTypeMap;
                }
            }
 
            public ExceptionDispatchInfo? CreationException
            {
                get => _creationException;
                set => _creationException = value;
            }
        }
 
        // See assemblynative.hpp for native version.
        public unsafe struct ProcessAttributesCallbackArg
        {
            public void* Utf8String1;
            public void* Utf8String2;
            public int StringLen1;
            public int StringLen2;
        }
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeMapLazyDictionary_ProcessAttributes")]
        private static unsafe partial void ProcessAttributes(
            QCallAssembly assembly,
            QCallTypeHandle groupType,
            delegate* unmanaged<CallbackContext*, ProcessAttributesCallbackArg*, Interop.BOOL> newExternalTypeEntry,
            delegate* unmanaged<CallbackContext*, ProcessAttributesCallbackArg*, Interop.BOOL> newProxyTypeEntry,
            CallbackContext* context);
 
        public ref struct Utf16SharedBuffer
        {
            private char[]? _backingArray;
            public Utf16SharedBuffer()
            {
                _backingArray = null;
                Buffer = default;
            }
 
            public Utf16SharedBuffer(char[] backingBuffer, int validLength)
            {
                _backingArray = backingBuffer;
                Buffer = new ReadOnlySpan<char>(backingBuffer, 0, validLength);
            }
 
            public ReadOnlySpan<char> Buffer { get; init; }
 
            public void Dispose()
            {
                if (_backingArray != null)
                {
                    ArrayPool<char>.Shared.Return(_backingArray);
                }
            }
        }
 
        private static void ConvertUtf8ToUtf16(ReadOnlySpan<byte> utf8TypeName, out Utf16SharedBuffer utf16Buffer)
        {
            // Use quick conservative estimate for small strings
            int needed = (utf8TypeName.Length < 1024)
                ? Encoding.UTF8.GetMaxCharCount(utf8TypeName.Length)
                : Encoding.UTF8.GetCharCount(utf8TypeName);
 
            char[] buffer = ArrayPool<char>.Shared.Rent(needed);
            int converted = Encoding.UTF8.GetChars(utf8TypeName, buffer);
            utf16Buffer = new Utf16SharedBuffer(buffer, converted);
        }
 
        [UnmanagedCallersOnly]
        private static unsafe Interop.BOOL NewExternalTypeEntry(CallbackContext* context, ProcessAttributesCallbackArg* arg)
        {
            Debug.Assert(context != null);
            Debug.Assert(arg != null);
            Debug.Assert(arg->Utf8String1 != null);
            Debug.Assert(arg->Utf8String2 != null);
 
            try
            {
                string externalTypeName = new((sbyte*)arg->Utf8String1, 0, arg->StringLen1, Encoding.UTF8);
                TypeNameUtf8 targetTypeName = new()
                {
                    Utf8TypeName = arg->Utf8String2,
                    Utf8TypeNameLen = arg->StringLen2
                };
                context->ExternalTypeMap.Add(externalTypeName, targetTypeName, context->CurrentAssembly);
            }
            catch (Exception ex)
            {
                context->CreationException = ExceptionDispatchInfo.Capture(ex);
                return Interop.BOOL.FALSE; // Stop processing.
            }
 
            return Interop.BOOL.TRUE; // Continue processing.
        }
 
        [UnmanagedCallersOnly]
        private static unsafe Interop.BOOL NewProxyTypeEntry(CallbackContext* context, ProcessAttributesCallbackArg* arg)
        {
            Debug.Assert(context != null);
            Debug.Assert(arg != null);
            Debug.Assert(arg->Utf8String1 != null);
            Debug.Assert(arg->Utf8String2 != null);
 
            Utf16SharedBuffer sourceTypeBuffer = new();
            try
            {
                ConvertUtf8ToUtf16(new ReadOnlySpan<byte>(arg->Utf8String1, arg->StringLen1), out sourceTypeBuffer);
                TypeName parsedSource = TypeNameParser.Parse(sourceTypeBuffer.Buffer, throwOnError: true)!;
 
                TypeNameUtf8 sourceTypeName = new()
                {
                    Utf8TypeName = arg->Utf8String1,
                    Utf8TypeNameLen = arg->StringLen1
                };
                TypeNameUtf8 proxyTypeName = new()
                {
                    Utf8TypeName = arg->Utf8String2,
                    Utf8TypeNameLen = arg->StringLen2
                };
                context->ProxyTypeMap.Add(parsedSource, sourceTypeName, proxyTypeName, context->CurrentAssembly);
            }
            catch (Exception ex)
            {
                context->CreationException = ExceptionDispatchInfo.Capture(ex);
                return Interop.BOOL.FALSE; // Stop processing.
            }
            finally
            {
                sourceTypeBuffer.Dispose();
            }
 
            return Interop.BOOL.TRUE; // Continue processing.
        }
 
        private static unsafe CallbackContext CreateMaps(
            RuntimeType groupType,
            delegate* unmanaged<CallbackContext*, ProcessAttributesCallbackArg*, Interop.BOOL> newExternalTypeEntry,
            delegate* unmanaged<CallbackContext*, ProcessAttributesCallbackArg*, Interop.BOOL> newProxyTypeEntry)
        {
            RuntimeAssembly? startingAssembly = (RuntimeAssembly?)Assembly.GetEntryAssembly();
            if (startingAssembly is null)
            {
                throw new InvalidOperationException(SR.InvalidOperation_TypeMapMissingEntryAssembly);
            }
 
            CallbackContext context;
            ProcessAttributes(
                new QCallAssembly(ref startingAssembly),
                new QCallTypeHandle(ref groupType),
                newExternalTypeEntry,
                newProxyTypeEntry,
                &context);
 
            // If an exception was thrown during the processing of
            // the attributes, rethrow it.
            context.CreationException?.Throw();
 
            return context;
        }
 
        public static IReadOnlyDictionary<string, Type> CreateExternalTypeMap(RuntimeType groupType)
        {
            unsafe
            {
                return CreateMaps(
                    groupType,
                    &NewExternalTypeEntry,
                    null).ExternalTypeMap;
            }
        }
 
        public static IReadOnlyDictionary<Type, Type> CreateProxyTypeMap(RuntimeType groupType)
        {
            unsafe
            {
                return CreateMaps(
                    groupType,
                    null,
                    &NewProxyTypeEntry).ProxyTypeMap;
            }
        }
 
        private abstract class LazyTypeLoadDictionary<TKey> : IReadOnlyDictionary<TKey, Type> where TKey : notnull
        {
            protected abstract bool TryGetOrLoadType(TKey key, [NotNullWhen(true)] out Type? type);
 
            public Type this[TKey key]
            {
                get
                {
                    if (!TryGetOrLoadType(key, out Type? type))
                    {
                        ThrowHelper.ThrowKeyNotFoundException(key);
                    }
 
                    return type;
                }
            }
 
            public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out Type value) => TryGetOrLoadType(key, out value);
 
            // Not supported to avoid exposing TypeMap entries in a manner that
            // would violate invariants the Trimmer is attempting to enforce.
            public IEnumerable<TKey> Keys => throw new NotSupportedException();
            public IEnumerable<Type> Values => throw new NotSupportedException();
            public int Count => throw new NotSupportedException();
            public bool ContainsKey(TKey key) => throw new NotSupportedException();
            public IEnumerator<KeyValuePair<TKey, Type>> GetEnumerator() => throw new NotSupportedException();
            IEnumerator IEnumerable.GetEnumerator() => throw new NotSupportedException();
        }
 
        private unsafe struct TypeNameUtf8
        {
            public required void* Utf8TypeName { get; init; }
            public required int Utf8TypeNameLen { get; init; }
        }
 
        [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
        private sealed class DelayedType
        {
            private TypeNameUtf8 _typeNameUtf8;
            private RuntimeAssembly _fallbackAssembly;
 
            private Type? _type;
 
            public DelayedType(TypeNameUtf8 typeNameUtf8, RuntimeAssembly fallbackAssembly)
            {
                _typeNameUtf8 = typeNameUtf8;
                _fallbackAssembly = fallbackAssembly;
                _type = null;
            }
 
            public unsafe Type GetOrLoadType()
            {
                if (_type is null)
                {
                    Utf16SharedBuffer typeNameBuffer = new();
                    try
                    {
                        ConvertUtf8ToUtf16(new ReadOnlySpan<byte>(_typeNameUtf8.Utf8TypeName, _typeNameUtf8.Utf8TypeNameLen), out typeNameBuffer);
                        _type = TypeNameResolver.GetTypeHelper(
                            typeNameBuffer.Buffer,
                            _fallbackAssembly,
                            throwOnError: true,
                            requireAssemblyQualifiedName: false)!;
                    }
                    finally
                    {
                        typeNameBuffer.Dispose();
                    }
                }
                return _type;
            }
        }
 
        [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
        private sealed class LazyExternalTypeDictionary : LazyTypeLoadDictionary<string>
        {
            private static int ComputeHashCode(string key) => key.GetHashCode();
 
            private readonly Dictionary<int, DelayedType> _lazyData = new();
 
            protected override bool TryGetOrLoadType(string key, [NotNullWhen(true)] out Type? type)
            {
                int hash = ComputeHashCode(key);
                if (!_lazyData.TryGetValue(hash, out DelayedType? value))
                {
                    type = null;
                    return false;
                }
 
                type = value.GetOrLoadType();
                return true;
            }
 
            public void Add(string key, TypeNameUtf8 targetType, RuntimeAssembly fallbackAssembly)
            {
                int hash = ComputeHashCode(key);
                if (_lazyData.ContainsKey(hash))
                {
                    ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
                }
 
                _lazyData.Add(hash, new DelayedType(targetType, fallbackAssembly));
            }
        }
 
        [RequiresUnreferencedCode("Lazy TypeMap isn't supported for Trimmer scenarios")]
        private sealed class LazyProxyTypeDictionary : LazyTypeLoadDictionary<Type>
        {
            private static int ComputeHashCode(RuntimeType key)
                => Internal.VersionResilientHashCode.TypeHashCode(key);
 
            private static int ComputeHashCode(TypeName key)
                => Internal.VersionResilientHashCode.TypeHashCode(key);
 
            private struct SourceProxyPair
            {
                public required DelayedType Source { get; init; }
                public required DelayedType Proxy { get; init; }
            }
 
            private sealed class DelayedTypeCollection
            {
                public required SourceProxyPair First { get; init; }
                public List<SourceProxyPair>? Others { get; private set; }
 
                public void Add(SourceProxyPair newEntryMaybe)
                {
                    Others ??= new List<SourceProxyPair>();
                    Others.Add(newEntryMaybe);
                }
            }
 
            private readonly Dictionary<int, DelayedTypeCollection> _lazyData = new();
 
            protected override bool TryGetOrLoadType(Type key, [NotNullWhen(true)] out Type? type)
            {
                if (key is not RuntimeType rtType)
                {
                    throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(key));
                }
 
                int hash = ComputeHashCode(rtType);
 
                if (_lazyData.TryGetValue(hash, out DelayedTypeCollection? value))
                {
                    // The common case, no duplicate mappings.
                    if (value.First.Source.GetOrLoadType() == key)
                    {
                        type = value.First.Proxy.GetOrLoadType();
                        return true;
                    }
                    else if (value.Others != null)
                    {
                        // Common case failed, look at alternate mappings.
                        foreach (SourceProxyPair entry in value.Others)
                        {
                            if (entry.Source.GetOrLoadType() == key)
                            {
                                type = entry.Proxy.GetOrLoadType();
                                return true;
                            }
                        }
                    }
                }
                type = null;
                return false;
            }
 
            public void Add(
                TypeName parsedSourceTypeName,
                TypeNameUtf8 sourceTypeName,
                TypeNameUtf8 proxyTypeName,
                RuntimeAssembly fallbackAssembly)
            {
                int hash = ComputeHashCode(parsedSourceTypeName);
 
                SourceProxyPair newEntryMaybe = new()
                {
                    Source = new DelayedType(sourceTypeName, fallbackAssembly),
                    Proxy = new DelayedType(proxyTypeName, fallbackAssembly)
                };
 
                if (!_lazyData.TryGetValue(hash, out DelayedTypeCollection? types))
                {
                    types = new DelayedTypeCollection() { First = newEntryMaybe };
                    _lazyData.Add(hash, types);
                }
                else
                {
                    types.Add(newEntryMaybe);
                }
            }
        }
    }
}