File: src\System\Reflection\TypeNameResolver.CoreCLR.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Reflection.Metadata;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading;
 
namespace System.Reflection
{
    internal partial struct TypeNameResolver
    {
        private Func<AssemblyName, Assembly?>? _assemblyResolver;
        private Func<Assembly?, string, bool, Type?>? _typeResolver;
        private bool _throwOnError;
        private bool _ignoreCase;
        private bool _extensibleParser;
        private bool _requireAssemblyQualifiedName;
        private bool _suppressContextualReflectionContext;
        private IntPtr _unsafeAccessorMethod;
        private Assembly? _requestingAssembly;
        private Assembly? _topLevelAssembly;
 
        private bool SupportsUnboundGenerics { get => _unsafeAccessorMethod != IntPtr.Zero; }
 
        [RequiresUnreferencedCode("The type might be removed")]
        internal static Type? GetType(
            string typeName,
            Assembly requestingAssembly,
            bool throwOnError = false,
            bool ignoreCase = false)
        {
            return GetType(typeName, assemblyResolver: null, typeResolver: null, requestingAssembly: requestingAssembly,
                throwOnError: throwOnError, ignoreCase: ignoreCase, extensibleParser: false);
        }
 
        [RequiresUnreferencedCode("The type might be removed")]
        internal static Type? GetType(
            string typeName,
            Func<AssemblyName, Assembly?>? assemblyResolver,
            Func<Assembly?, string, bool, Type?>? typeResolver,
            Assembly? requestingAssembly,
            bool throwOnError = false,
            bool ignoreCase = false,
            bool extensibleParser = true)
        {
            ArgumentNullException.ThrowIfNull(typeName);
 
            // Compat: Empty name throws TypeLoadException instead of
            // the natural ArgumentException
            if (typeName.Length == 0)
            {
                if (throwOnError)
                    throw new TypeLoadException(SR.Arg_TypeLoadNullStr);
                return null;
            }
 
            TypeName? parsed = TypeNameParser.Parse(typeName, throwOnError);
            if (parsed is null)
            {
                return null;
            }
 
            return new TypeNameResolver()
            {
                _assemblyResolver = assemblyResolver,
                _typeResolver = typeResolver,
                _throwOnError = throwOnError,
                _ignoreCase = ignoreCase,
                _extensibleParser = extensibleParser,
                _requestingAssembly = requestingAssembly
            }.Resolve(parsed);
        }
 
        [RequiresUnreferencedCode("The type might be removed")]
        internal static Type? GetType(
            string typeName,
            bool throwOnError,
            bool ignoreCase,
            Assembly topLevelAssembly)
        {
            TypeName? parsed = TypeNameParser.Parse(typeName, throwOnError, new() { IsAssemblyGetType = true });
 
            if (parsed is null)
            {
                return null;
            }
            else if (parsed.AssemblyName is not null)
            {
                return throwOnError ? throw new ArgumentException(SR.Argument_AssemblyGetTypeCannotSpecifyAssembly) : null;
            }
 
            return new TypeNameResolver()
            {
                _throwOnError = throwOnError,
                _ignoreCase = ignoreCase,
                _topLevelAssembly = topLevelAssembly,
                _requestingAssembly = topLevelAssembly
            }.Resolve(parsed);
        }
 
        // Resolve type name referenced by a custom attribute metadata.
        // It uses the standard Type.GetType(typeName, throwOnError: true) algorithm with the following modifications:
        // - ContextualReflectionContext is not taken into account
        // - The dependency between the returned type and the requesting assembly is recorded for the purpose of
        // lifetime tracking of collectible types.
        internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName, RuntimeModule scope)
        {
            ArgumentException.ThrowIfNullOrEmpty(typeName);
 
            RuntimeAssembly requestingAssembly = scope.GetRuntimeAssembly();
 
            TypeName parsed = TypeName.Parse(typeName);
            RuntimeType? type = (RuntimeType?)new TypeNameResolver()
            {
                _throwOnError = true,
                _suppressContextualReflectionContext = true,
                _requestingAssembly = requestingAssembly
            }.Resolve(parsed);
 
            Debug.Assert(type != null);
 
            RuntimeTypeHandle.RegisterCollectibleTypeDependency(type, requestingAssembly);
 
            return type;
        }
 
        // Used by VM
        internal static unsafe RuntimeType? GetTypeHelper(char* pTypeName, RuntimeAssembly? requestingAssembly,
            bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod)
        {
            ReadOnlySpan<char> typeName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pTypeName);
            return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName, unsafeAccessorMethod);
        }
 
        internal static RuntimeType? GetTypeHelper(ReadOnlySpan<char> typeName, RuntimeAssembly? requestingAssembly,
            bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod = 0)
        {
            // Compat: Empty name throws TypeLoadException instead of
            // the natural ArgumentException
            if (typeName.Length == 0)
            {
                if (throwOnError)
                    throw new TypeLoadException(SR.Arg_TypeLoadNullStr);
                return null;
            }
 
            TypeName? parsed = TypeNameParser.Parse(typeName, throwOnError);
            if (parsed is null)
            {
                return null;
            }
 
            RuntimeType? type = (RuntimeType?)new TypeNameResolver()
            {
                _requestingAssembly = requestingAssembly,
                _throwOnError = throwOnError,
                _suppressContextualReflectionContext = true,
                _requireAssemblyQualifiedName = requireAssemblyQualifiedName,
                _unsafeAccessorMethod = unsafeAccessorMethod,
            }.Resolve(parsed);
 
            if (type != null)
                RuntimeTypeHandle.RegisterCollectibleTypeDependency(type, requestingAssembly);
 
            return type;
        }
 
        private Assembly? ResolveAssembly(AssemblyName assemblyName)
        {
            Assembly? assembly;
            if (_assemblyResolver is not null)
            {
                assembly = _assemblyResolver(assemblyName);
                if (assembly is null && _throwOnError)
                {
                    throw new FileNotFoundException(SR.Format(SR.FileNotFound_ResolveAssembly, assemblyName));
                }
            }
            else
            {
                assembly = RuntimeAssembly.InternalLoad(assemblyName, ref Unsafe.NullRef<StackCrawlMark>(),
                    _suppressContextualReflectionContext ? null : AssemblyLoadContext.CurrentContextualReflectionContext,
                    requestingAssembly: (RuntimeAssembly?)_requestingAssembly, throwOnFileNotFound: _throwOnError);
            }
            return assembly;
        }
 
        [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "UnsafeAccessors_ResolveGenericParamToTypeHandle")]
        private static partial IntPtr ResolveGenericParamToTypeHandle(IntPtr unsafeAccessorMethod, [MarshalAs(UnmanagedType.Bool)] bool isMethodParam, uint paramIndex);
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode.")]
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
            Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode.")]
        private Type? GetType(string escapedTypeName, ReadOnlySpan<string> nestedTypeNames, TypeName parsedName)
        {
            Assembly? assembly;
 
            if (parsedName.AssemblyName is not null)
            {
                assembly = ResolveAssembly(parsedName.AssemblyName.ToAssemblyName());
                if (assembly is null)
                    return null;
            }
            else
            {
                assembly = _topLevelAssembly;
            }
 
            Type? type;
 
            // Resolve the top level type.
            if (_typeResolver is not null)
            {
                type = _typeResolver(assembly, escapedTypeName, _ignoreCase);
 
                if (type is null)
                {
                    if (_throwOnError)
                    {
                        throw new TypeLoadException(assembly is null ?
                            SR.Format(SR.TypeLoad_ResolveType, escapedTypeName) :
                            SR.Format(SR.TypeLoad_ResolveTypeFromAssembly, escapedTypeName, assembly.FullName),
                            typeName: escapedTypeName);
                    }
                    return null;
                }
            }
            else
            {
                if (assembly is null)
                {
                    if (SupportsUnboundGenerics
                        && !string.IsNullOrEmpty(escapedTypeName)
                        && escapedTypeName[0] == '!')
                    {
                        Debug.Assert(_throwOnError); // Unbound generic support currently always throws.
 
                        // Parse the type as an unbound generic parameter. Following the common VAR/MVAR IL syntax:
                        //  !<Number> - Represents a zero-based index into the type's generic parameters.
                        //  !!<Number> - Represents a zero-based index into the method's generic parameters.
 
                        // Confirm we have at least one more character
                        if (escapedTypeName.Length == 1)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
                        }
 
                        // At this point we expect either another '!' and then a number or a number.
                        bool isMethodParam = escapedTypeName[1] == '!';
                        ReadOnlySpan<char> toParse = isMethodParam
                            ? escapedTypeName.AsSpan(2)  // Skip over "!!"
                            : escapedTypeName.AsSpan(1); // Skip over "!"
                        if (!uint.TryParse(toParse, NumberStyles.None, null, out uint paramIndex))
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
                        }
 
                        Debug.Assert(_unsafeAccessorMethod != IntPtr.Zero);
                        IntPtr typeHandle = ResolveGenericParamToTypeHandle(_unsafeAccessorMethod, isMethodParam, paramIndex);
                        if (typeHandle == IntPtr.Zero)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
                        }
 
                        return RuntimeTypeHandle.GetRuntimeTypeFromHandle(typeHandle);
                    }
 
                    if (_requireAssemblyQualifiedName)
                    {
                        if (_throwOnError)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
                        }
                        return null;
                    }
                    return GetTypeFromDefaultAssemblies(TypeName.Unescape(escapedTypeName), nestedTypeNames, parsedName);
                }
 
                if (assembly is RuntimeAssembly runtimeAssembly)
                {
                    // Compat: Non-extensible parser allows ambiguous matches with ignore case lookup
                    bool useReflectionForNestedTypes = _extensibleParser && _ignoreCase;
 
                    type = runtimeAssembly.GetTypeCore(TypeName.Unescape(escapedTypeName), useReflectionForNestedTypes ? default : nestedTypeNames,
                        throwOnFileNotFound: _throwOnError, ignoreCase: _ignoreCase);
 
                    if (type is null)
                    {
                        if (_throwOnError)
                        {
                            throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveTypeFromAssembly, parsedName.FullName, runtimeAssembly.FullName),
                                typeName: parsedName.FullName);
                        }
                        return null;
                    }
 
                    if (!useReflectionForNestedTypes)
                    {
                        return type;
                    }
                }
                else
                {
                    // This is a third-party Assembly object. Emulate GetTypeCore() by calling the public GetType()
                    // method. This is wasteful because it'll probably reparse a type string that we've already parsed
                    // but it can't be helped.
                    type = assembly.GetType(escapedTypeName, throwOnError: _throwOnError, ignoreCase: _ignoreCase);
                }
 
                if (type is null)
                    return null;
            }
 
            for (int i = 0; i < nestedTypeNames.Length; i++)
            {
                BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public;
                if (_ignoreCase)
                    bindingFlags |= BindingFlags.IgnoreCase;
 
                type = type.GetNestedType(nestedTypeNames[i], bindingFlags);
 
                if (type is null)
                {
                    if (_throwOnError)
                    {
                        throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveNestedType,
                            nestedTypeNames[i], (i > 0) ? nestedTypeNames[i - 1] : TypeName.Unescape(escapedTypeName)),
                            typeName: parsedName.FullName);
                    }
                    return null;
                }
            }
 
            return type;
        }
 
        private Type? GetTypeFromDefaultAssemblies(string typeName, ReadOnlySpan<string> nestedTypeNames, TypeName parsedName)
        {
            RuntimeAssembly? requestingAssembly = (RuntimeAssembly?)_requestingAssembly;
            if (requestingAssembly is not null)
            {
                Type? type = requestingAssembly.GetTypeCore(typeName, nestedTypeNames, throwOnFileNotFound: false, ignoreCase: _ignoreCase);
                if (type is not null)
                    return type;
            }
 
            RuntimeAssembly coreLib = (RuntimeAssembly)typeof(object).Assembly;
            if (requestingAssembly != coreLib)
            {
                Type? type = coreLib.GetTypeCore(typeName, nestedTypeNames, throwOnFileNotFound: false, ignoreCase: _ignoreCase);
                if (type is not null)
                    return type;
            }
 
            RuntimeAssembly? resolvedAssembly = AssemblyLoadContext.OnTypeResolve(requestingAssembly, parsedName.FullName);
            if (resolvedAssembly is not null)
            {
                Type? type = resolvedAssembly.GetTypeCore(typeName, nestedTypeNames, throwOnFileNotFound: false, ignoreCase: _ignoreCase);
                if (type is not null)
                    return type;
            }
 
            if (_throwOnError)
            {
                throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveTypeFromAssembly, parsedName.FullName, (requestingAssembly ?? coreLib).FullName),
                    typeName: parsedName.FullName);
            }
 
            return null;
        }
    }
}