Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify managed runtime type name parsers #83484

Merged
merged 9 commits into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeModule.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimeParameterInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimePropertyInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\TypeNameParser.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Resources\ManifestBasedResourceGroveler.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
Expand Down Expand Up @@ -237,7 +238,6 @@
<Compile Include="$(BclSourcesRoot)\System\Type.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\TypedReference.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\TypeLoadException.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\TypeNameParser.cs" />
<Compile Include="$(BclSourcesRoot)\System\ValueType.cs" />
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,37 +190,65 @@ public override MethodInfo? EntryPoint
}
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetType", StringMarshalling = StringMarshalling.Utf16)]
private static partial void GetType(QCallAssembly assembly,
string name,
[MarshalAs(UnmanagedType.Bool)] bool throwOnError,
[MarshalAs(UnmanagedType.Bool)] bool ignoreCase,
ObjectHandleOnStack type,
ObjectHandleOnStack keepAlive,
ObjectHandleOnStack assemblyLoadContext);
// For case-sensitive lookups, marshal the strings directly to Utf8 to avoid unnecessary string copies.
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetTypeCore", StringMarshalling = StringMarshalling.Utf8)]
private static partial void GetTypeCore(QCallAssembly assembly,
string typeName,
ReadOnlySpan<string> nestedTypeNames,
int nestedTypeNamesLength,
ObjectHandleOnStack retType);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetTypeCoreIgnoreCase", StringMarshalling = StringMarshalling.Utf16)]
private static partial void GetTypeCoreIgnoreCase(QCallAssembly assembly,
string typeName,
ReadOnlySpan<string> nestedTypeNames,
int nestedTypeNamesLength,
ObjectHandleOnStack retType);

internal Type? GetTypeCore(string typeName, ReadOnlySpan<string> nestedTypeNames, bool throwOnError, bool ignoreCase)
{
RuntimeAssembly runtimeAssembly = this;
Type? type = null;

try
{
if (ignoreCase)
{
GetTypeCoreIgnoreCase(new QCallAssembly(ref runtimeAssembly),
typeName,
nestedTypeNames,
nestedTypeNames.Length,
ObjectHandleOnStack.Create(ref type));
}
else
{
GetTypeCore(new QCallAssembly(ref runtimeAssembly),
typeName,
nestedTypeNames,
nestedTypeNames.Length,
ObjectHandleOnStack.Create(ref type));
}
}
catch (FileNotFoundException) when (!throwOnError)
{
return null;
}

if (type == null && throwOnError)
throw new TypeLoadException(SR.Format(SR.ClassLoad_General /* TypeLoad_TypeNotFoundInAssembly */, typeName, FullName));

return type;
}

[RequiresUnreferencedCode("Types might be removed")]
public override Type? GetType(
string name, // throw on null strings regardless of the value of "throwOnError"
bool throwOnError, bool ignoreCase)
{
ArgumentNullException.ThrowIfNull(name);

RuntimeType? type = null;
object? keepAlive = null;
AssemblyLoadContext? assemblyLoadContextStack = AssemblyLoadContext.CurrentContextualReflectionContext;
ArgumentException.ThrowIfNullOrEmpty(name);

RuntimeAssembly runtimeAssembly = this;
GetType(new QCallAssembly(ref runtimeAssembly),
name,
throwOnError,
ignoreCase,
ObjectHandleOnStack.Create(ref type),
ObjectHandleOnStack.Create(ref keepAlive),
ObjectHandleOnStack.Create(ref assemblyLoadContextStack));
GC.KeepAlive(keepAlive);

return type;
return TypeNameParser.GetType(name, topLevelAssembly: this,
throwOnError: throwOnError, ignoreCase: ignoreCase);
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "AssemblyNative_GetExportedTypes")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,14 +420,10 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont
string className, // throw on null strings regardless of the value of "throwOnError"
bool throwOnError, bool ignoreCase)
{
ArgumentNullException.ThrowIfNull(className);
ArgumentException.ThrowIfNullOrEmpty(className);

RuntimeType? retType = null;
object? keepAlive = null;
RuntimeModule thisAsLocal = this;
GetType(new QCallModule(ref thisAsLocal), className, throwOnError, ignoreCase, ObjectHandleOnStack.Create(ref retType), ObjectHandleOnStack.Create(ref keepAlive));
GC.KeepAlive(keepAlive);
return retType;
return TypeNameParser.GetType(className, topLevelAssembly: Assembly,
throwOnError: throwOnError, ignoreCase: ignoreCase);
}

[RequiresAssemblyFiles(UnknownStringMessageInRAF)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// 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.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading;

namespace System.Reflection
{
internal unsafe ref partial struct TypeNameParser
{
private ReadOnlySpan<char> _input;
private int _index;
private int _errorIndex; // Position for error reporting

private Func<AssemblyName, Assembly?>? _assemblyResolver;
private Func<Assembly?, string, bool, Type?>? _typeResolver;
private bool _throwOnError;
private bool _ignoreCase;
jkotas marked this conversation as resolved.
Show resolved Hide resolved
private bool _extensibleParser;
private Assembly? _requestingAssembly;
private Assembly? _topLevelAssembly;

[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;
}

return new TypeNameParser(typeName)
{
_assemblyResolver = assemblyResolver,
_typeResolver = typeResolver,
_throwOnError = throwOnError,
_ignoreCase = ignoreCase,
_extensibleParser = extensibleParser,
_requestingAssembly = requestingAssembly
}.Parse();
}

[RequiresUnreferencedCode("The type might be removed")]
internal static Type? GetType(
string typeName,
bool throwOnError,
bool ignoreCase,
Assembly topLevelAssembly)
{
return new TypeNameParser(typeName)
{
_throwOnError = throwOnError,
_ignoreCase = ignoreCase,
_topLevelAssembly = topLevelAssembly,
_requestingAssembly = topLevelAssembly
}.Parse();
}

private bool CheckTopLevelAssemblyQualifiedName()
{
if (_topLevelAssembly is not null)
{
if (_throwOnError)
throw new ArgumentException(SR.Argument_AssemblyGetTypeCannotSpecifyAssembly);
return false;
}
return true;
}

private Assembly? ResolveAssembly(string assemblyName)
{
Assembly? assembly;
if (_assemblyResolver is not null)
{
assembly = _assemblyResolver(new AssemblyName(assemblyName));
if (assembly is null && _throwOnError)
{
throw new FileNotFoundException(SR.Format(SR.FileNotFound_ResolveAssembly, assemblyName));
}
}
else
{
assembly = RuntimeAssembly.InternalLoad(new AssemblyName(assemblyName), ref Unsafe.NullRef<StackCrawlMark>(), AssemblyLoadContext.CurrentContextualReflectionContext,
requestingAssembly: (RuntimeAssembly?)_requestingAssembly, throwOnFileNotFound: _throwOnError);
}
return assembly;
}

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
Justification = "TypeNameParser.GetType is marked as RequiresUnreferencedCode.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
Justification = "TypeNameParser.GetType is marked as RequiresUnreferencedCode.")]
private Type? GetType(string typeName, ReadOnlySpan<string> nestedTypeNames, string? assemblyNameIfAny)
{
Assembly? assembly;

if (assemblyNameIfAny is not null)
{
assembly = ResolveAssembly(assemblyNameIfAny);
if (assembly is null)
return null;
}
else
{
assembly = _topLevelAssembly;
}

Type? type;

// Resolve the top level type.
if (_typeResolver is not null)
{
string escapedTypeName = EscapeTypeName(typeName);

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));
}
return null;
}
}
else
{
if (assembly is null)
{
return GetTypeFromDefaultAssemblies(typeName, nestedTypeNames);
}

if (assembly is RuntimeAssembly runtimeAssembly)
{
// Compat: Non-extensible parser allows ambiguous matches with ignore case lookup
if (!_extensibleParser || !_ignoreCase)
{
return runtimeAssembly.GetTypeCore(typeName, nestedTypeNames, throwOnError: _throwOnError, ignoreCase: _ignoreCase);
}
type = runtimeAssembly.GetTypeCore(typeName, default, throwOnError: _throwOnError, ignoreCase: _ignoreCase);
}
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(EscapeTypeName(typeName), 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));
}
return null;
}
}

return type;
}

private Type? GetTypeFromDefaultAssemblies(string typeName, ReadOnlySpan<string> nestedTypeNames)
{
RuntimeAssembly? requestingAssembly = (RuntimeAssembly?)_requestingAssembly;
if (requestingAssembly is not null)
{
Type? type = ((RuntimeAssembly)requestingAssembly).GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
if (type is not null)
return type;
}

RuntimeAssembly coreLib = (RuntimeAssembly)typeof(object).Assembly;
if (requestingAssembly != coreLib)
{
Type? type = ((RuntimeAssembly)coreLib).GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
if (type is not null)
return type;
}

RuntimeAssembly? resolvedAssembly = AssemblyLoadContext.OnTypeResolve(requestingAssembly, EscapeTypeName(typeName, nestedTypeNames));
if (resolvedAssembly is not null)
{
Type? type = resolvedAssembly.GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
if (type is not null)
return type;
}

if (_throwOnError)
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveTypeFromAssembly, EscapeTypeName(typeName), (requestingAssembly ?? coreLib).FullName));

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -544,12 +544,6 @@ private static partial void GetTypeByName(string name, [MarshalAs(UnmanagedType.
ObjectHandleOnStack assemblyLoadContext,
ObjectHandleOnStack type, ObjectHandleOnStack keepalive);

// Wrapper function to reduce the need for ifdefs.
internal static RuntimeType? GetTypeByName(string name, bool throwOnError, bool ignoreCase, ref StackCrawlMark stackMark)
{
return GetTypeByName(name, throwOnError, ignoreCase, ref stackMark, AssemblyLoadContext.CurrentContextualReflectionContext!);
}

internal static RuntimeType? GetTypeByName(string name, bool throwOnError, bool ignoreCase, ref StackCrawlMark stackMark,
AssemblyLoadContext assemblyLoadContext)
{
Expand Down
Loading