1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-10 18:11:04 +09:00

[CoreCLR and native AOT] UnsafeAccessorAttribute supports generic parameters (#99468)

* Generic type support

Instance and static fields on generic types
Re-enable and add to field tests
Generic types with non-generic methods.
Generic static methods

* Native AOT support

* Add design document
Add strict check for precisely matched Generic constraints
This commit is contained in:
Aaron Robinson 2024-03-20 13:25:43 -07:00 committed by GitHub
parent 21bfbd5763
commit d58c5417f6
Signed by: github
GPG key ID: B5690EEEBB952194
20 changed files with 1296 additions and 215 deletions

View file

@ -0,0 +1,137 @@
# `UnsafeAccessorAttribute`
## Background and motivation
Number of existing .NET serializers depend on skipping member visibility checks for data serialization. Examples include System.Text.Json or EF Core. In order to skip the visibility checks, the serializers typically use dynamically emitted code (Reflection.Emit or Linq.Expressions) and classic reflection APIs as slow fallback. Neither of these two options are great for source generated serializers and native AOT compilation. This API proposal introduces a first class zero-overhead mechanism for skipping visibility checks.
## Semantics
This attribute will be applied to an `extern static` method. The implementation of the `extern static` method annotated with this attribute will be provided by the runtime based on the information in the attribute and the signature of the method that the attribute is applied to. The runtime will try to find the matching method or field and forward the call to it. If the matching method or field is not found, the body of the `extern static` method will throw `MissingFieldException` or `MissingMethodException`.
For `Method`, `StaticMethod`, `Field`, and `StaticField`, the type of the first argument of the annotated `extern static` method identifies the owning type. Only the specific type defined will be examined for inaccessible members. The type hierarchy is not walked looking for a match.
The value of the first argument is treated as `this` pointer for instance fields and methods.
The first argument must be passed as `ref` for instance fields and methods on structs.
The value of the first argument is not used by the implementation for static fields and methods.
The return value for an accessor to a field can be `ref` if setting of the field is desired.
Constructors can be accessed using Constructor or Method.
The return type is considered for the signature match. Modreqs and modopts are initially not considered for the signature match. However, if an ambiguity exists ignoring modreqs and modopts, a precise match is attempted. If an ambiguity still exists, `AmbiguousMatchException` is thrown.
By default, the attributed method's name dictates the name of the method/field. This can cause confusion in some cases since language abstractions, like C# local functions, generate mangled IL names. The solution to this is to use the `nameof` mechanism and define the `Name` property.
Scenarios involving generics may require creating new generic types to contain the `extern static` method definition. The decision was made to require all `ELEMENT_TYPE_VAR` and `ELEMENT_TYPE_MVAR` instances to match identically type and generic parameter index. This means if the target method for access uses an `ELEMENT_TYPE_VAR`, the `extern static` method must also use an `ELEMENT_TYPE_VAR`. For example:
```csharp
class C<T>
{
T M<U>(U u) => default;
}
class Accessor<V>
{
// Correct - V is an ELEMENT_TYPE_VAR and W is ELEMENT_TYPE_VAR,
// respectively the same as T and U in the definition of C<T>::M<U>().
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
extern static void CallM<W>(C<V> c, W w);
// Incorrect - Since Y must be an ELEMENT_TYPE_VAR, but is ELEMENT_TYPE_MVAR below.
// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
// extern static void CallM<Y, Z>(C<Y> c, Z z);
}
```
Methods with the `UnsafeAccessorAttribute` that access members with generic parameters are expected to have the same declared constraints with the target member. Failure to do so results in unspecified behavior. For example:
```csharp
class C<T>
{
T M<U>(U u) where U: Base => default;
}
class Accessor<V>
{
// Correct - Constraints match the target member.
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
extern static void CallM<W>(C<V> c, W w) where W: Base;
// Incorrect - Constraints do not match target member.
// [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
// extern static void CallM<W>(C<V> c, W w);
}
```
## API
```csharp
namespace System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class UnsafeAccessorAttribute : Attribute
{
public UnsafeAccessorAttribute(UnsafeAccessorKind kind);
public UnsafeAccessorKind Kind { get; }
// The name defaults to the annotated method name if not specified.
// The name must be null for constructors
public string? Name { get; set; }
}
public enum UnsafeAccessorKind
{
Constructor, // call instance constructor (`newobj` in IL)
Method, // call instance method (`callvirt` in IL)
StaticMethod, // call static method (`call` in IL)
Field, // address of instance field (`ldflda` in IL)
StaticField // address of static field (`ldsflda` in IL)
};
```
## API Usage
```csharp
class UserData
{
private UserData() { }
public string Name { get; set; }
}
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
extern static UserData CallPrivateConstructor();
// This API allows accessing backing fields for auto-implemented properties with unspeakable names.
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "<Name>k__BackingField")]
extern static ref string GetName(UserData userData);
UserData ud = CallPrivateConstructor();
GetName(ud) = "Joe";
```
Using generics
```csharp
class UserData<T>
{
private T _field;
private UserData(T t) { _field = t; }
private U ConvertFieldToT<U>() => (U)_field;
}
// The Accessors class provides the generic Type parameter for the method definitions.
class Accessors<V>
{
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
extern static UserData<V> CallPrivateConstructor(V v);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ConvertFieldToT")]
extern static U CallConvertFieldToT<U>(UserData<V> userData);
}
UserData<string> ud = Accessors<string>.CallPrivateConstructor("Joe");
Accessors<string>.CallPrivateConstructor<object>(ud);
```

View file

@ -686,27 +686,27 @@ namespace Internal.IL.Stubs
public ILToken NewToken(TypeDesc value)
{
return NewToken(value, 0x01000000);
return NewToken(value, 0x01000000); // mdtTypeRef
}
public ILToken NewToken(MethodDesc value)
{
return NewToken(value, 0x0a000000);
return NewToken(value, 0x0a000000); // mdtMemberRef
}
public ILToken NewToken(FieldDesc value)
{
return NewToken(value, 0x0a000000);
return NewToken(value, 0x0a000000); // mdtMemberRef
}
public ILToken NewToken(string value)
{
return NewToken(value, 0x70000000);
return NewToken(value, 0x70000000); // mdtString
}
public ILToken NewToken(MethodSignature value)
{
return NewToken(value, 0x11000000);
return NewToken(value, 0x11000000); // mdtSignature
}
public ILLocalVariable NewLocal(TypeDesc localType, bool isPinned = false)

View file

@ -29,12 +29,6 @@ namespace Internal.IL
return GenerateAccessorBadImageFailure(method);
}
// Block generic support early
if (method.HasInstantiation || method.OwningType.HasInstantiation)
{
return GenerateAccessorBadImageFailure(method);
}
if (!TryParseUnsafeAccessorAttribute(method, decodedAttribute.Value, out UnsafeAccessorKind kind, out string name))
{
return GenerateAccessorBadImageFailure(method);
@ -54,7 +48,7 @@ namespace Internal.IL
firstArgType = sig[0];
}
bool isAmbiguous = false;
SetTargetResult result;
// Using the kind type, perform the following:
// 1) Validate the basic type information from the signature.
@ -77,9 +71,10 @@ namespace Internal.IL
}
const string ctorName = ".ctor";
if (!TrySetTargetMethod(ref context, ctorName, out isAmbiguous))
result = TrySetTargetMethod(ref context, ctorName);
if (result is not SetTargetResult.Success)
{
return GenerateAccessorSpecificFailure(ref context, ctorName, isAmbiguous);
return GenerateAccessorSpecificFailure(ref context, ctorName, result);
}
break;
case UnsafeAccessorKind.Method:
@ -105,9 +100,10 @@ namespace Internal.IL
}
context.IsTargetStatic = kind == UnsafeAccessorKind.StaticMethod;
if (!TrySetTargetMethod(ref context, name, out isAmbiguous))
result = TrySetTargetMethod(ref context, name);
if (result is not SetTargetResult.Success)
{
return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous);
return GenerateAccessorSpecificFailure(ref context, name, result);
}
break;
@ -136,9 +132,10 @@ namespace Internal.IL
}
context.IsTargetStatic = kind == UnsafeAccessorKind.StaticField;
if (!TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType()))
result = TrySetTargetField(ref context, name, ((ParameterizedType)retType).GetParameterType());
if (result is not SetTargetResult.Success)
{
return GenerateAccessorSpecificFailure(ref context, name, isAmbiguous);
return GenerateAccessorSpecificFailure(ref context, name, result);
}
break;
@ -232,6 +229,12 @@ namespace Internal.IL
targetType = null;
}
// We do not support signature variables as a target (for example, VAR and MVAR).
if (targetType is SignatureVariable)
{
targetType = null;
}
validated = targetType;
return validated != null;
}
@ -366,7 +369,45 @@ namespace Internal.IL
return true;
}
private static bool TrySetTargetMethod(ref GenerationContext context, string name, out bool isAmbiguous, bool ignoreCustomModifiers = true)
private static bool VerifyDeclarationSatisfiesTargetConstraints(MethodDesc declaration, TypeDesc targetType, MethodDesc targetMethod)
{
Debug.Assert(declaration != null);
Debug.Assert(targetType != null);
Debug.Assert(targetMethod != null);
if (targetType.HasInstantiation)
{
Instantiation declClassInst = declaration.OwningType.Instantiation;
var instType = targetType.Context.GetInstantiatedType((MetadataType)targetType.GetTypeDefinition(), declClassInst);
if (!instType.CheckConstraints())
{
return false;
}
targetMethod = instType.FindMethodOnExactTypeWithMatchingTypicalMethod(targetMethod);
}
if (targetMethod.HasInstantiation)
{
Instantiation declMethodInst = declaration.Instantiation;
var instMethod = targetType.Context.GetInstantiatedMethod(targetMethod, declMethodInst);
if (!instMethod.CheckConstraints())
{
return false;
}
}
return true;
}
private enum SetTargetResult
{
Success,
Missing,
Ambiguous,
Invalid,
}
private static SetTargetResult TrySetTargetMethod(ref GenerationContext context, string name, bool ignoreCustomModifiers = true)
{
TypeDesc targetType = context.TargetType;
@ -399,23 +440,39 @@ namespace Internal.IL
// We have detected ambiguity when ignoring custom modifiers.
// Start over, but look for a match requiring custom modifiers
// to match precisely.
if (TrySetTargetMethod(ref context, name, out isAmbiguous, ignoreCustomModifiers: false))
return true;
if (SetTargetResult.Success == TrySetTargetMethod(ref context, name, ignoreCustomModifiers: false))
return SetTargetResult.Success;
}
isAmbiguous = true;
return false;
return SetTargetResult.Ambiguous;
}
targetMaybe = md;
}
isAmbiguous = false;
if (targetMaybe != null)
{
if (!VerifyDeclarationSatisfiesTargetConstraints(context.Declaration, targetType, targetMaybe))
{
return SetTargetResult.Invalid;
}
if (targetMaybe.HasInstantiation)
{
TypeDesc[] methodInstantiation = new TypeDesc[targetMaybe.Instantiation.Length];
for (int i = 0; i < methodInstantiation.Length; ++i)
{
methodInstantiation[i] = targetMaybe.Context.GetSignatureVariable(i, true);
}
targetMaybe = targetMaybe.Context.GetInstantiatedMethod(targetMaybe, new Instantiation(methodInstantiation));
}
Debug.Assert(targetMaybe is not null);
}
context.TargetMethod = targetMaybe;
return context.TargetMethod != null;
return context.TargetMethod != null ? SetTargetResult.Success : SetTargetResult.Missing;
}
private static bool TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType)
private static SetTargetResult TrySetTargetField(ref GenerationContext context, string name, TypeDesc fieldType)
{
TypeDesc targetType = context.TargetType;
@ -431,10 +488,10 @@ namespace Internal.IL
&& fieldType == fd.FieldType)
{
context.TargetField = fd;
return true;
return SetTargetResult.Success;
}
}
return false;
return SetTargetResult.Missing;
}
private static MethodIL GenerateAccessor(ref GenerationContext context)
@ -486,7 +543,7 @@ namespace Internal.IL
return emit.Link(context.Declaration);
}
private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, bool ambiguous)
private static MethodIL GenerateAccessorSpecificFailure(ref GenerationContext context, string name, SetTargetResult result)
{
ILEmitter emit = new ILEmitter();
ILCodeStream codeStream = emit.NewCodeStream();
@ -496,14 +553,19 @@ namespace Internal.IL
MethodDesc thrower;
TypeSystemContext typeSysContext = context.Declaration.Context;
if (ambiguous)
if (result is SetTargetResult.Ambiguous)
{
codeStream.EmitLdc((int)ExceptionStringID.AmbiguousMatchUnsafeAccessor);
thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowAmbiguousMatchException");
}
else if (result is SetTargetResult.Invalid)
{
codeStream.EmitLdc((int)ExceptionStringID.InvalidProgramDefault);
thrower = typeSysContext.GetHelperEntryPoint("ThrowHelpers", "ThrowInvalidProgramException");
}
else
{
Debug.Assert(result is SetTargetResult.Missing);
ExceptionStringID id;
if (context.Kind == UnsafeAccessorKind.Field || context.Kind == UnsafeAccessorKind.StaticField)
{

View file

@ -298,15 +298,12 @@ namespace
{
STANDARD_VM_CONTRACT;
TypeHandle type;
MethodDesc* pMD;
FieldDesc* pFD;
ResolvedToken resolved{};
pResolver->ResolveToken(token, &resolved);
pResolver->ResolveToken(token, &type, &pMD, &pFD);
_ASSERTE(!resolved.TypeHandle.IsNull());
_ASSERTE(!type.IsNull());
*nameOut = type.GetMethodTable()->GetFullyQualifiedNameInfo(namespaceOut);
*nameOut = resolved.TypeHandle.GetMethodTable()->GetFullyQualifiedNameInfo(namespaceOut);
return S_OK;
}

View file

@ -1325,7 +1325,7 @@ void LCGMethodResolver::AddToUsedIndCellList(BYTE * indcell)
}
void LCGMethodResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc ** ppMD, FieldDesc ** ppFD)
void LCGMethodResolver::ResolveToken(mdToken token, ResolvedToken* resolvedToken)
{
STANDARD_VM_CONTRACT;
@ -1335,24 +1335,35 @@ void LCGMethodResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc
DECLARE_ARGHOLDER_ARRAY(args, 5);
TypeHandle handle;
MethodDesc* pMD = NULL;
FieldDesc* pFD = NULL;
args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(ObjectFromHandle(m_managedResolver));
args[ARGNUM_1] = DWORD_TO_ARGHOLDER(token);
args[ARGNUM_2] = pTH;
args[ARGNUM_3] = ppMD;
args[ARGNUM_4] = ppFD;
args[ARGNUM_2] = &handle;
args[ARGNUM_3] = &pMD;
args[ARGNUM_4] = &pFD;
CALL_MANAGED_METHOD_NORET(args);
_ASSERTE(*ppMD == NULL || *ppFD == NULL);
_ASSERTE(pMD == NULL || pFD == NULL);
if (pTH->IsNull())
if (handle.IsNull())
{
if (*ppMD != NULL) *pTH = (*ppMD)->GetMethodTable();
else
if (*ppFD != NULL) *pTH = (*ppFD)->GetEnclosingMethodTable();
if (pMD != NULL)
{
handle = pMD->GetMethodTable();
}
else if (pFD != NULL)
{
handle = pFD->GetEnclosingMethodTable();
}
}
_ASSERTE(!pTH->IsNull());
_ASSERTE(!handle.IsNull());
resolvedToken->TypeHandle = handle;
resolvedToken->Method = pMD;
resolvedToken->Field = pFD;
}
//---------------------------------------------------------------------------------------

View file

@ -37,6 +37,15 @@ public:
void Delete();
};
struct ResolvedToken final
{
TypeHandle TypeHandle;
SigPointer TypeSignature;
SigPointer MethodSignature;
MethodDesc* Method;
FieldDesc* Field;
};
//---------------------------------------------------------------------------------------
//
class DynamicResolver
@ -90,7 +99,7 @@ public:
virtual OBJECTHANDLE ConstructStringLiteral(mdToken metaTok) = 0;
virtual BOOL IsValidStringRef(mdToken metaTok) = 0;
virtual STRINGREF GetStringLiteral(mdToken metaTok) = 0;
virtual void ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc ** ppMD, FieldDesc ** ppFD) = 0;
virtual void ResolveToken(mdToken token, ResolvedToken* resolvedToken) = 0;
virtual SigPointer ResolveSignature(mdToken token) = 0;
virtual SigPointer ResolveSignatureForVarArg(mdToken token) = 0;
virtual void GetEHInfo(unsigned EHnumber, CORINFO_EH_CLAUSE* clause) = 0;
@ -141,7 +150,7 @@ public:
OBJECTHANDLE ConstructStringLiteral(mdToken metaTok);
BOOL IsValidStringRef(mdToken metaTok);
void ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc ** ppMD, FieldDesc ** ppFD);
void ResolveToken(mdToken token, ResolvedToken* resolvedToken);
SigPointer ResolveSignature(mdToken token);
SigPointer ResolveSignatureForVarArg(mdToken token);
void GetEHInfo(unsigned EHnumber, CORINFO_EH_CLAUSE* clause);

View file

@ -133,13 +133,10 @@ STRINGREF ILStubResolver::GetStringLiteral(mdToken metaTok)
return NULL;
}
void ILStubResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc ** ppMD, FieldDesc ** ppFD)
void ILStubResolver::ResolveToken(mdToken token, ResolvedToken* resolvedToken)
{
STANDARD_VM_CONTRACT;
*pTH = NULL;
*ppMD = NULL;
*ppFD = NULL;
_ASSERTE(resolvedToken != NULL);
switch (TypeFromToken(token))
{
@ -147,8 +144,8 @@ void ILStubResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc **
{
MethodDesc* pMD = m_pCompileTimeState->m_tokenLookupMap.LookupMethodDef(token);
_ASSERTE(pMD);
*ppMD = pMD;
*pTH = TypeHandle(pMD->GetMethodTable());
resolvedToken->Method = pMD;
resolvedToken->TypeHandle = TypeHandle(pMD->GetMethodTable());
}
break;
@ -156,7 +153,7 @@ void ILStubResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc **
{
TypeHandle typeHnd = m_pCompileTimeState->m_tokenLookupMap.LookupTypeDef(token);
_ASSERTE(!typeHnd.IsNull());
*pTH = typeHnd;
resolvedToken->TypeHandle = typeHnd;
}
break;
@ -164,11 +161,60 @@ void ILStubResolver::ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc **
{
FieldDesc* pFD = m_pCompileTimeState->m_tokenLookupMap.LookupFieldDef(token);
_ASSERTE(pFD);
*ppFD = pFD;
*pTH = TypeHandle(pFD->GetEnclosingMethodTable());
resolvedToken->Field = pFD;
resolvedToken->TypeHandle = TypeHandle(pFD->GetEnclosingMethodTable());
}
break;
#if !defined(DACCESS_COMPILE)
case mdtMemberRef:
{
TokenLookupMap::MemberRefEntry entry = m_pCompileTimeState->m_tokenLookupMap.LookupMemberRef(token);
if (entry.Type == mdtFieldDef)
{
_ASSERTE(entry.Entry.Field != NULL);
if (entry.ClassSignatureToken != mdTokenNil)
resolvedToken->TypeSignature = m_pCompileTimeState->m_tokenLookupMap.LookupSig(entry.ClassSignatureToken);
resolvedToken->Field = entry.Entry.Field;
resolvedToken->TypeHandle = TypeHandle(entry.Entry.Field->GetApproxEnclosingMethodTable());
}
else
{
_ASSERTE(entry.Type == mdtMethodDef);
_ASSERTE(entry.Entry.Method != NULL);
if (entry.ClassSignatureToken != mdTokenNil)
resolvedToken->TypeSignature = m_pCompileTimeState->m_tokenLookupMap.LookupSig(entry.ClassSignatureToken);
resolvedToken->Method = entry.Entry.Method;
MethodTable* pMT = entry.Entry.Method->GetMethodTable();
_ASSERTE(!pMT->ContainsGenericVariables());
resolvedToken->TypeHandle = TypeHandle(pMT);
}
}
break;
case mdtMethodSpec:
{
TokenLookupMap::MethodSpecEntry entry = m_pCompileTimeState->m_tokenLookupMap.LookupMethodSpec(token);
_ASSERTE(entry.Method != NULL);
if (entry.ClassSignatureToken != mdTokenNil)
resolvedToken->TypeSignature = m_pCompileTimeState->m_tokenLookupMap.LookupSig(entry.ClassSignatureToken);
if (entry.MethodSignatureToken != mdTokenNil)
resolvedToken->MethodSignature = m_pCompileTimeState->m_tokenLookupMap.LookupSig(entry.MethodSignatureToken);
resolvedToken->Method = entry.Method;
MethodTable* pMT = entry.Method->GetMethodTable();
_ASSERTE(!pMT->ContainsGenericVariables());
resolvedToken->TypeHandle = TypeHandle(pMT);
}
break;
#endif // !defined(DACCESS_COMPILE)
default:
UNREACHABLE_MSG("unexpected metadata token type");
}

View file

@ -35,7 +35,7 @@ public:
OBJECTHANDLE ConstructStringLiteral(mdToken metaTok);
BOOL IsValidStringRef(mdToken metaTok);
STRINGREF GetStringLiteral(mdToken metaTok);
void ResolveToken(mdToken token, TypeHandle * pTH, MethodDesc ** ppMD, FieldDesc ** ppFD);
void ResolveToken(mdToken token, ResolvedToken* resolvedToken);
SigPointer ResolveSignature(mdToken token);
SigPointer ResolveSignatureForVarArg(mdToken token);
void GetEHInfo(unsigned EHnumber, CORINFO_EH_CLAUSE* clause);

View file

@ -156,15 +156,13 @@ inline CORINFO_MODULE_HANDLE GetScopeHandle(MethodDesc* method)
//This is common refactored code from within several of the access check functions.
static BOOL ModifyCheckForDynamicMethod(DynamicResolver *pResolver,
TypeHandle *pOwnerTypeForSecurity,
AccessCheckOptions::AccessCheckType *pAccessCheckType,
DynamicResolver** ppAccessContext)
AccessCheckOptions::AccessCheckType *pAccessCheckType)
{
CONTRACTL {
STANDARD_VM_CHECK;
PRECONDITION(CheckPointer(pResolver));
PRECONDITION(CheckPointer(pOwnerTypeForSecurity));
PRECONDITION(CheckPointer(pAccessCheckType));
PRECONDITION(CheckPointer(ppAccessContext));
PRECONDITION(*pAccessCheckType == AccessCheckOptions::kNormalAccessibilityChecks);
} CONTRACTL_END;
@ -883,7 +881,18 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken
if (IsDynamicScope(pResolvedToken->tokenScope))
{
GetDynamicResolver(pResolvedToken->tokenScope)->ResolveToken(pResolvedToken->token, &th, &pMD, &pFD);
ResolvedToken resolved{};
GetDynamicResolver(pResolvedToken->tokenScope)->ResolveToken(pResolvedToken->token, &resolved);
th = resolved.TypeHandle;
pMD = resolved.Method;
pFD = resolved.Field;
// Record supplied signatures.
if (!resolved.TypeSignature.IsNull())
resolved.TypeSignature.GetSignature(&pResolvedToken->pTypeSpec, &pResolvedToken->cbTypeSpec);
if (!resolved.MethodSignature.IsNull())
resolved.MethodSignature.GetSignature(&pResolvedToken->pMethodSpec, &pResolvedToken->cbMethodSpec);
//
// Check that we got the expected handles and fill in missing data if necessary
@ -893,18 +902,10 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken
if (pMD != NULL)
{
if ((tkType != mdtMethodDef) && (tkType != mdtMemberRef))
if ((tkType != mdtMethodDef) && (tkType != mdtMemberRef) && (tkType != mdtMethodSpec))
ThrowBadTokenException(pResolvedToken);
if ((tokenType & CORINFO_TOKENKIND_Method) == 0)
ThrowBadTokenException(pResolvedToken);
if (th.IsNull())
th = pMD->GetMethodTable();
// "PermitUninstDefOrRef" check
if ((tokenType != CORINFO_TOKENKIND_Ldtoken) && pMD->ContainsGenericVariables())
{
COMPlusThrow(kInvalidProgramException);
}
// if this is a BoxedEntryPointStub get the UnboxedEntryPoint one
if (pMD->IsUnboxingStub())
@ -924,8 +925,6 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken
ThrowBadTokenException(pResolvedToken);
if ((tokenType & CORINFO_TOKENKIND_Field) == 0)
ThrowBadTokenException(pResolvedToken);
if (th.IsNull())
th = pFD->GetApproxEnclosingMethodTable();
if (pFD->IsStatic() && (tokenType != CORINFO_TOKENKIND_Ldtoken))
{
@ -959,7 +958,7 @@ void CEEInfo::resolveToken(/* IN, OUT */ CORINFO_RESOLVED_TOKEN * pResolvedToken
else
{
mdToken metaTOK = pResolvedToken->token;
Module * pModule = (Module *)pResolvedToken->tokenScope;
Module * pModule = GetModule(pResolvedToken->tokenScope);
switch (TypeFromToken(metaTOK))
{
@ -1705,7 +1704,9 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken,
SigTypeContext::InitTypeContext(pCallerForSecurity, &typeContext);
SigPointer sigptr(pResolvedToken->pTypeSpec, pResolvedToken->cbTypeSpec);
fieldTypeForSecurity = sigptr.GetTypeHandleThrowing((Module *)pResolvedToken->tokenScope, &typeContext);
Module* targetModule = GetModule(pResolvedToken->tokenScope);
fieldTypeForSecurity = sigptr.GetTypeHandleThrowing(targetModule, &typeContext);
// typeHnd can be a variable type
if (fieldTypeForSecurity.GetMethodTable() == NULL)
@ -1717,15 +1718,13 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken,
BOOL doAccessCheck = TRUE;
AccessCheckOptions::AccessCheckType accessCheckType = AccessCheckOptions::kNormalAccessibilityChecks;
DynamicResolver * pAccessContext = NULL;
//More in code:CEEInfo::getCallInfo, but the short version is that the caller and callee Descs do
//not completely describe the type.
TypeHandle callerTypeForSecurity = TypeHandle(pCallerForSecurity->GetMethodTable());
if (IsDynamicScope(pResolvedToken->tokenScope))
{
doAccessCheck = ModifyCheckForDynamicMethod(GetDynamicResolver(pResolvedToken->tokenScope), &callerTypeForSecurity,
&accessCheckType, &pAccessContext);
&accessCheckType);
}
//Now for some link time checks.
@ -1737,7 +1736,7 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken,
{
//Well, let's check some visibility at least.
AccessCheckOptions accessCheckOptions(accessCheckType,
pAccessContext,
NULL,
FALSE,
pField);
@ -1851,22 +1850,19 @@ CEEInfo::findCallSiteSig(
{
_ASSERTE(TypeFromToken(sigMethTok) == mdtMethodDef);
TypeHandle classHandle;
MethodDesc * pMD = NULL;
FieldDesc * pFD = NULL;
// in this case a method is asked for its sig. Resolve the method token and get the sig
pResolver->ResolveToken(sigMethTok, &classHandle, &pMD, &pFD);
if (pMD == NULL)
ResolvedToken resolved{};
pResolver->ResolveToken(sigMethTok, &resolved);
if (resolved.Method == NULL)
COMPlusThrow(kInvalidProgramException);
PCCOR_SIGNATURE pSig = NULL;
DWORD cbSig;
pMD->GetSig(&pSig, &cbSig);
resolved.Method->GetSig(&pSig, &cbSig);
sig = SigPointer(pSig, cbSig);
context = MAKE_METHODCONTEXT(pMD);
scopeHnd = GetScopeHandle(pMD->GetModule());
context = MAKE_METHODCONTEXT(resolved.Method);
scopeHnd = GetScopeHandle(resolved.Method->GetModule());
}
sig.GetSignature(&pSig, &cbSig);
@ -3278,7 +3274,7 @@ NoSpecialCase:
sigBuilder.AppendData(pContextMT->GetNumDicts() - 1);
}
Module * pModule = (Module *)pResolvedToken->tokenScope;
Module * pModule = GetModule(pResolvedToken->tokenScope);
switch (entryKind)
{
@ -4959,7 +4955,6 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass(
BOOL doAccessCheck = TRUE;
AccessCheckOptions::AccessCheckType accessCheckType = AccessCheckOptions::kNormalAccessibilityChecks;
DynamicResolver * pAccessContext = NULL;
//All access checks must be done on the open instantiation.
MethodDesc * pCallerForSecurity = GetMethodForSecurity(callerHandle);
@ -4972,7 +4967,7 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass(
SigTypeContext::InitTypeContext(pCallerForSecurity, &typeContext);
SigPointer sigptr(pResolvedToken->pTypeSpec, pResolvedToken->cbTypeSpec);
pCalleeForSecurity = sigptr.GetTypeHandleThrowing((Module *)pResolvedToken->tokenScope, &typeContext);
pCalleeForSecurity = sigptr.GetTypeHandleThrowing(GetModule(pResolvedToken->tokenScope), &typeContext);
}
while (pCalleeForSecurity.HasTypeParam())
@ -4983,8 +4978,7 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass(
if (IsDynamicScope(pResolvedToken->tokenScope))
{
doAccessCheck = ModifyCheckForDynamicMethod(GetDynamicResolver(pResolvedToken->tokenScope),
&callerTypeForSecurity, &accessCheckType,
&pAccessContext);
&callerTypeForSecurity, &accessCheckType);
}
//Since this is a check against a TypeHandle, there are some things we can stick in a TypeHandle that
@ -4999,7 +4993,7 @@ CorInfoIsAccessAllowedResult CEEInfo::canAccessClass(
if (doAccessCheck)
{
AccessCheckOptions accessCheckOptions(accessCheckType,
pAccessContext,
NULL,
FALSE /*throw on error*/,
pCalleeForSecurity.GetMethodTable());
@ -5560,7 +5554,7 @@ void CEEInfo::getCallInfo(
if (pResolvedToken->pTypeSpec != NULL)
{
SigPointer sigptr(pResolvedToken->pTypeSpec, pResolvedToken->cbTypeSpec);
calleeTypeForSecurity = sigptr.GetTypeHandleThrowing((Module *)pResolvedToken->tokenScope, &typeContext);
calleeTypeForSecurity = sigptr.GetTypeHandleThrowing(GetModule(pResolvedToken->tokenScope), &typeContext);
// typeHnd can be a variable type
if (calleeTypeForSecurity.GetMethodTable() == NULL)
@ -5587,7 +5581,7 @@ void CEEInfo::getCallInfo(
IfFailThrow(sp.GetByte(&etype));
// Load the generic method instantiation
THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, (Module *)pResolvedToken->tokenScope);
THROW_BAD_FORMAT_MAYBE(etype == (BYTE)IMAGE_CEE_CS_CALLCONV_GENERICINST, 0, GetModule(pResolvedToken->tokenScope));
IfFailThrow(sp.GetData(&nGenericMethodArgs));
@ -5601,7 +5595,7 @@ void CEEInfo::getCallInfo(
for (uint32_t i = 0; i < nGenericMethodArgs; i++)
{
genericMethodArgs[i] = sp.GetTypeHandleThrowing((Module *)pResolvedToken->tokenScope, &typeContext);
genericMethodArgs[i] = sp.GetTypeHandleThrowing(GetModule(pResolvedToken->tokenScope), &typeContext);
_ASSERTE (!genericMethodArgs[i].IsNull());
IfFailThrow(sp.SkipExactlyOne());
}
@ -5621,14 +5615,13 @@ void CEEInfo::getCallInfo(
BOOL doAccessCheck = TRUE;
BOOL canAccessMethod = TRUE;
AccessCheckOptions::AccessCheckType accessCheckType = AccessCheckOptions::kNormalAccessibilityChecks;
DynamicResolver * pAccessContext = NULL;
callerTypeForSecurity = TypeHandle(pCallerForSecurity->GetMethodTable());
if (pCallerForSecurity->IsDynamicMethod())
{
doAccessCheck = ModifyCheckForDynamicMethod(pCallerForSecurity->AsDynamicMethodDesc()->GetResolver(),
&callerTypeForSecurity,
&accessCheckType, &pAccessContext);
&accessCheckType);
}
pResult->accessAllowed = CORINFO_ACCESS_ALLOWED;
@ -5636,7 +5629,7 @@ void CEEInfo::getCallInfo(
if (doAccessCheck)
{
AccessCheckOptions accessCheckOptions(accessCheckType,
pAccessContext,
NULL,
FALSE,
pCalleeForSecurity);
@ -12397,10 +12390,11 @@ void CEEJitInfo::setEHinfo (
((pEHClause->Flags & COR_ILEXCEPTION_CLAUSE_FILTER) == 0) &&
(clause->ClassToken != NULL))
{
MethodDesc * pMD; FieldDesc * pFD;
m_pMethodBeingCompiled->AsDynamicMethodDesc()->GetResolver()->ResolveToken(clause->ClassToken, (TypeHandle *)&pEHClause->TypeHandle, &pMD, &pFD);
ResolvedToken resolved{};
m_pMethodBeingCompiled->AsDynamicMethodDesc()->GetResolver()->ResolveToken(clause->ClassToken, &resolved);
pEHClause->TypeHandle = (void*)resolved.TypeHandle.AsPtr();
SetHasCachedTypeHandle(pEHClause);
LOG((LF_EH, LL_INFO1000000, " CachedTypeHandle: 0x%08lx -> 0x%08lx\n", clause->ClassToken, pEHClause->TypeHandle));
LOG((LF_EH, LL_INFO1000000, " CachedTypeHandle: 0x%08x -> %p\n", clause->ClassToken, pEHClause->TypeHandle));
}
EE_TO_JIT_TRANSITION();
@ -13004,18 +12998,17 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,
//and its return type.
AccessCheckOptions::AccessCheckType accessCheckType = AccessCheckOptions::kNormalAccessibilityChecks;
TypeHandle ownerTypeForSecurity = TypeHandle(pMethodForSecurity->GetMethodTable());
DynamicResolver *pAccessContext = NULL;
BOOL doAccessCheck = TRUE;
if (pMethodForSecurity->IsDynamicMethod())
{
doAccessCheck = ModifyCheckForDynamicMethod(pMethodForSecurity->AsDynamicMethodDesc()->GetResolver(),
&ownerTypeForSecurity,
&accessCheckType, &pAccessContext);
&accessCheckType);
}
if (doAccessCheck)
{
AccessCheckOptions accessCheckOptions(accessCheckType,
pAccessContext,
NULL,
TRUE /*Throw on error*/,
pMethodForSecurity);

View file

@ -472,6 +472,17 @@ WORD MethodTable::GetNumMethods()
return GetClass()->GetNumMethods();
}
PTR_MethodTable MethodTable::GetTypicalMethodTable()
{
LIMITED_METHOD_DAC_CONTRACT;
if (IsArray())
return (PTR_MethodTable)this;
PTR_MethodTable methodTableMaybe = GetModule()->LookupTypeDef(GetCl()).AsMethodTable();
_ASSERTE(methodTableMaybe->IsTypicalTypeDefinition());
return methodTableMaybe;
}
//==========================================================================================
BOOL MethodTable::HasSameTypeDefAs(MethodTable *pMT)
{

View file

@ -1182,6 +1182,8 @@ public:
return !HasInstantiation() || IsGenericTypeDefinition();
}
PTR_MethodTable GetTypicalMethodTable();
BOOL HasSameTypeDefAs(MethodTable *pMT);
//-------------------------------------------------------------------

View file

@ -1116,6 +1116,7 @@ namespace
: Kind{ kind }
, Declaration{ pMD }
, DeclarationSig{ pMD }
, TargetTypeSig{}
, TargetType{}
, IsTargetStatic{ false }
, TargetMethod{}
@ -1125,13 +1126,14 @@ namespace
UnsafeAccessorKind Kind;
MethodDesc* Declaration;
MetaSig DeclarationSig;
SigPointer TargetTypeSig;
TypeHandle TargetType;
bool IsTargetStatic;
MethodDesc* TargetMethod;
FieldDesc* TargetField;
};
TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe)
TypeHandle ValidateTargetType(TypeHandle targetTypeMaybe, CorElementType targetFromSig)
{
TypeHandle targetType = targetTypeMaybe.IsByRef()
? targetTypeMaybe.GetTypeParam()
@ -1142,6 +1144,12 @@ namespace
if (targetType.IsTypeDesc())
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
// We do not support generic signature types as valid targets.
if (targetFromSig == ELEMENT_TYPE_VAR || targetFromSig == ELEMENT_TYPE_MVAR)
{
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
}
return targetType;
}
@ -1167,16 +1175,29 @@ namespace
ModuleBase* pModule2 = method->GetModule();
const Substitution* pSubst2 = NULL;
//
// Parsing the signature follows details defined in ECMA-335 - II.23.2.1
//
// Validate calling convention
if ((*pSig1 & IMAGE_CEE_CS_CALLCONV_MASK) != (*pSig2 & IMAGE_CEE_CS_CALLCONV_MASK))
{
return false;
}
BYTE callConv = *pSig1;
BYTE callConvDecl = *pSig1;
BYTE callConvMethod = *pSig2;
pSig1++;
pSig2++;
// Handle generic param count
DWORD declGenericCount = 0;
DWORD methodGenericCount = 0;
if (callConvDecl & IMAGE_CEE_CS_CALLCONV_GENERIC)
IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &declGenericCount));
if (callConvMethod & IMAGE_CEE_CS_CALLCONV_GENERIC)
IfFailThrow(CorSigUncompressData_EndPtr(pSig2, pEndSig2, &methodGenericCount));
DWORD declArgCount;
DWORD methodArgCount;
IfFailThrow(CorSigUncompressData_EndPtr(pSig1, pEndSig1, &declArgCount));
@ -1250,6 +1271,74 @@ namespace
return true;
}
void VerifyDeclarationSatisfiesTargetConstraints(MethodDesc* declaration, MethodTable* targetType, MethodDesc* targetMethod)
{
CONTRACTL
{
STANDARD_VM_CHECK;
PRECONDITION(declaration != NULL);
PRECONDITION(targetType != NULL);
PRECONDITION(targetMethod != NULL);
}
CONTRACTL_END;
// If the target method has no generic parameters there is nothing to verify
if (!targetMethod->HasClassOrMethodInstantiation())
return;
// Construct a context for verifying target's constraints are
// satisfied by the declaration.
Instantiation declClassInst;
Instantiation declMethodInst;
Instantiation targetClassInst;
Instantiation targetMethodInst;
if (targetType->HasInstantiation())
{
declClassInst = declaration->GetMethodTable()->GetInstantiation();
targetClassInst = targetType->GetTypicalMethodTable()->GetInstantiation();
}
if (targetMethod->HasMethodInstantiation())
{
declMethodInst = declaration->LoadTypicalMethodDefinition()->GetMethodInstantiation();
targetMethodInst = targetMethod->LoadTypicalMethodDefinition()->GetMethodInstantiation();
}
SigTypeContext typeContext;
SigTypeContext::InitTypeContext(declClassInst, declMethodInst, &typeContext);
InstantiationContext instContext{ &typeContext };
//
// Validate constraints on Type parameters
//
DWORD typeParamCount = targetClassInst.GetNumArgs();
if (typeParamCount != declClassInst.GetNumArgs())
COMPlusThrow(kInvalidProgramException, W("Argument_GenTypeConstraintsNotEqual"));
for (DWORD i = 0; i < typeParamCount; ++i)
{
TypeHandle arg = declClassInst[i];
TypeVarTypeDesc* param = targetClassInst[i].AsGenericVariable();
if (!param->SatisfiesConstraints(&typeContext, arg, &instContext))
COMPlusThrow(kInvalidProgramException, W("Argument_GenTypeConstraintsNotEqual"));
}
//
// Validate constraints on Method parameters
//
DWORD methodParamCount = targetMethodInst.GetNumArgs();
if (methodParamCount != declMethodInst.GetNumArgs())
COMPlusThrow(kInvalidProgramException, W("Argument_GenMethodConstraintsNotEqual"));
for (DWORD i = 0; i < methodParamCount; ++i)
{
TypeHandle arg = declMethodInst[i];
TypeVarTypeDesc* param = targetMethodInst[i].AsGenericVariable();
if (!param->SatisfiesConstraints(&typeContext, arg, &instContext))
COMPlusThrow(kInvalidProgramException, W("Argument_GenMethodConstraintsNotEqual"));
}
}
bool TrySetTargetMethod(
GenerationContext& cxt,
LPCUTF8 methodName,
@ -1264,11 +1353,13 @@ namespace
TypeHandle targetType = cxt.TargetType;
_ASSERTE(!targetType.IsTypeDesc());
MethodTable* pMT = targetType.AsMethodTable();
MethodDesc* targetMaybe = NULL;
// Following a similar iteration pattern found in MemberLoader::FindMethod().
// However, we are only operating on the current type not walking the type hierarchy.
MethodTable::IntroducedMethodIterator iter(targetType.AsMethodTable());
MethodTable::IntroducedMethodIterator iter(pMT);
for (; iter.IsValid(); iter.Next())
{
MethodDesc* curr = iter.GetMethodDesc();
@ -1304,6 +1395,9 @@ namespace
targetMaybe = curr;
}
if (targetMaybe != NULL)
VerifyDeclarationSatisfiesTargetConstraints(cxt.Declaration, pMT, targetMaybe);
cxt.TargetMethod = targetMaybe;
return cxt.TargetMethod != NULL;
}
@ -1321,19 +1415,47 @@ namespace
TypeHandle targetType = cxt.TargetType;
_ASSERTE(!targetType.IsTypeDesc());
MethodTable* pMT = targetType.AsMethodTable();
CorElementType elemType = fieldType.GetSignatureCorElementType();
ApproxFieldDescIterator fdIterator(
targetType.AsMethodTable(),
pMT,
(cxt.IsTargetStatic ? ApproxFieldDescIterator::STATIC_FIELDS : ApproxFieldDescIterator::INSTANCE_FIELDS));
PTR_FieldDesc pField;
while ((pField = fdIterator.Next()) != NULL)
{
// Validate the name and target type match.
if (strcmp(fieldName, pField->GetName()) == 0
&& fieldType == pField->LookupFieldTypeHandle())
if (strcmp(fieldName, pField->GetName()) != 0)
continue;
// We check if the possible field is class or valuetype
// since generic fields need resolution.
CorElementType fieldTypeMaybe = pField->GetFieldType();
if (fieldTypeMaybe == ELEMENT_TYPE_CLASS
|| fieldTypeMaybe == ELEMENT_TYPE_VALUETYPE)
{
cxt.TargetField = pField;
return true;
if (fieldType != pField->LookupFieldTypeHandle())
continue;
}
else
{
if (elemType != fieldTypeMaybe)
continue;
}
if (cxt.Kind == UnsafeAccessorKind::StaticField && pMT->HasGenericsStaticsInfo())
{
// Statics require the exact typed field as opposed to the canonically
// typed field. In order to do that we lookup the current index of the
// approx field and then use that index to get the precise field from
// the approx field.
MethodTable* pFieldMT = pField->GetApproxEnclosingMethodTable();
DWORD index = pFieldMT->GetIndexForFieldDesc(pField);
pField = pMT->GetFieldDescByIndex(index);
}
cxt.TargetField = pField;
return true;
}
return false;
}
@ -1351,12 +1473,14 @@ namespace
ilResolver->SetStubMethodDesc(cxt.Declaration);
ilResolver->SetStubTargetMethodDesc(cxt.TargetMethod);
// [TODO] Handle generics
SigTypeContext emptyContext;
SigTypeContext genericContext;
if (cxt.Declaration->GetClassification() == mcInstantiated)
SigTypeContext::InitTypeContext(cxt.Declaration, &genericContext);
ILStubLinker sl(
cxt.Declaration->GetModule(),
cxt.Declaration->GetSignature(),
&emptyContext,
&genericContext,
cxt.TargetMethod,
(ILStubLinkerFlags)ILSTUB_LINKER_FLAG_NONE);
@ -1377,24 +1501,126 @@ namespace
switch (cxt.Kind)
{
case UnsafeAccessorKind::Constructor:
{
_ASSERTE(cxt.TargetMethod != NULL);
pCode->EmitNEWOBJ(pCode->GetToken(cxt.TargetMethod), targetArgCount);
mdToken target;
if (!cxt.TargetType.HasInstantiation())
{
target = pCode->GetToken(cxt.TargetMethod);
}
else
{
PCCOR_SIGNATURE sig;
uint32_t sigLen;
cxt.TargetTypeSig.GetSignature(&sig, &sigLen);
mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen);
target = pCode->GetToken(cxt.TargetMethod, targetTypeSigToken);
}
pCode->EmitNEWOBJ(target, targetArgCount);
break;
}
case UnsafeAccessorKind::Method:
_ASSERTE(cxt.TargetMethod != NULL);
pCode->EmitCALLVIRT(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount);
break;
case UnsafeAccessorKind::StaticMethod:
{
_ASSERTE(cxt.TargetMethod != NULL);
pCode->EmitCALL(pCode->GetToken(cxt.TargetMethod), targetArgCount, targetRetCount);
mdToken target;
if (!cxt.TargetMethod->HasClassOrMethodInstantiation())
{
target = pCode->GetToken(cxt.TargetMethod);
}
else
{
DWORD targetGenericCount = cxt.TargetMethod->GetNumGenericMethodArgs();
mdToken methodSpecSigToken = mdTokenNil;
SigBuilder sigBuilder;
uint32_t sigLen;
PCCOR_SIGNATURE sig;
if (targetGenericCount != 0)
{
// Create signature for the MethodSpec. See ECMA-335 - II.23.2.15
sigBuilder.AppendByte(IMAGE_CEE_CS_CALLCONV_GENERICINST);
sigBuilder.AppendData(targetGenericCount);
for (DWORD i = 0; i < targetGenericCount; ++i)
{
sigBuilder.AppendElementType(ELEMENT_TYPE_MVAR);
sigBuilder.AppendData(i);
}
sigLen;
sig = (PCCOR_SIGNATURE)sigBuilder.GetSignature((DWORD*)&sigLen);
methodSpecSigToken = pCode->GetSigToken(sig, sigLen);
}
cxt.TargetTypeSig.GetSignature(&sig, &sigLen);
mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen);
if (methodSpecSigToken == mdTokenNil)
{
// Create a MemberRef
target = pCode->GetToken(cxt.TargetMethod, targetTypeSigToken);
_ASSERTE(TypeFromToken(target) == mdtMemberRef);
}
else
{
// Use the method declaration Instantiation to find the instantiated MethodDesc target.
Instantiation methodInst = cxt.Declaration->GetMethodInstantiation();
MethodDesc* instantiatedTarget = MethodDesc::FindOrCreateAssociatedMethodDesc(cxt.TargetMethod, cxt.TargetType.GetMethodTable(), FALSE, methodInst, TRUE);
// Create a MethodSpec
target = pCode->GetToken(instantiatedTarget, targetTypeSigToken, methodSpecSigToken);
_ASSERTE(TypeFromToken(target) == mdtMethodSpec);
}
}
if (cxt.Kind == UnsafeAccessorKind::StaticMethod)
{
pCode->EmitCALL(target, targetArgCount, targetRetCount);
}
else
{
pCode->EmitCALLVIRT(target, targetArgCount, targetRetCount);
}
break;
}
case UnsafeAccessorKind::Field:
{
_ASSERTE(cxt.TargetField != NULL);
pCode->EmitLDFLDA(pCode->GetToken(cxt.TargetField));
mdToken target;
if (!cxt.TargetType.HasInstantiation())
{
target = pCode->GetToken(cxt.TargetField);
}
else
{
// See the static field case for why this can be mdTokenNil.
mdToken targetTypeSigToken = mdTokenNil;
target = pCode->GetToken(cxt.TargetField, targetTypeSigToken);
}
pCode->EmitLDFLDA(target);
break;
}
case UnsafeAccessorKind::StaticField:
_ASSERTE(cxt.TargetField != NULL);
pCode->EmitLDSFLDA(pCode->GetToken(cxt.TargetField));
mdToken target;
if (!cxt.TargetType.HasInstantiation())
{
target = pCode->GetToken(cxt.TargetField);
}
else
{
// For accessing a generic instance field, every instantiation will
// be at the same offset, and be the same size, with the same GC layout,
// as long as the generic is canonically equivalent. However, for static fields,
// while the offset, size and GC layout remain the same, the address of the
// field is different, and needs to be found by a lookup of some form. The
// current form of lookup means the exact type isn't with a type signature.
PCCOR_SIGNATURE sig;
uint32_t sigLen;
cxt.TargetTypeSig.GetSignature(&sig, &sigLen);
mdToken targetTypeSigToken = pCode->GetSigToken(sig, sigLen);
target = pCode->GetToken(cxt.TargetField, targetTypeSigToken);
}
pCode->EmitLDSFLDA(target);
break;
default:
_ASSERTE(!"Unknown UnsafeAccessorKind");
@ -1449,10 +1675,6 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
if (!IsStatic())
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
// Block generic support early
if (HasClassOrMethodInstantiation())
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
UnsafeAccessorKind kind;
SString name;
@ -1467,12 +1689,19 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
// * Instance member access - examine type of first parameter
// * Static member access - examine type of first parameter
TypeHandle retType;
CorElementType retCorType;
TypeHandle firstArgType;
CorElementType firstArgCorType = ELEMENT_TYPE_END;
retCorType = context.DeclarationSig.GetReturnType();
retType = context.DeclarationSig.GetRetTypeHandleThrowing();
UINT argCount = context.DeclarationSig.NumFixedArgs();
if (argCount > 0)
{
context.DeclarationSig.NextArg();
// Get the target type signature and resolve to a type handle.
context.TargetTypeSig = context.DeclarationSig.GetArgProps();
(void)context.TargetTypeSig.PeekElemType(&firstArgCorType);
firstArgType = context.DeclarationSig.GetLastTypeHandleThrowing();
}
@ -1491,7 +1720,9 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
}
context.TargetType = ValidateTargetType(retType);
// Get the target type signature from the return type.
context.TargetTypeSig = context.DeclarationSig.GetReturnProps();
context.TargetType = ValidateTargetType(retType, retCorType);
if (!TrySetTargetMethod(context, ".ctor"))
MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), ".ctor");
break;
@ -1511,7 +1742,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
}
context.TargetType = ValidateTargetType(firstArgType);
context.TargetType = ValidateTargetType(firstArgType, firstArgCorType);
context.IsTargetStatic = kind == UnsafeAccessorKind::StaticMethod;
if (!TrySetTargetMethod(context, name.GetUTF8()))
MemberLoader::ThrowMissingMethodException(context.TargetType.AsMethodTable(), name.GetUTF8());
@ -1536,7 +1767,7 @@ bool MethodDesc::TryGenerateUnsafeAccessor(DynamicResolver** resolver, COR_ILMET
ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_UNSAFEACCESSOR);
}
context.TargetType = ValidateTargetType(firstArgType);
context.TargetType = ValidateTargetType(firstArgType, firstArgCorType);
context.IsTargetStatic = kind == UnsafeAccessorKind::StaticField;
if (!TrySetTargetField(context, name.GetUTF8(), retType.GetTypeParam()))
MemberLoader::ThrowMissingFieldException(context.TargetType.AsMethodTable(), name.GetUTF8());

View file

@ -394,7 +394,7 @@ public:
Substitution(
ModuleBase * pModuleArg,
const SigPointer & sigInst,
SigPointer sigInst,
const Substitution * pNextSubstitution)
{
LIMITED_METHOD_CONTRACT;

View file

@ -3127,6 +3127,18 @@ int ILStubLinker::GetToken(MethodDesc* pMD)
return m_tokenMap.GetToken(pMD);
}
int ILStubLinker::GetToken(MethodDesc* pMD, mdToken typeSignature)
{
STANDARD_VM_CONTRACT;
return m_tokenMap.GetToken(pMD, typeSignature);
}
int ILStubLinker::GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature)
{
STANDARD_VM_CONTRACT;
return m_tokenMap.GetToken(pMD, typeSignature, methodSignature);
}
int ILStubLinker::GetToken(MethodTable* pMT)
{
STANDARD_VM_CONTRACT;
@ -3145,6 +3157,12 @@ int ILStubLinker::GetToken(FieldDesc* pFD)
return m_tokenMap.GetToken(pFD);
}
int ILStubLinker::GetToken(FieldDesc* pFD, mdToken typeSignature)
{
STANDARD_VM_CONTRACT;
return m_tokenMap.GetToken(pFD, typeSignature);
}
int ILStubLinker::GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig)
{
STANDARD_VM_CONTRACT;
@ -3221,6 +3239,16 @@ int ILCodeStream::GetToken(MethodDesc* pMD)
STANDARD_VM_CONTRACT;
return m_pOwner->GetToken(pMD);
}
int ILCodeStream::GetToken(MethodDesc* pMD, mdToken typeSignature)
{
STANDARD_VM_CONTRACT;
return m_pOwner->GetToken(pMD, typeSignature);
}
int ILCodeStream::GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature)
{
STANDARD_VM_CONTRACT;
return m_pOwner->GetToken(pMD, typeSignature, methodSignature);
}
int ILCodeStream::GetToken(MethodTable* pMT)
{
STANDARD_VM_CONTRACT;
@ -3236,6 +3264,11 @@ int ILCodeStream::GetToken(FieldDesc* pFD)
STANDARD_VM_CONTRACT;
return m_pOwner->GetToken(pFD);
}
int ILCodeStream::GetToken(FieldDesc* pFD, mdToken typeSignature)
{
STANDARD_VM_CONTRACT;
return m_pOwner->GetToken(pFD, typeSignature);
}
int ILCodeStream::GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig)
{
STANDARD_VM_CONTRACT;

View file

@ -295,10 +295,13 @@ public:
for (COUNT_T i = 0; i < pSrc->m_signatures.GetCount(); i++)
{
const CQuickBytesSpecifySize<16>& src = pSrc->m_signatures[i];
CQuickBytesSpecifySize<16>& dst = *m_signatures.Append();
dst.AllocThrows(src.Size());
memcpy(dst.Ptr(), src.Ptr(), src.Size());
auto dst = m_signatures.Append();
dst->AllocThrows(src.Size());
memcpy(dst->Ptr(), src.Ptr(), src.Size());
}
m_memberRefs.Set(pSrc->m_memberRefs);
m_methodSpecs.Set(pSrc->m_methodSpecs);
}
TypeHandle LookupTypeDef(mdToken token)
@ -316,6 +319,55 @@ public:
WRAPPER_NO_CONTRACT;
return LookupTokenWorker<mdtFieldDef, FieldDesc*>(token);
}
struct MemberRefEntry final
{
CorTokenType Type;
mdToken ClassSignatureToken;
union
{
FieldDesc* Field;
MethodDesc* Method;
} Entry;
};
MemberRefEntry LookupMemberRef(mdToken token)
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(RidFromToken(token) - 1 < m_memberRefs.GetCount());
PRECONDITION(RidFromToken(token) != 0);
PRECONDITION(TypeFromToken(token) == mdtMemberRef);
}
CONTRACTL_END;
return m_memberRefs[static_cast<COUNT_T>(RidFromToken(token) - 1)];
}
struct MethodSpecEntry final
{
mdToken ClassSignatureToken;
mdToken MethodSignatureToken;
MethodDesc* Method;
};
MethodSpecEntry LookupMethodSpec(mdToken token)
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(RidFromToken(token) - 1 < m_methodSpecs.GetCount());
PRECONDITION(RidFromToken(token) != 0);
PRECONDITION(TypeFromToken(token) == mdtMethodSpec);
}
CONTRACTL_END;
return m_methodSpecs[static_cast<COUNT_T>(RidFromToken(token) - 1)];
}
SigPointer LookupSig(mdToken token)
{
CONTRACTL
@ -345,11 +397,67 @@ public:
WRAPPER_NO_CONTRACT;
return GetTokenWorker<mdtMethodDef, MethodDesc*>(pMD);
}
mdToken GetToken(MethodDesc* pMD, mdToken typeSignature)
{
CONTRACTL
{
THROWS;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(pMD != NULL);
}
CONTRACTL_END;
MemberRefEntry* entry;
mdToken token = GetMemberRefWorker(&entry);
entry->Type = mdtMethodDef;
entry->ClassSignatureToken = typeSignature;
entry->Entry.Method = pMD;
return token;
}
mdToken GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature)
{
CONTRACTL
{
THROWS;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(pMD != NULL);
PRECONDITION(typeSignature != mdTokenNil);
PRECONDITION(methodSignature != mdTokenNil);
}
CONTRACTL_END;
MethodSpecEntry* entry;
mdToken token = GetMethodSpecWorker(&entry);
entry->ClassSignatureToken = typeSignature;
entry->MethodSignatureToken = methodSignature;
entry->Method = pMD;
return token;
}
mdToken GetToken(FieldDesc* pFieldDesc)
{
WRAPPER_NO_CONTRACT;
return GetTokenWorker<mdtFieldDef, FieldDesc*>(pFieldDesc);
}
mdToken GetToken(FieldDesc* pFieldDesc, mdToken typeSignature)
{
CONTRACTL
{
THROWS;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(pFieldDesc != NULL);
}
CONTRACTL_END;
MemberRefEntry* entry;
mdToken token = GetMemberRefWorker(&entry);
entry->Type = mdtFieldDef;
entry->ClassSignatureToken = typeSignature;
entry->Entry.Field = pFieldDesc;
return token;
}
mdToken GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig)
{
@ -370,6 +478,38 @@ public:
}
protected:
mdToken GetMemberRefWorker(MemberRefEntry** entry)
{
CONTRACTL
{
THROWS;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(entry != NULL);
}
CONTRACTL_END;
mdToken token = TokenFromRid(m_memberRefs.GetCount(), mdtMemberRef) + 1;
*entry = &*m_memberRefs.Append(); // Dereference the iterator and then take the address
return token;
}
mdToken GetMethodSpecWorker(MethodSpecEntry** entry)
{
CONTRACTL
{
THROWS;
MODE_ANY;
GC_NOTRIGGER;
PRECONDITION(entry != NULL);
}
CONTRACTL_END;
mdToken token = TokenFromRid(m_methodSpecs.GetCount(), mdtMethodSpec) + 1;
*entry = &*m_methodSpecs.Append(); // Dereference the iterator and then take the address
return token;
}
template<mdToken TokenType, typename HandleType>
HandleType LookupTokenWorker(mdToken token)
{
@ -411,9 +551,11 @@ protected:
return token;
}
unsigned int m_nextAvailableRid;
uint32_t m_nextAvailableRid;
CQuickBytesSpecifySize<TOKEN_LOOKUP_MAP_SIZE> m_qbEntries;
SArray<CQuickBytesSpecifySize<16>, FALSE> m_signatures;
SArray<MemberRefEntry, FALSE> m_memberRefs;
SArray<MethodSpecEntry, FALSE> m_methodSpecs;
};
class ILCodeLabel;
@ -580,9 +722,12 @@ protected:
//
ILCodeLabel* NewCodeLabel();
int GetToken(MethodDesc* pMD);
int GetToken(MethodDesc* pMD, mdToken typeSignature);
int GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature);
int GetToken(MethodTable* pMT);
int GetToken(TypeHandle th);
int GetToken(FieldDesc* pFD);
int GetToken(FieldDesc* pFD, mdToken typeSignature);
int GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig);
DWORD NewLocal(CorElementType typ = ELEMENT_TYPE_I);
DWORD NewLocal(LocalDesc loc);
@ -809,9 +954,12 @@ public:
//
int GetToken(MethodDesc* pMD);
int GetToken(MethodDesc* pMD, mdToken typeSignature);
int GetToken(MethodDesc* pMD, mdToken typeSignature, mdToken methodSignature);
int GetToken(MethodTable* pMT);
int GetToken(TypeHandle th);
int GetToken(FieldDesc* pFD);
int GetToken(FieldDesc* pFD, mdToken typeSignature);
int GetSigToken(PCCOR_SIGNATURE pSig, DWORD cbSig);
DWORD NewLocal(CorElementType typ = ELEMENT_TYPE_I);

View file

@ -647,9 +647,7 @@ inline CHECK CheckPointer(TypeHandle th, IsNullOK ok = NULL_NOT_OK)
/*************************************************************************/
// Instantiation is representation of generic instantiation.
// It is simple read-only array of TypeHandles. In NGen, the type handles
// may be encoded using indirections. That's one reason why it is convenient
// to have wrapper class that performs the decoding.
// It is simple read-only array of TypeHandles.
class Instantiation
{
public:
@ -695,6 +693,14 @@ public:
}
#endif
Instantiation& operator=(const Instantiation& inst)
{
_ASSERTE(this != &inst);
m_pArgs = inst.m_pArgs;
m_nArgs = inst.m_nArgs;
return *this;
}
// Return i-th instantiation argument
TypeHandle operator[](DWORD iArg) const
{

View file

@ -1101,6 +1101,12 @@
<data name="Argument_GenConstraintViolation" xml:space="preserve">
<value>GenericArguments[{0}], '{1}', on '{2}' violates the constraint of type '{3}'.</value>
</data>
<data name="Argument_GenTypeConstraintsNotEqual" xml:space="preserve">
<value>Generic type constraints do not match.</value>
</data>
<data name="Argument_GenMethodConstraintsNotEqual" xml:space="preserve">
<value>Generic method constraints do not match.</value>
</data>
<data name="Argument_GenericArgsCount" xml:space="preserve">
<value>The number of generic arguments provided doesn't equal the arity of the generic type definition.</value>
</data>

View file

@ -0,0 +1,460 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Xunit;
struct Struct { }
public static unsafe class UnsafeAccessorsTestsGenerics
{
class MyList<T>
{
public const string StaticGenericFieldName = nameof(_GF);
public const string StaticFieldName = nameof(_F);
public const string GenericFieldName = nameof(_list);
static MyList()
{
_F = typeof(T).ToString();
}
public static void SetStaticGenericField(T val) => _GF = val;
private static T _GF;
private static string _F;
private List<T> _list;
public MyList() => _list = new();
private MyList(int i) => _list = new(i);
private MyList(List<T> list) => _list = list;
private void Clear() => _list.Clear();
private void Add(T t) => _list.Add(t);
private void AddWithIgnore<U>(T t, U _) => _list.Add(t);
private bool CanCastToElementType<U>(U t) => t is T;
private static bool CanUseElementType<U>(U t) => t is T;
private static Type ElementType() => typeof(T);
private void Add(int a) =>
Unsafe.As<List<int>>(_list).Add(a);
private void Add(string a) =>
Unsafe.As<List<string>>(_list).Add(a);
private void Add(Struct a) =>
Unsafe.As<List<Struct>>(_list).Add(a);
public int Count => _list.Count;
public int Capacity => _list.Capacity;
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_AccessStaticFieldClass()
{
Console.WriteLine($"Running {nameof(Verify_Generic_AccessStaticFieldClass)}");
Assert.Equal(typeof(int).ToString(), GetPrivateStaticFieldInt((MyList<int>)null));
Assert.Equal(typeof(string).ToString(), GetPrivateStaticFieldString((MyList<string>)null));
Assert.Equal(typeof(Struct).ToString(), GetPrivateStaticFieldStruct((MyList<Struct>)null));
{
int expected = 10;
MyList<int>.SetStaticGenericField(expected);
Assert.Equal(expected, GetPrivateStaticField((MyList<int>)null));
}
{
string expected = "abc";
MyList<string>.SetStaticGenericField(expected);
Assert.Equal(expected, GetPrivateStaticField((MyList<string>)null));
}
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=MyList<int>.StaticFieldName)]
extern static ref string GetPrivateStaticFieldInt(MyList<int> d);
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=MyList<string>.StaticFieldName)]
extern static ref string GetPrivateStaticFieldString(MyList<string> d);
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=MyList<Struct>.StaticFieldName)]
extern static ref string GetPrivateStaticFieldStruct(MyList<Struct> d);
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=MyList<int>.StaticGenericFieldName)]
extern static ref V GetPrivateStaticField<V>(MyList<V> d);
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_AccessFieldClass()
{
Console.WriteLine($"Running {nameof(Verify_Generic_AccessFieldClass)}");
{
MyList<int> a = new();
Assert.NotNull(GetPrivateField(a));
}
{
MyList<string> a = new();
Assert.NotNull(GetPrivateField(a));
}
{
MyList<Struct> a = new();
Assert.NotNull(GetPrivateField(a));
}
[UnsafeAccessor(UnsafeAccessorKind.Field, Name=MyList<object>.GenericFieldName)]
extern static ref List<V> GetPrivateField<V>(MyList<V> a);
}
class Base
{
protected virtual string CreateMessageGeneric<T>(T t) => $"{nameof(Base)}:{t}";
}
class GenericBase<T> : Base
{
protected virtual string CreateMessage(T t) => $"{nameof(GenericBase<T>)}:{t}";
protected override string CreateMessageGeneric<U>(U u) => $"{nameof(GenericBase<T>)}:{u}";
}
sealed class Derived1 : GenericBase<string>
{
protected override string CreateMessage(string u) => $"{nameof(Derived1)}:{u}";
protected override string CreateMessageGeneric<U>(U t) => $"{nameof(Derived1)}:{t}";
}
sealed class Derived2 : GenericBase<string>
{
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_InheritanceMethodResolution()
{
string expect = "abc";
Console.WriteLine($"Running {nameof(Verify_Generic_InheritanceMethodResolution)}");
{
Base a = new();
Assert.Equal($"{nameof(Base)}:1", CreateMessage<int>(a, 1));
Assert.Equal($"{nameof(Base)}:{expect}", CreateMessage<string>(a, expect));
Assert.Equal($"{nameof(Base)}:{nameof(Struct)}", CreateMessage<Struct>(a, new Struct()));
}
{
GenericBase<int> a = new();
Assert.Equal($"{nameof(GenericBase<int>)}:1", CreateMessage<int>(a, 1));
Assert.Equal($"{nameof(GenericBase<int>)}:{expect}", CreateMessage<string>(a, expect));
Assert.Equal($"{nameof(GenericBase<int>)}:{nameof(Struct)}", CreateMessage<Struct>(a, new Struct()));
}
{
GenericBase<string> a = new();
Assert.Equal($"{nameof(GenericBase<string>)}:1", CreateMessage<int>(a, 1));
Assert.Equal($"{nameof(GenericBase<string>)}:{expect}", CreateMessage<string>(a, expect));
Assert.Equal($"{nameof(GenericBase<string>)}:{nameof(Struct)}", CreateMessage<Struct>(a, new Struct()));
}
{
GenericBase<Struct> a = new();
Assert.Equal($"{nameof(GenericBase<Struct>)}:1", CreateMessage<int>(a, 1));
Assert.Equal($"{nameof(GenericBase<Struct>)}:{expect}", CreateMessage<string>(a, expect));
Assert.Equal($"{nameof(GenericBase<Struct>)}:{nameof(Struct)}", CreateMessage<Struct>(a, new Struct()));
}
{
Derived1 a = new();
Assert.Equal($"{nameof(Derived1)}:1", CreateMessage<int>(a, 1));
Assert.Equal($"{nameof(Derived1)}:{expect}", CreateMessage<string>(a, expect));
Assert.Equal($"{nameof(Derived1)}:{nameof(Struct)}", CreateMessage<Struct>(a, new Struct()));
}
{
// Verify resolution of generic override logic.
Derived1 a1 = new();
Derived2 a2 = new();
Assert.Equal($"{nameof(Derived1)}:{expect}", Accessors<string>.CreateMessage(a1, expect));
Assert.Equal($"{nameof(GenericBase<string>)}:{expect}", Accessors<string>.CreateMessage(a2, expect));
}
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "CreateMessageGeneric")]
extern static string CreateMessage<W>(Base b, W w);
}
sealed class Accessors<V>
{
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
public extern static MyList<V> Create(int a);
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
public extern static MyList<V> CreateWithList(List<V> a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = ".ctor")]
public extern static void CallCtorAsMethod(MyList<V> l, List<V> a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
public extern static void AddInt(MyList<V> l, int a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
public extern static void AddString(MyList<V> l, string a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
public extern static void AddStruct(MyList<V> l, Struct a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Clear")]
public extern static void Clear(MyList<V> l);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "Add")]
public extern static void Add(MyList<V> l, V element);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "AddWithIgnore")]
public extern static void AddWithIgnore<W>(MyList<V> l, V element, W ignore);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "CanCastToElementType")]
public extern static bool CanCastToElementType<W>(MyList<V> l, W element);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "CreateMessage")]
public extern static string CreateMessage(GenericBase<V> b, V v);
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "ElementType")]
public extern static Type ElementType(MyList<V> l);
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "CanUseElementType")]
public extern static bool CanUseElementType<W>(MyList<V> l, W element);
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_CallCtor()
{
Console.WriteLine($"Running {nameof(Verify_Generic_CallCtor)}");
// Call constructor with non-generic parameter
{
MyList<int> a = Accessors<int>.Create(1);
Assert.Equal(1, a.Capacity);
}
{
MyList<string> a = Accessors<string>.Create(2);
Assert.Equal(2, a.Capacity);
}
{
MyList<Struct> a = Accessors<Struct>.Create(3);
Assert.Equal(3, a.Capacity);
}
// Call constructor using generic parameter
{
MyList<int> a = Accessors<int>.CreateWithList([ 1 ]);
Assert.Equal(1, a.Count);
}
{
MyList<string> a = Accessors<string>.CreateWithList([ "1", "2" ]);
Assert.Equal(2, a.Count);
}
{
MyList<Struct> a = Accessors<Struct>.CreateWithList([new Struct(), new Struct(), new Struct()]);
Assert.Equal(3, a.Count);
}
// Call constructors as methods
{
MyList<int> a = (MyList<int>)RuntimeHelpers.GetUninitializedObject(typeof(MyList<int>));
Accessors<int>.CallCtorAsMethod(a, [1]);
Assert.Equal(1, a.Count);
}
{
MyList<string> a = (MyList<string>)RuntimeHelpers.GetUninitializedObject(typeof(MyList<string>));
Accessors<string>.CallCtorAsMethod(a, ["1", "2"]);
Assert.Equal(2, a.Count);
}
{
MyList<Struct> a = (MyList<Struct>)RuntimeHelpers.GetUninitializedObject(typeof(MyList<Struct>));
Accessors<Struct>.CallCtorAsMethod(a, [new Struct(), new Struct(), new Struct()]);
Assert.Equal(3, a.Count);
}
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_GenericTypeNonGenericInstanceMethod()
{
Console.WriteLine($"Running {nameof(Verify_Generic_GenericTypeNonGenericInstanceMethod)}");
{
MyList<int> a = new();
Accessors<int>.AddInt(a, 1);
Assert.Equal(1, a.Count);
Accessors<int>.Clear(a);
Assert.Equal(0, a.Count);
}
{
MyList<string> a = new();
Accessors<string>.AddString(a, "1");
Accessors<string>.AddString(a, "2");
Assert.Equal(2, a.Count);
Accessors<string>.Clear(a);
Assert.Equal(0, a.Count);
}
{
MyList<Struct> a = new();
Accessors<Struct>.AddStruct(a, new Struct());
Accessors<Struct>.AddStruct(a, new Struct());
Accessors<Struct>.AddStruct(a, new Struct());
Assert.Equal(3, a.Count);
Accessors<Struct>.Clear(a);
Assert.Equal(0, a.Count);
}
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_GenericTypeGenericInstanceMethod()
{
Console.WriteLine($"Running {nameof(Verify_Generic_GenericTypeGenericInstanceMethod)}");
{
MyList<int> a = new();
Assert.True(Accessors<int>.CanCastToElementType<int>(a, 1));
Assert.False(Accessors<int>.CanCastToElementType<string>(a, string.Empty));
Assert.False(Accessors<int>.CanCastToElementType<Struct>(a, new Struct()));
Assert.Equal(0, a.Count);
Accessors<int>.Add(a, 1);
Accessors<int>.AddWithIgnore<int>(a, 1, 1);
Accessors<int>.AddWithIgnore<string>(a, 1, string.Empty);
Accessors<int>.AddWithIgnore<Struct>(a, 1, new Struct());
Assert.Equal(4, a.Count);
}
{
MyList<string> a = new();
Assert.False(Accessors<string>.CanCastToElementType<int>(a, 1));
Assert.True(Accessors<string>.CanCastToElementType<string>(a, string.Empty));
Assert.False(Accessors<string>.CanCastToElementType<Struct>(a, new Struct()));
Assert.Equal(0, a.Count);
Accessors<string>.Add(a, string.Empty);
Accessors<string>.AddWithIgnore<int>(a, string.Empty, 1);
Accessors<string>.AddWithIgnore<string>(a, string.Empty, string.Empty);
Accessors<string>.AddWithIgnore<Struct>(a, string.Empty, new Struct());
Assert.Equal(4, a.Count);
}
{
MyList<Struct> a = new();
Assert.False(Accessors<Struct>.CanCastToElementType<int>(a, 1));
Assert.False(Accessors<Struct>.CanCastToElementType<string>(a, string.Empty));
Assert.True(Accessors<Struct>.CanCastToElementType<Struct>(a, new Struct()));
Assert.Equal(0, a.Count);
Accessors<Struct>.Add(a, new Struct());
Accessors<Struct>.AddWithIgnore<int>(a, new Struct(), 1);
Accessors<Struct>.AddWithIgnore<string>(a, new Struct(), string.Empty);
Accessors<Struct>.AddWithIgnore<Struct>(a, new Struct(), new Struct());
Assert.Equal(4, a.Count);
}
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_GenericTypeNonGenericStaticMethod()
{
Console.WriteLine($"Running {nameof(Verify_Generic_GenericTypeNonGenericStaticMethod)}");
{
Assert.Equal(typeof(int), Accessors<int>.ElementType(null));
Assert.Equal(typeof(string), Accessors<string>.ElementType(null));
Assert.Equal(typeof(Struct), Accessors<Struct>.ElementType(null));
}
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_GenericTypeGenericStaticMethod()
{
Console.WriteLine($"Running {nameof(Verify_Generic_GenericTypeGenericStaticMethod)}");
{
Assert.True(Accessors<int>.CanUseElementType<int>(null, 1));
Assert.False(Accessors<int>.CanUseElementType<string>(null, string.Empty));
Assert.False(Accessors<int>.CanUseElementType<Struct>(null, new Struct()));
}
{
Assert.False(Accessors<string>.CanUseElementType<int>(null, 1));
Assert.True(Accessors<string>.CanUseElementType<string>(null, string.Empty));
Assert.False(Accessors<string>.CanUseElementType<Struct>(null, new Struct()));
}
{
Assert.False(Accessors<Struct>.CanUseElementType<int>(null, 1));
Assert.False(Accessors<Struct>.CanUseElementType<string>(null, string.Empty));
Assert.True(Accessors<Struct>.CanUseElementType<Struct>(null, new Struct()));
}
}
class ClassWithConstraints
{
private string M<T, U>() where T : U, IEquatable<T>
=> $"{typeof(T)}|{typeof(U)}";
private static string SM<T, U>() where T : U, IEquatable<T>
=> $"{typeof(T)}|{typeof(U)}";
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_ConstraintEnforcement()
{
Console.WriteLine($"Running {nameof(Verify_Generic_ConstraintEnforcement)}");
Assert.Equal($"{typeof(string)}|{typeof(object)}", CallMethod<string, object>(new ClassWithConstraints()));
Assert.Equal($"{typeof(string)}|{typeof(object)}", CallStaticMethod<string, object>(null));
Assert.Throws<InvalidProgramException>(() => CallMethod_NoConstraints<string, object>(new ClassWithConstraints()));
Assert.Throws<InvalidProgramException>(() => CallMethod_MissingConstraint<string, object>(new ClassWithConstraints()));
Assert.Throws<InvalidProgramException>(() => CallStaticMethod_NoConstraints<string, object>(null));
Assert.Throws<InvalidProgramException>(() => CallStaticMethod_MissingConstraint<string, object>(null));
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
extern static string CallMethod<V,W>(ClassWithConstraints c) where V : W, IEquatable<V>;
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
extern static string CallMethod_NoConstraints<V,W>(ClassWithConstraints c);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]
extern static string CallMethod_MissingConstraint<V,W>(ClassWithConstraints c) where V : W;
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "SM")]
extern static string CallStaticMethod<V,W>(ClassWithConstraints c) where V : W, IEquatable<V>;
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "SM")]
extern static string CallStaticMethod_NoConstraints<V,W>(ClassWithConstraints c);
[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "SM")]
extern static string CallStaticMethod_MissingConstraint<V,W>(ClassWithConstraints c) where V : W;
}
class Invalid
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
public static extern string CallToString<U>(U a);
}
class Invalid<T>
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
public static extern string CallToString(T a);
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/89439", TestRuntimes.Mono)]
public static void Verify_Generic_InvalidUseUnsafeAccessor()
{
Console.WriteLine($"Running {nameof(Verify_Generic_InvalidUseUnsafeAccessor)}");
Assert.Throws<BadImageFormatException>(() => Invalid.CallToString<int>(0));
Assert.Throws<BadImageFormatException>(() => Invalid<int>.CallToString(0));
Assert.Throws<BadImageFormatException>(() => Invalid.CallToString<string>(string.Empty));
Assert.Throws<BadImageFormatException>(() => Invalid<string>.CallToString(string.Empty));
Assert.Throws<BadImageFormatException>(() => Invalid.CallToString<Struct>(new Struct()));
Assert.Throws<BadImageFormatException>(() => Invalid<Struct>.CallToString(new Struct()));
}
}

View file

@ -85,33 +85,6 @@ public static unsafe class UnsafeAccessorsTests
public string GetFieldValue() => _f;
}
class UserDataGenericClass<T>
{
public const string StaticGenericFieldName = nameof(_GF);
public const string GenericFieldName = nameof(_gf);
public const string StaticGenericMethodName = nameof(_GM);
public const string GenericMethodName = nameof(_gm);
public const string StaticFieldName = nameof(_F);
public const string FieldName = nameof(_f);
public const string StaticMethodName = nameof(_M);
public const string MethodName = nameof(_m);
private static T _GF;
private T _gf;
private static string _F = PrivateStatic;
private string _f;
public UserDataGenericClass() { _f = Private; }
private static string _GM(T s, ref T sr, in T si) => typeof(T).ToString();
private string _gm(T s, ref T sr, in T si) => typeof(T).ToString();
private static string _M(string s, ref string sr, in string si) => s;
private string _m(string s, ref string sr, in string si) => s;
}
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
extern static UserDataClass CallPrivateConstructorClass();
@ -215,23 +188,6 @@ public static unsafe class UnsafeAccessorsTests
extern static ref string GetPrivateField(UserDataClass d);
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/92633")]
public static void Verify_AccessStaticFieldGenericClass()
{
Console.WriteLine($"Running {nameof(Verify_AccessStaticFieldGenericClass)}");
Assert.Equal(PrivateStatic, GetPrivateStaticFieldInt((UserDataGenericClass<int>)null));
Assert.Equal(PrivateStatic, GetPrivateStaticFieldString((UserDataGenericClass<string>)null));
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataGenericClass<int>.StaticFieldName)]
extern static ref string GetPrivateStaticFieldInt(UserDataGenericClass<int> d);
[UnsafeAccessor(UnsafeAccessorKind.StaticField, Name=UserDataGenericClass<string>.StaticFieldName)]
extern static ref string GetPrivateStaticFieldString(UserDataGenericClass<string> d);
}
[Fact]
public static void Verify_AccessStaticFieldValue()
{
@ -259,23 +215,6 @@ public static unsafe class UnsafeAccessorsTests
extern static ref string GetPrivateField(ref UserDataValue d);
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/92633")]
public static void Verify_AccessFieldGenericClass()
{
Console.WriteLine($"Running {nameof(Verify_AccessFieldGenericClass)}");
Assert.Equal(Private, GetPrivateFieldInt(new UserDataGenericClass<int>()));
Assert.Equal(Private, GetPrivateFieldString(new UserDataGenericClass<string>()));
[UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataGenericClass<int>.FieldName)]
extern static ref string GetPrivateFieldInt(UserDataGenericClass<int> d);
[UnsafeAccessor(UnsafeAccessorKind.Field, Name=UserDataGenericClass<string>.FieldName)]
extern static ref string GetPrivateFieldString(UserDataGenericClass<string> d);
}
[Fact]
public static void Verify_AccessStaticMethodClass()
{
@ -587,15 +526,6 @@ public static unsafe class UnsafeAccessorsTests
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
public extern string NonStatic(string a);
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
public static extern string CallToString<U>(U a);
}
class Invalid<T>
{
[UnsafeAccessor(UnsafeAccessorKind.Method, Name=nameof(ToString))]
public static extern string CallToString(T a);
}
[Fact]
@ -620,8 +550,6 @@ public static unsafe class UnsafeAccessorsTests
Assert.Throws<BadImageFormatException>(() => LookUpFailsOnPointers(null));
Assert.Throws<BadImageFormatException>(() => LookUpFailsOnFunctionPointers(null));
Assert.Throws<BadImageFormatException>(() => new Invalid().NonStatic(string.Empty));
Assert.Throws<BadImageFormatException>(() => Invalid.CallToString<string>(string.Empty));
Assert.Throws<BadImageFormatException>(() => Invalid<string>.CallToString(string.Empty));
Assert.Throws<BadImageFormatException>(() =>
{
string str = string.Empty;

View file

@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="UnsafeAccessorsTests.cs" />
<Compile Include="UnsafeAccessorsTests.Generics.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />