Skip to content

Commit e2634eb

Browse files
Implementation for UnsafeAccessorTypeAttribute (#114881)
* CoreCLR support * Mono support (narrow) * NativeAOT support * Add linker support --------- Co-authored-by: Jeremy Koritzinsky <[email protected]>
1 parent 3104b88 commit e2634eb

File tree

34 files changed

+1922
-134
lines changed

34 files changed

+1922
-134
lines changed

src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
67
using System.IO;
78
using System.Reflection.Metadata;
89
using System.Runtime.CompilerServices;
@@ -21,9 +22,12 @@ internal partial struct TypeNameResolver
2122
private bool _extensibleParser;
2223
private bool _requireAssemblyQualifiedName;
2324
private bool _suppressContextualReflectionContext;
25+
private IntPtr _unsafeAccessorMethod;
2426
private Assembly? _requestingAssembly;
2527
private Assembly? _topLevelAssembly;
2628

29+
private bool SupportsUnboundGenerics { get => _unsafeAccessorMethod != IntPtr.Zero; }
30+
2731
[RequiresUnreferencedCode("The type might be removed")]
2832
internal static Type? GetType(
2933
string typeName,
@@ -128,14 +132,14 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
128132

129133
// Used by VM
130134
internal static unsafe RuntimeType? GetTypeHelper(char* pTypeName, RuntimeAssembly? requestingAssembly,
131-
bool throwOnError, bool requireAssemblyQualifiedName)
135+
bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod)
132136
{
133137
ReadOnlySpan<char> typeName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(pTypeName);
134-
return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName);
138+
return GetTypeHelper(typeName, requestingAssembly, throwOnError, requireAssemblyQualifiedName, unsafeAccessorMethod);
135139
}
136140

137141
internal static unsafe RuntimeType? GetTypeHelper(ReadOnlySpan<char> typeName, RuntimeAssembly? requestingAssembly,
138-
bool throwOnError, bool requireAssemblyQualifiedName)
142+
bool throwOnError, bool requireAssemblyQualifiedName, IntPtr unsafeAccessorMethod = 0)
139143
{
140144
// Compat: Empty name throws TypeLoadException instead of
141145
// the natural ArgumentException
@@ -158,6 +162,7 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
158162
_throwOnError = throwOnError,
159163
_suppressContextualReflectionContext = true,
160164
_requireAssemblyQualifiedName = requireAssemblyQualifiedName,
165+
_unsafeAccessorMethod = unsafeAccessorMethod,
161166
}.Resolve(parsed);
162167

163168
if (type != null)
@@ -186,6 +191,9 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
186191
return assembly;
187192
}
188193

194+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "UnsafeAccessors_ResolveGenericParamToTypeHandle")]
195+
private static partial IntPtr ResolveGenericParamToTypeHandle(IntPtr unsafeAccessorMethod, [MarshalAs(UnmanagedType.Bool)] bool isMethodParam, uint paramIndex);
196+
189197
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
190198
Justification = "TypeNameResolver.GetType is marked as RequiresUnreferencedCode.")]
191199
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern",
@@ -228,6 +236,42 @@ internal static RuntimeType GetTypeReferencedByCustomAttribute(string typeName,
228236
{
229237
if (assembly is null)
230238
{
239+
if (SupportsUnboundGenerics
240+
&& !string.IsNullOrEmpty(escapedTypeName)
241+
&& escapedTypeName[0] == '!')
242+
{
243+
Debug.Assert(_throwOnError); // Unbound generic support currently always throws.
244+
245+
// Parse the type as an unbound generic parameter. Following the common VAR/MVAR IL syntax:
246+
// !<Number> - Represents a zero-based index into the type's generic parameters.
247+
// !!<Number> - Represents a zero-based index into the method's generic parameters.
248+
249+
// Confirm we have at least one more character
250+
if (escapedTypeName.Length == 1)
251+
{
252+
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
253+
}
254+
255+
// At this point we expect either another '!' and then a number or a number.
256+
bool isMethodParam = escapedTypeName[1] == '!';
257+
ReadOnlySpan<char> toParse = isMethodParam
258+
? escapedTypeName.AsSpan(2) // Skip over "!!"
259+
: escapedTypeName.AsSpan(1); // Skip over "!"
260+
if (!uint.TryParse(toParse, NumberStyles.None, null, out uint paramIndex))
261+
{
262+
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
263+
}
264+
265+
Debug.Assert(_unsafeAccessorMethod != IntPtr.Zero);
266+
IntPtr typeHandle = ResolveGenericParamToTypeHandle(_unsafeAccessorMethod, isMethodParam, paramIndex);
267+
if (typeHandle == IntPtr.Zero)
268+
{
269+
throw new TypeLoadException(SR.Format(SR.TypeLoad_ResolveType, escapedTypeName), typeName: escapedTypeName);
270+
}
271+
272+
return RuntimeTypeHandle.GetRuntimeTypeFromHandle(typeHandle);
273+
}
274+
231275
if (_requireAssemblyQualifiedName)
232276
{
233277
if (_throwOnError)

src/coreclr/dlls/mscorrc/mscorrc.rc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ BEGIN
596596
BFA_BAD_FIELD_TOKEN "Field token out of range."
597597
BFA_INVALID_FIELD_ACC_FLAGS "Invalid Field Access Flags."
598598
BFA_INVALID_UNSAFEACCESSOR "Invalid usage of UnsafeAccessorAttribute."
599+
BFA_INVALID_UNSAFEACCESSORTYPE "Invalid usage of UnsafeAccessorTypeAttribute."
600+
BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE "ValueTypes are not supported with UnsafeAccessorTypeAttribute."
599601
BFA_FIELD_LITERAL_AND_INIT "Field is Literal and InitOnly."
600602
BFA_NONSTATIC_GLOBAL_FIELD "Non-Static Global Field."
601603
BFA_INSTANCE_FIELD_IN_INT "Instance Field in an Interface."

src/coreclr/dlls/mscorrc/resource.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,11 @@
415415
#define BFA_BAD_TYPEREF_TOKEN 0x2046
416416
#define BFA_BAD_CLASS_INT_CA_FORMAT 0x2048
417417
#define BFA_BAD_COMPLUS_SIG 0x2049
418-
#define BFA_BAD_ELEM_IN_SIZEOF 0x204b
419-
#define BFA_IJW_IN_COLLECTIBLE_ALC 0x204c
420-
#define BFA_INVALID_UNSAFEACCESSOR 0x204d
421-
418+
#define BFA_BAD_ELEM_IN_SIZEOF 0x204a
419+
#define BFA_IJW_IN_COLLECTIBLE_ALC 0x204b
420+
#define BFA_INVALID_UNSAFEACCESSOR 0x204c
421+
#define BFA_INVALID_UNSAFEACCESSORTYPE 0x204d
422+
#define BFA_INVALID_UNSAFEACCESSORTYPE_VALUETYPE 0x204e
422423
#define IDS_CLASSLOAD_INTERFACE_NO_ACCESS 0x204f
423424

424425
#define BFA_BAD_CA_HEADER 0x2050

src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public static class CustomAttributeTypeNameParser
2121
/// This is the inverse of what <see cref="CustomAttributeTypeNameFormatter"/> does.
2222
/// </summary>
2323
public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, string name, bool throwIfNotFound = true,
24-
Func<ModuleDesc, string, MetadataType> canonResolver = null)
24+
Func<ModuleDesc, string, TypeDesc> canonGenericResolver = null)
2525
{
2626
if (!TypeName.TryParse(name.AsSpan(), out TypeName parsed, s_typeNameParseOptions))
2727
ThrowHelper.ThrowTypeLoadException(name, module);
@@ -31,7 +31,7 @@ public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module,
3131
_context = module.Context,
3232
_module = module,
3333
_throwIfNotFound = throwIfNotFound,
34-
_canonResolver = canonResolver
34+
_canonGenericResolver = canonGenericResolver
3535
}.Resolve(parsed);
3636
}
3737

@@ -91,7 +91,7 @@ private struct TypeNameResolver
9191
internal TypeSystemContext _context;
9292
internal ModuleDesc _module;
9393
internal bool _throwIfNotFound;
94-
internal Func<ModuleDesc, string, MetadataType> _canonResolver;
94+
internal Func<ModuleDesc, string, TypeDesc> _canonGenericResolver;
9595

9696
internal List<ModuleDesc> _referencedModules;
9797

@@ -136,30 +136,30 @@ private TypeDesc GetSimpleType(TypeName typeName)
136136
}
137137

138138
ModuleDesc module = _module;
139-
if (topLevelTypeName.AssemblyName != null)
139+
if (topLevelTypeName.AssemblyName is not null)
140140
{
141141
module = _context.ResolveAssembly(typeName.AssemblyName, throwIfNotFound: _throwIfNotFound);
142142
if (module == null)
143143
return null;
144144
}
145145

146-
if (module != null)
146+
if (module is not null)
147147
{
148148
TypeDesc type = GetSimpleTypeFromModule(typeName, module);
149-
if (type != null)
149+
if (type is not null)
150150
{
151151
_referencedModules?.Add(module);
152152
return type;
153153
}
154154
}
155155

156-
// If it didn't resolve and wasn't assembly-qualified, we also try core library
157156
if (topLevelTypeName.AssemblyName == null)
158157
{
158+
// If it didn't resolve and wasn't assembly-qualified, we also try core library
159159
if (module != _context.SystemModule)
160160
{
161161
TypeDesc type = GetSimpleTypeFromModule(typeName, _context.SystemModule);
162-
if (type != null)
162+
if (type is not null)
163163
{
164164
_referencedModules?.Add(_context.SystemModule);
165165
return type;
@@ -184,9 +184,9 @@ private TypeDesc GetSimpleTypeFromModule(TypeName typeName, ModuleDesc module)
184184

185185
string fullName = TypeNameHelpers.Unescape(typeName.FullName);
186186

187-
if (_canonResolver != null)
187+
if (_canonGenericResolver != null)
188188
{
189-
MetadataType canonType = _canonResolver(module, fullName);
189+
TypeDesc canonType = _canonGenericResolver(module, fullName);
190190
if (canonType != null)
191191
return canonType;
192192
}

0 commit comments

Comments
 (0)