Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Enable Invoke and GetValue for ref-returning members #17639

Merged
4 commits merged into from Apr 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/dlls/mscorrc/mscorrc.rc
Expand Up @@ -1538,6 +1538,7 @@ BEGIN

IDS_EE_TORNSTATE "Unexpected change made to file '%1'."

IDS_INVOKE_NULLREF_RETURNED "The target method returned a null reference."
END

// These strings are generated from within the EE for streams
Expand Down
2 changes: 2 additions & 0 deletions src/dlls/mscorrc/resource.h
Expand Up @@ -898,3 +898,5 @@
#define IDS_EE_NDIRECT_LOADLIB_MAC 0x263f
#define IDS_EE_NDIRECT_GETPROCADDRESS_UNIX 0x2640
#define IDS_EE_ERROR_COM 0x2641

#define IDS_INVOKE_NULLREF_RETURNED 0x2642
4 changes: 2 additions & 2 deletions src/mscorlib/Resources/Strings.resx
Expand Up @@ -2935,8 +2935,8 @@
<data name="NotSupported_ByRefLikeArray" xml:space="preserve">
<value>Cannot create arrays of ByRef-like values.</value>
</data>
<data name="NotSupported_ByRefReturn" xml:space="preserve">
<value>ByRef return value not supported in reflection invocation.</value>
<data name="NotSupported_ByRefToByRefLikeReturn" xml:space="preserve">
<value>ByRef to ByRefLike return values not supported in reflection invocation.</value>
</data>
<data name="NotSupported_CallToVarArg" xml:space="preserve">
<value>Vararg calling convention not supported.</value>
Expand Down
8 changes: 4 additions & 4 deletions src/mscorlib/src/System/Reflection/RuntimeMethodInfo.cs
Expand Up @@ -41,7 +41,7 @@ internal INVOCATION_FLAGS InvocationFlags
//
// first take care of all the NO_INVOKE cases.
if (ContainsGenericParameters ||
ReturnType.IsByRef ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need IsByRefLike condition here as well? Or are the ByRefLike returns handled somewhere else?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That gets handled in the "else" branch (RuntimeMethodHandle.GetSecurityFlags(this);)

(ReturnType.IsByRef && ReturnType.GetElementType().IsByRefLike) ||
Copy link
Member

@jkotas jkotas Apr 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the message that will be thrown for this case in ThrowNoInvokeException ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

(declaringType != null && declaringType.ContainsGenericParameters) ||
((CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs))
{
Expand Down Expand Up @@ -443,10 +443,10 @@ private void ThrowNoInvokeException()
{
throw new MemberAccessException();
}
// ByRef return are not allowed in reflection
else if (ReturnType.IsByRef)
// ByRef to ByRefLike returns are not allowed in reflection
else if (ReturnType.IsByRef && ReturnType.GetElementType().IsByRefLike)
{
throw new NotSupportedException(SR.NotSupported_ByRefReturn);
throw new NotSupportedException(SR.NotSupported_ByRefToByRefLikeReturn);
}

throw new TargetException();
Expand Down
31 changes: 19 additions & 12 deletions src/vm/invokeutil.cpp
Expand Up @@ -649,9 +649,14 @@ void InvokeUtil::ValidField(TypeHandle th, OBJECTREF* value)
COMPlusThrow(kArgumentException,W("Arg_ObjObj"));
}

// InternalCreateObject
// This routine will create the specified object from the value
OBJECTREF InvokeUtil::CreateObject(TypeHandle th, void * pValue) {
//
// CreateObjectAfterInvoke
// This routine will create the specified object from the value returned by the Invoke target.
//
// This does not handle the ELEMENT_TYPE_VALUETYPE case. The caller must preallocate the box object and
// copy the value type into it afterward.
//
OBJECTREF InvokeUtil::CreateObjectAfterInvoke(TypeHandle th, void * pValue) {
CONTRACTL {
THROWS;
GC_TRIGGERS;
Expand All @@ -666,6 +671,9 @@ OBJECTREF InvokeUtil::CreateObject(TypeHandle th, void * pValue) {
MethodTable *pMT = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetSignatureCorElementType is a regular element type that you find in metadata signatures, etc.

GetInternalCorElementType used by the caller is relevant for calling convention - what gets passed in registers, etc.

The bug that broke corefx is when these two differ.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it broke enums. I'm thinking we should just strip CreateObject down to handling objects, pointers and function pointers and let the prealloc-and-copy code handle all value types.

OBJECTREF obj = NULL;

// WARNING: pValue can be an inner reference into a managed object and it is not protected from GC. You must do nothing that
// triggers a GC until the all the data it points to has been captured in a GC-protected location.

// Handle the non-table types
switch (type) {
case ELEMENT_TYPE_VOID:
Expand All @@ -682,12 +690,8 @@ OBJECTREF InvokeUtil::CreateObject(TypeHandle th, void * pValue) {
goto PrimitiveType;

case ELEMENT_TYPE_VALUETYPE:
{
_ASSERTE(!th.IsTypeDesc());
pMT = th.AsMethodTable();
obj = pMT->Box(pValue);
_ASSERTE(!"You cannot use this function for arbitrary value types. You must preallocate a box object and copy the value in yourself.");
break;
}

case ELEMENT_TYPE_CLASS: // Class
case ELEMENT_TYPE_SZARRAY: // Single Dim, Zero
Expand Down Expand Up @@ -718,14 +722,17 @@ OBJECTREF InvokeUtil::CreateObject(TypeHandle th, void * pValue) {
{
// Don't use MethodTable::Box here for perf reasons
PREFIX_ASSUME(pMT != NULL);
obj = AllocateObject(pMT);
DWORD size = pMT->GetNumInstanceFieldBytes();
memcpyNoGCRefs(obj->UnBox(), pValue, size);

UINT64 capturedValue;
memcpyNoGCRefs(&capturedValue, pValue, size); // Must capture the primitive value before we allocate the boxed object which can trigger a GC.

INDEBUG(pValue = (LPVOID)0xcccccccc); // We're about to allocate a GC object - can no longer trust pValue
obj = AllocateObject(pMT);
memcpyNoGCRefs(obj->UnBox(), &capturedValue, size);
}
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ELEMENT_TYPE_BYREF should not be needed anymore. Also, it is referring to resource string that no longer exists.


case ELEMENT_TYPE_BYREF:
COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefReturn"));
case ELEMENT_TYPE_END:
default:
_ASSERTE(!"Unknown Type");
Expand Down
2 changes: 1 addition & 1 deletion src/vm/invokeutil.h
Expand Up @@ -105,7 +105,7 @@ class InvokeUtil
// Given a type, this routine will convert an return value representing that
// type into an ObjectReference. If the type is a primitive, the
// value is wrapped in one of the Value classes.
static OBJECTREF CreateObject(TypeHandle th, void * pValue);
static OBJECTREF CreateObjectAfterInvoke(TypeHandle th, void * pValue);

// This is a special purpose Exception creation function. It
// creates the TargetInvocationExeption placing the passed
Expand Down
43 changes: 40 additions & 3 deletions src/vm/reflectioninvocation.cpp
Expand Up @@ -1165,12 +1165,28 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
// if we have the magic Value Class return, we need to allocate that class
// and place a pointer to it on the stack.

BOOL hasRefReturnAndNeedsBoxing = FALSE; // Indicates that the method has a BYREF return type and the target type needs to be copied into a preallocated boxed object.

TypeHandle retTH = gc.pSig->GetReturnTypeHandle();
TypeHandle refReturnTargetTH; // Valid only if retType == ELEMENT_TYPE_BYREF. Caches the TypeHandle of the byref target.
BOOL fHasRetBuffArg = argit.HasRetBuffArg();
CorElementType retType = retTH.GetInternalCorElementType();
if (retType == ELEMENT_TYPE_VALUETYPE || fHasRetBuffArg) {
gc.retVal = retTH.GetMethodTable()->Allocate();
}
else if (retType == ELEMENT_TYPE_BYREF)
{
refReturnTargetTH = retTH.AsTypeDesc()->GetTypeParam();
CorElementType refReturnTargetType = refReturnTargetTH.GetInternalCorElementType();

// If the target of the byref is a general valuetype (i.e. not one of the primitives), we need to preallocate a boxed object
// to hold the managed return value.
if (refReturnTargetType == ELEMENT_TYPE_VALUETYPE)
{
hasRefReturnAndNeedsBoxing = TRUE;
gc.retVal = refReturnTargetTH.GetMethodTable()->Allocate();
}
}

// Copy "this" pointer
if (!pMeth->IsStatic()) {
Expand Down Expand Up @@ -1396,13 +1412,23 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
gc.retVal = Nullable::NormalizeBox(gc.retVal);
}
else
if (retType == ELEMENT_TYPE_VALUETYPE)
if (retType == ELEMENT_TYPE_VALUETYPE || hasRefReturnAndNeedsBoxing)
{
_ASSERTE(gc.retVal != NULL);

if (hasRefReturnAndNeedsBoxing)
{
// Method has BYREF return and the target type is one that needs boxing. We need to copy into the boxed object we have allocated for this purpose.
LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue;
if (pReturnedReference == NULL)
{
COMPlusThrow(kNullReferenceException, IDS_INVOKE_NULLREF_RETURNED);
}
CopyValueClass(gc.retVal->GetData(), pReturnedReference, gc.retVal->GetMethodTable(), gc.retVal->GetAppDomain());
}
// if the structure is returned by value, then we need to copy in the boxed object
// we have allocated for this purpose.
if (!fHasRetBuffArg)
else if (!fHasRetBuffArg)
{
CopyValueClass(gc.retVal->GetData(), &callDescrData.returnValue, gc.retVal->GetMethodTable(), gc.retVal->GetAppDomain());
}
Expand All @@ -1417,9 +1443,20 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod,
// If the return type is a Nullable<T> box it into the correct form
gc.retVal = Nullable::NormalizeBox(gc.retVal);
}
else if (retType == ELEMENT_TYPE_BYREF)
{
// WARNING: pReturnedReference is an unprotected inner reference so we must not trigger a GC until the referenced value has been safely captured.
LPVOID pReturnedReference = *(LPVOID*)&callDescrData.returnValue;
if (pReturnedReference == NULL)
{
COMPlusThrow(kNullReferenceException, IDS_INVOKE_NULLREF_RETURNED);
}

gc.retVal = InvokeUtil::CreateObjectAfterInvoke(refReturnTargetTH, pReturnedReference);
}
else
{
gc.retVal = InvokeUtil::CreateObject(retTH, &callDescrData.returnValue);
gc.retVal = InvokeUtil::CreateObjectAfterInvoke(retTH, &callDescrData.returnValue);
}

while (byRefToNullables != NULL) {
Expand Down