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

TypeName parsing API #100094

Merged
merged 52 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
22d131e
TypeNameParser first ref and configurable input validation
adamsitnik Jan 23, 2024
5de9526
assembly name parsing
adamsitnik Jan 24, 2024
6dbcd59
initial generic type info parsing
adamsitnik Jan 25, 2024
6f1f161
decorator parsing
adamsitnik Jan 26, 2024
4ec4ea5
Handle single dimensional array indexing
adamsitnik Jan 29, 2024
f5a8716
implement TypeName.GetType
adamsitnik Jan 29, 2024
52cb351
nested types support
adamsitnik Jan 30, 2024
60ad6f0
support ignore case
adamsitnik Jan 31, 2024
b5349bd
integrate with System.Private.CoreLib:
adamsitnik Jan 31, 2024
2dbd091
integrate with System.Private.CoreLib:
adamsitnik Feb 1, 2024
bd78637
integrate with System.Private.CoreLib for Mono and clr tools, improve…
adamsitnik Feb 2, 2024
8fda94a
build fix 1/n
adamsitnik Feb 2, 2024
745e7bb
make TypeNameParser internal, extend TypeName with Parse and TryParse…
adamsitnik Feb 7, 2024
ee43e71
introduce TotalComplexity
adamsitnik Feb 7, 2024
a3a7f26
introduce FullName, so we have Name, FullName and AssemblyQualifiedNa…
adamsitnik Feb 7, 2024
a2296d0
back tick error handling
adamsitnik Feb 9, 2024
4c8a6f7
move helper methods to a standalone helper type, include it as a link…
adamsitnik Feb 9, 2024
eadf970
increase test coverage, improve edge case handling
adamsitnik Feb 12, 2024
abf7543
sample SerializationBinder that uses the new APIs
adamsitnik Feb 12, 2024
281c4f3
cover more serialization binder scenarios with the tests to ensure th…
adamsitnik Feb 13, 2024
dc58cce
strict mode parsing: type names
adamsitnik Feb 19, 2024
b947977
strict mode parsing: assembly names
adamsitnik Feb 20, 2024
4c9a7d5
Merge remote-tracking branch 'upstream/main' into typeNameParser
adamsitnik Feb 22, 2024
c63f3a8
fix the build and apply some design changes
adamsitnik Feb 22, 2024
83cdd1b
add escaping support
adamsitnik Feb 22, 2024
23ad44f
fix the last failing tests, increase test coverage, fix the perf, fix…
adamsitnik Feb 23, 2024
96b04f4
Merge remote-tracking branch 'upstream/main' into typeNameParser
adamsitnik Mar 19, 2024
c8014fd
apply changes based on the 1st API Design Review
adamsitnik Mar 19, 2024
1c76366
apply changes based on the final API Design Review
adamsitnik Mar 20, 2024
8cd205a
solve the TODOs
adamsitnik Mar 20, 2024
ae514ec
implement IEquatable, add missing exception messages, set default Max…
adamsitnik Mar 21, 2024
c7c67c8
address code review feedback, make some tests conditional, remove inv…
adamsitnik Mar 22, 2024
87804e9
Apply suggestions from code review
adamsitnik Mar 22, 2024
97aad27
supress IL3050:RequiresDynamicCode
adamsitnik Mar 22, 2024
f940723
remove everything related to strict parsing (it will come back in a s…
adamsitnik Mar 22, 2024
b1ed250
remove special handling for Array.MaxLength-many generic args
adamsitnik Mar 22, 2024
90a5582
remove the unused property, use the new names for non-public APIs as …
adamsitnik Mar 22, 2024
9a5d01b
make the allocations happen when they are actually needed for the fir…
adamsitnik Apr 2, 2024
cfb2216
don't pre-allocate full names for all declaring types, just store the…
adamsitnik Apr 3, 2024
d63365f
build fix
adamsitnik Apr 3, 2024
7d685c8
Merge remote-tracking branch 'upstream/main' into typeNameParser
adamsitnik Apr 9, 2024
04731fb
address part of the code review feedback
adamsitnik Apr 9, 2024
113a87f
fix the build
adamsitnik Apr 11, 2024
7005164
AssemblyNameInfo
adamsitnik Apr 15, 2024
cd8a9c2
don't enforce the back tick convention
adamsitnik Apr 16, 2024
1143a3e
Merge remote-tracking branch 'upstream/main' into typeNameParser
adamsitnik Apr 17, 2024
afdbc5f
address API and code review feedback:
adamsitnik Apr 17, 2024
413be9e
minor tweaks after reading the code again
adamsitnik Apr 17, 2024
b424d76
try to improve the escaping handling
adamsitnik Apr 18, 2024
da203c0
address code review feedback:
adamsitnik Apr 23, 2024
7979e27
address code review feedback:
adamsitnik Apr 24, 2024
22de761
Apply suggestions from code review
adamsitnik Apr 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.AssemblyNameInfo? assemblyNameIfAny, string fullEscapedName)
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
{
Assembly? assembly;

if (assemblyNameIfAny is not null)
{
assembly = ResolveAssembly(assemblyNameIfAny);
assembly = ResolveAssembly(assemblyNameIfAny.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, fullEscapedName);
}

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, string fullEscapedName)
{
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, fullEscapedName);
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, fullEscapedName, (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);
}
}