Skip to content

Commit

Permalink
TypeName parsing API (dotnet#100094)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
2 people authored and matouskozak committed Apr 30, 2024
1 parent 4b5b0af commit 2d30f93
Show file tree
Hide file tree
Showing 31 changed files with 3,599 additions and 1,300 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Text;
using System.Threading;

namespace System.Reflection
Expand Down Expand Up @@ -57,15 +55,21 @@ internal partial struct TypeNameParser
return null;
}

return new TypeNameParser(typeName)
Metadata.TypeName? parsed = Metadata.TypeNameParser.Parse(typeName, throwOnError: throwOnError);
if (parsed is null)
{
return null;
}

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

[RequiresUnreferencedCode("The type might be removed")]
Expand All @@ -75,13 +79,24 @@ internal partial struct TypeNameParser
bool ignoreCase,
Assembly topLevelAssembly)
{
return new TypeNameParser(typeName)
Metadata.TypeName? parsed = Metadata.TypeNameParser.Parse(typeName, throwOnError);

if (parsed is null)
{
return null;
}
else if (topLevelAssembly is not null && parsed.AssemblyName is not null)
{
return throwOnError ? throw new ArgumentException(SR.Argument_AssemblyGetTypeCannotSpecifyAssembly) : null;
}

return new TypeNameParser()
{
_throwOnError = throwOnError,
_ignoreCase = ignoreCase,
_topLevelAssembly = topLevelAssembly,
_requestingAssembly = topLevelAssembly
}.Parse();
}.Resolve(parsed);
}

// Resolve type name referenced by a custom attribute metadata.
Expand All @@ -95,12 +110,13 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,

RuntimeAssembly requestingAssembly = scope.GetRuntimeAssembly();

RuntimeType? type = (RuntimeType?)new TypeNameParser(typeName)
Metadata.TypeName parsed = Metadata.TypeName.Parse(typeName);
RuntimeType? type = (RuntimeType?)new TypeNameParser()
{
_throwOnError = true,
_suppressContextualReflectionContext = true,
_requestingAssembly = requestingAssembly
}.Parse();
}.Resolve(parsed);

Debug.Assert(type != null);

Expand All @@ -124,45 +140,40 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
return null;
}

RuntimeType? type = (RuntimeType?)new TypeNameParser(typeName)
Metadata.TypeName? parsed = Metadata.TypeNameParser.Parse(typeName, throwOnError);
if (parsed is null)
{
return null;
}

RuntimeType? type = (RuntimeType?)new TypeNameParser()
{
_requestingAssembly = requestingAssembly,
_throwOnError = throwOnError,
_suppressContextualReflectionContext = true,
_requireAssemblyQualifiedName = requireAssemblyQualifiedName,
}.Parse();
}.Resolve(parsed);

if (type != null)
RuntimeTypeHandle.RegisterCollectibleTypeDependency(type, requestingAssembly);

return type;
}

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)
private Assembly? ResolveAssembly(AssemblyName assemblyName)
{
Assembly? assembly;
if (_assemblyResolver is not null)
{
assembly = _assemblyResolver(new AssemblyName(assemblyName));
assembly = _assemblyResolver(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>(),
assembly = RuntimeAssembly.InternalLoad(assemblyName, ref Unsafe.NullRef<StackCrawlMark>(),
_suppressContextualReflectionContext ? null : AssemblyLoadContext.CurrentContextualReflectionContext,
requestingAssembly: (RuntimeAssembly?)_requestingAssembly, throwOnFileNotFound: _throwOnError);
}
Expand All @@ -173,13 +184,14 @@ private bool CheckTopLevelAssemblyQualifiedName()
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)
private Type? GetType(string escapedTypeName, // For nested types, it's Name. For other types it's FullName
ReadOnlySpan<string> nestedTypeNames, Metadata.TypeName parsedName)
{
Assembly? assembly;

if (assemblyNameIfAny is not null)
if (parsedName.AssemblyName is not null)
{
assembly = ResolveAssembly(assemblyNameIfAny);
assembly = ResolveAssembly(parsedName.AssemblyName.ToAssemblyName());
if (assembly is null)
return null;
}
Expand All @@ -193,8 +205,6 @@ private bool CheckTopLevelAssemblyQualifiedName()
// Resolve the top level type.
if (_typeResolver is not null)
{
string escapedTypeName = EscapeTypeName(typeName);

type = _typeResolver(assembly, escapedTypeName, _ignoreCase);

if (type is null)
Expand All @@ -216,28 +226,29 @@ private bool CheckTopLevelAssemblyQualifiedName()
{
if (_throwOnError)
{
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, EscapeTypeName(typeName)));
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName));
}
return null;
}
return GetTypeFromDefaultAssemblies(typeName, nestedTypeNames);
return GetTypeFromDefaultAssemblies(UnescapeTypeName(escapedTypeName), nestedTypeNames, parsedName);
}

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

if (type is null)
Expand All @@ -257,7 +268,7 @@ private bool CheckTopLevelAssemblyQualifiedName()
if (_throwOnError)
{
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveNestedType,
nestedTypeNames[i], (i > 0) ? nestedTypeNames[i - 1] : typeName));
nestedTypeNames[i], (i > 0) ? nestedTypeNames[i - 1] : escapedTypeName));
}
return null;
}
Expand All @@ -266,25 +277,25 @@ private bool CheckTopLevelAssemblyQualifiedName()
return type;
}

private Type? GetTypeFromDefaultAssemblies(string typeName, ReadOnlySpan<string> nestedTypeNames)
private Type? GetTypeFromDefaultAssemblies(string typeName, ReadOnlySpan<string> nestedTypeNames, Metadata.TypeName parsedName)
{
RuntimeAssembly? requestingAssembly = (RuntimeAssembly?)_requestingAssembly;
if (requestingAssembly is not null)
{
Type? type = ((RuntimeAssembly)requestingAssembly).GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
Type? type = 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);
Type? type = coreLib.GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
if (type is not null)
return type;
}

RuntimeAssembly? resolvedAssembly = AssemblyLoadContext.OnTypeResolve(requestingAssembly, EscapeTypeName(typeName, nestedTypeNames));
RuntimeAssembly? resolvedAssembly = AssemblyLoadContext.OnTypeResolve(requestingAssembly, parsedName.FullName);
if (resolvedAssembly is not null)
{
Type? type = resolvedAssembly.GetTypeCore(typeName, nestedTypeNames, throwOnError: false, ignoreCase: _ignoreCase);
Expand All @@ -293,7 +304,7 @@ private bool CheckTopLevelAssemblyQualifiedName()
}

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

return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using StackCrawlMark = System.Threading.StackCrawlMark;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Metadata;

namespace System.Reflection
{
Expand Down Expand Up @@ -129,6 +130,11 @@ public AssemblyName ToAssemblyName()
return assemblyName;
}

internal static RuntimeAssemblyName FromAssemblyNameInfo(AssemblyNameInfo source)
{
return new(source.Name, source.Version, source.CultureName, source._flags, source.PublicKeyOrToken);
}

//
// Copies a RuntimeAssemblyName into a freshly allocated AssemblyName with no data aliasing to any other object.
//
Expand All @@ -142,10 +148,10 @@ public void CopyToAssemblyName(AssemblyName blank)

// Our "Flags" contain both the classic flags and the ProcessorArchitecture + ContentType bits. The public AssemblyName has separate properties for
// these. The setters for these properties quietly mask out any bits intended for the other one, so we needn't do that ourselves..
blank.Flags = ExtractAssemblyNameFlags(this.Flags);
blank.ContentType = ExtractAssemblyContentType(this.Flags);
blank.Flags = AssemblyNameInfo.ExtractAssemblyNameFlags(this.Flags);
blank.ContentType = AssemblyNameInfo.ExtractAssemblyContentType(this.Flags);
#pragma warning disable SYSLIB0037 // AssemblyName.ProcessorArchitecture is obsolete
blank.ProcessorArchitecture = ExtractProcessorArchitecture(this.Flags);
blank.ProcessorArchitecture = AssemblyNameInfo.ExtractProcessorArchitecture(this.Flags);
#pragma warning restore SYSLIB0037

if (this.PublicKeyOrToken != null)
Expand All @@ -168,17 +174,8 @@ public string FullName
get
{
byte[]? pkt = (0 != (Flags & AssemblyNameFlags.PublicKey)) ? AssemblyNameHelpers.ComputePublicKeyToken(PublicKeyOrToken) : PublicKeyOrToken;
return AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, pkt, ExtractAssemblyNameFlags(Flags), ExtractAssemblyContentType(Flags));
return AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, pkt, AssemblyNameInfo.ExtractAssemblyNameFlags(Flags), AssemblyNameInfo.ExtractAssemblyContentType(Flags));
}
}

private static AssemblyNameFlags ExtractAssemblyNameFlags(AssemblyNameFlags combinedFlags)
=> combinedFlags & unchecked((AssemblyNameFlags)0xFFFFF10F);

private static AssemblyContentType ExtractAssemblyContentType(AssemblyNameFlags flags)
=> (AssemblyContentType)((((int)flags) >> 9) & 0x7);

private static ProcessorArchitecture ExtractProcessorArchitecture(AssemblyNameFlags flags)
=> (ProcessorArchitecture)((((int)flags) >> 4) & 0x7);
}
}

0 comments on commit 2d30f93

Please sign in to comment.