Skip to content

Commit f602738

Browse files
jkoritzinskyAaronRobinsonMSFT
andauthoredMar 24, 2024··
Call the Copy Constructor for stack arguments in C++/CLI on Windows-x86 (#100050)
* Add repro test case * Directly load the argument address using ldarga to avoid making a copy * Reimplement the "Copy Constructor Cookie" logic in a more modern and maintainable style to get the test passing again * Narrow support to Windows only --------- Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
1 parent 6c685e3 commit f602738

File tree

13 files changed

+359
-12
lines changed

13 files changed

+359
-12
lines changed
 

‎src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs

+69
Original file line numberDiff line numberDiff line change
@@ -1315,6 +1315,75 @@ public IntPtr AddRef()
13151315
}
13161316
} // class CleanupWorkListElement
13171317

1318+
internal unsafe struct CopyConstructorCookie
1319+
{
1320+
private void* m_source;
1321+
1322+
private nuint m_destinationOffset;
1323+
1324+
public delegate*<void*, void*, void> m_copyConstructor;
1325+
1326+
public delegate*<void*, void> m_destructor;
1327+
1328+
public CopyConstructorCookie* m_next;
1329+
1330+
[StackTraceHidden]
1331+
public void ExecuteCopy(void* destinationBase)
1332+
{
1333+
if (m_copyConstructor != null)
1334+
{
1335+
m_copyConstructor((byte*)destinationBase + m_destinationOffset, m_source);
1336+
}
1337+
1338+
if (m_destructor != null)
1339+
{
1340+
m_destructor(m_source);
1341+
}
1342+
}
1343+
}
1344+
1345+
internal unsafe struct CopyConstructorChain
1346+
{
1347+
public void* m_realTarget;
1348+
public CopyConstructorCookie* m_head;
1349+
1350+
public void Add(CopyConstructorCookie* cookie)
1351+
{
1352+
cookie->m_next = m_head;
1353+
m_head = cookie;
1354+
}
1355+
1356+
[ThreadStatic]
1357+
private static CopyConstructorChain s_copyConstructorChain;
1358+
1359+
public void Install(void* realTarget)
1360+
{
1361+
m_realTarget = realTarget;
1362+
s_copyConstructorChain = this;
1363+
}
1364+
1365+
[StackTraceHidden]
1366+
private void ExecuteCopies(void* destinationBase)
1367+
{
1368+
for (CopyConstructorCookie* current = m_head; current != null; current = current->m_next)
1369+
{
1370+
current->ExecuteCopy(destinationBase);
1371+
}
1372+
}
1373+
1374+
[UnmanagedCallersOnly]
1375+
[StackTraceHidden]
1376+
public static void* ExecuteCurrentCopiesAndGetTarget(void* destinationBase)
1377+
{
1378+
void* target = s_copyConstructorChain.m_realTarget;
1379+
s_copyConstructorChain.ExecuteCopies(destinationBase);
1380+
// Reset this instance to ensure we don't accidentally execute the copies again.
1381+
// All of the pointers point to the stack, so we don't need to free any memory.
1382+
s_copyConstructorChain = default;
1383+
return target;
1384+
}
1385+
}
1386+
13181387
internal static partial class StubHelpers
13191388
{
13201389
[MethodImpl(MethodImplOptions.InternalCall)]

‎src/coreclr/vm/corelib.h

+15
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,21 @@ DEFINE_METHOD(HANDLE_MARSHALER, CONVERT_SAFEHANDLE_TO_NATIVE,ConvertSaf
10401040
DEFINE_METHOD(HANDLE_MARSHALER, THROW_SAFEHANDLE_FIELD_CHANGED, ThrowSafeHandleFieldChanged, SM_RetVoid)
10411041
DEFINE_METHOD(HANDLE_MARSHALER, THROW_CRITICALHANDLE_FIELD_CHANGED, ThrowCriticalHandleFieldChanged, SM_RetVoid)
10421042

1043+
#ifdef TARGET_WINDOWS
1044+
#ifdef TARGET_X86
1045+
DEFINE_CLASS(COPY_CONSTRUCTOR_CHAIN, StubHelpers, CopyConstructorChain)
1046+
DEFINE_METHOD(COPY_CONSTRUCTOR_CHAIN, EXECUTE_CURRENT_COPIES_AND_GET_TARGET, ExecuteCurrentCopiesAndGetTarget, SM_PtrVoid_RetPtrVoid)
1047+
DEFINE_METHOD(COPY_CONSTRUCTOR_CHAIN, INSTALL, Install, IM_PtrVoid_RetVoid)
1048+
DEFINE_METHOD(COPY_CONSTRUCTOR_CHAIN, ADD, Add, IM_PtrCopyConstructorCookie_RetVoid)
1049+
1050+
DEFINE_CLASS(COPY_CONSTRUCTOR_COOKIE, StubHelpers, CopyConstructorCookie)
1051+
DEFINE_FIELD(COPY_CONSTRUCTOR_COOKIE, SOURCE, m_source)
1052+
DEFINE_FIELD(COPY_CONSTRUCTOR_COOKIE, DESTINATION_OFFSET, m_destinationOffset)
1053+
DEFINE_FIELD(COPY_CONSTRUCTOR_COOKIE, COPY_CONSTRUCTOR, m_copyConstructor)
1054+
DEFINE_FIELD(COPY_CONSTRUCTOR_COOKIE, DESTRUCTOR, m_destructor)
1055+
#endif // TARGET_X86
1056+
#endif // TARGET_WINDOWS
1057+
10431058
DEFINE_CLASS(COMVARIANT, Marshalling, ComVariant)
10441059

10451060
DEFINE_CLASS(SZARRAYHELPER, System, SZArrayHelper)

‎src/coreclr/vm/dllimport.cpp

+56
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,10 @@ NDirectStubLinker::NDirectStubLinker(
16301630
m_pcsSetup->EmitSTLOC(m_dwTargetInterfacePointerLocalNum);
16311631
}
16321632
#endif // FEATURE_COMINTEROP
1633+
1634+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
1635+
m_dwCopyCtorChainLocalNum = (DWORD)-1;
1636+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
16331637
}
16341638

16351639
void NDirectStubLinker::SetCallingConvention(CorInfoCallConvExtension unmngCallConv, BOOL fIsVarArg)
@@ -1842,6 +1846,23 @@ DWORD NDirectStubLinker::GetReturnValueLocalNum()
18421846
return m_dwRetValLocalNum;
18431847
}
18441848

1849+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
1850+
DWORD NDirectStubLinker::GetCopyCtorChainLocalNum()
1851+
{
1852+
STANDARD_VM_CONTRACT;
1853+
1854+
if (m_dwCopyCtorChainLocalNum == (DWORD)-1)
1855+
{
1856+
// The local is created and initialized lazily when first asked.
1857+
m_dwCopyCtorChainLocalNum = NewLocal(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_CHAIN));
1858+
m_pcsSetup->EmitLDLOCA(m_dwCopyCtorChainLocalNum);
1859+
m_pcsSetup->EmitINITOBJ(m_pcsSetup->GetToken(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_CHAIN)));
1860+
}
1861+
1862+
return m_dwCopyCtorChainLocalNum;
1863+
}
1864+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
1865+
18451866
BOOL NDirectStubLinker::IsCleanupNeeded()
18461867
{
18471868
LIMITED_METHOD_CONTRACT;
@@ -2071,6 +2092,10 @@ void NDirectStubLinker::End(DWORD dwStubFlags)
20712092
}
20722093
}
20732094

2095+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
2096+
EXTERN_C void STDCALL CopyConstructorCallStub(void);
2097+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
2098+
20742099
void NDirectStubLinker::DoNDirect(ILCodeStream *pcsEmit, DWORD dwStubFlags, MethodDesc * pStubMD)
20752100
{
20762101
STANDARD_VM_CONTRACT;
@@ -2154,6 +2179,21 @@ void NDirectStubLinker::DoNDirect(ILCodeStream *pcsEmit, DWORD dwStubFlags, Meth
21542179
}
21552180
}
21562181

2182+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
2183+
if (m_dwCopyCtorChainLocalNum != (DWORD)-1)
2184+
{
2185+
// If we have a copy constructor chain local, we need to call the copy constructor stub
2186+
// to ensure that the chain is called correctly.
2187+
// Let's install the stub chain here and redirect the call to the stub.
2188+
DWORD targetLoc = NewLocal(ELEMENT_TYPE_I);
2189+
pcsEmit->EmitSTLOC(targetLoc);
2190+
pcsEmit->EmitLDLOCA(m_dwCopyCtorChainLocalNum);
2191+
pcsEmit->EmitLDLOC(targetLoc);
2192+
pcsEmit->EmitCALL(METHOD__COPY_CONSTRUCTOR_CHAIN__INSTALL, 2, 0);
2193+
pcsEmit->EmitLDC((DWORD_PTR)&CopyConstructorCallStub);
2194+
}
2195+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
2196+
21572197
// For managed-to-native calls, the rest of the work is done by the JIT. It will
21582198
// erect InlinedCallFrame, flip GC mode, and use the specified calling convention
21592199
// to call the target. For native-to-managed calls, this is an ordinary managed
@@ -6101,5 +6141,21 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD)
61016141
RETURN pVASigCookie->pNDirectILStub;
61026142
}
61036143

6144+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
6145+
// Copy constructor support for C++/CLI
6146+
EXTERN_C void* STDCALL CallCopyConstructorsWorker(void* esp)
6147+
{
6148+
STATIC_CONTRACT_THROWS;
6149+
STATIC_CONTRACT_GC_TRIGGERS;
6150+
STATIC_CONTRACT_MODE_PREEMPTIVE; // we've already switched to preemptive
6151+
6152+
using ExecuteCallback = void*(STDMETHODCALLTYPE*)(void*);
6153+
6154+
MethodDesc* pMD = CoreLibBinder::GetMethod(METHOD__COPY_CONSTRUCTOR_CHAIN__EXECUTE_CURRENT_COPIES_AND_GET_TARGET);
6155+
ExecuteCallback pExecute = (ExecuteCallback)pMD->GetMultiCallableAddrOfCode();
6156+
6157+
return pExecute(esp);
6158+
}
6159+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
61046160

61056161
#endif // #ifndef DACCESS_COMPILE

‎src/coreclr/vm/dllimport.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ struct StubSigDesc
5656
}
5757
}
5858
#endif // _DEBUG
59-
59+
6060
#ifndef DACCESS_COMPILE
6161
void InitTypeContext(Instantiation classInst, Instantiation methodInst)
6262
{
@@ -496,6 +496,9 @@ class NDirectStubLinker : public ILStubLinker
496496
DWORD GetCleanupWorkListLocalNum();
497497
DWORD GetThreadLocalNum();
498498
DWORD GetReturnValueLocalNum();
499+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
500+
DWORD GetCopyCtorChainLocalNum();
501+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
499502
void SetCleanupNeeded();
500503
void SetExceptionCleanupNeeded();
501504
BOOL IsCleanupWorkListSetup();
@@ -565,6 +568,10 @@ class NDirectStubLinker : public ILStubLinker
565568
DWORD m_dwTargetEntryPointLocalNum;
566569
#endif // FEATURE_COMINTEROP
567570

571+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
572+
DWORD m_dwCopyCtorChainLocalNum;
573+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
574+
568575
BOOL m_fHasCleanupCode;
569576
BOOL m_fHasExceptionCleanupCode;
570577
BOOL m_fCleanupWorkListIsSetup;

‎src/coreclr/vm/i386/asmhelpers.asm

+24
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ EXTERN _NDirectImportWorker@4:PROC
4141

4242
EXTERN _VarargPInvokeStubWorker@12:PROC
4343
EXTERN _GenericPInvokeCalliStubWorker@12:PROC
44+
EXTERN _CallCopyConstructorsWorker@4:PROC
4445

4546
EXTERN _PreStubWorker@8:PROC
4647
EXTERN _TheUMEntryPrestubWorker@4:PROC
@@ -1062,6 +1063,29 @@ GoCallCalliWorker:
10621063

10631064
_GenericPInvokeCalliHelper@0 endp
10641065

1066+
;==========================================================================
1067+
; This is small stub whose purpose is to record current stack pointer and
1068+
; call CallCopyConstructorsWorker to invoke copy constructors and destructors
1069+
; as appropriate. This stub operates on arguments already pushed to the
1070+
; stack by JITted IL stub and must not create a new frame, i.e. it must tail
1071+
; call to the target for it to see the arguments that copy ctors have been
1072+
; called on.
1073+
;
1074+
_CopyConstructorCallStub@0 proc public
1075+
; there may be an argument in ecx - save it
1076+
push ecx
1077+
1078+
; push pointer to arguments
1079+
lea edx, [esp + 8]
1080+
push edx
1081+
1082+
call _CallCopyConstructorsWorker@4
1083+
1084+
; restore ecx and tail call to the target
1085+
pop ecx
1086+
jmp eax
1087+
_CopyConstructorCallStub@0 endp
1088+
10651089
ifdef FEATURE_COMINTEROP
10661090

10671091
;==========================================================================

‎src/coreclr/vm/ilmarshalers.cpp

+33-3
Original file line numberDiff line numberDiff line change
@@ -3394,6 +3394,7 @@ ILCriticalHandleMarshaler::ReturnOverride(
33943394
return OVERRIDDEN;
33953395
} // ILCriticalHandleMarshaler::ReturnOverride
33963396

3397+
#if defined(TARGET_WINDOWS)
33973398
MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOverride(NDirectStubLinker* psl,
33983399
BOOL byref,
33993400
BOOL fin,
@@ -3459,6 +3460,36 @@ MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOver
34593460
#ifdef TARGET_X86
34603461
pslIL->SetStubTargetArgType(&locDesc); // native type is the value type
34613462
pslILDispatch->EmitLDLOC(dwNewValueTypeLocal); // we load the local directly
3463+
3464+
// Record this argument's stack slot in the copy constructor chain so we can correctly invoke the copy constructor.
3465+
DWORD ctorCookie = pslIL->NewLocal(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_COOKIE));
3466+
pslIL->EmitLDLOCA(ctorCookie);
3467+
pslIL->EmitINITOBJ(pslIL->GetToken(CoreLibBinder::GetClass(CLASS__COPY_CONSTRUCTOR_COOKIE)));
3468+
pslIL->EmitLDLOCA(ctorCookie);
3469+
pslIL->EmitLDLOCA(dwNewValueTypeLocal);
3470+
pslIL->EmitSTFLD(pslIL->GetToken(CoreLibBinder::GetField(FIELD__COPY_CONSTRUCTOR_COOKIE__SOURCE)));
3471+
pslIL->EmitLDLOCA(ctorCookie);
3472+
pslIL->EmitLDC(nativeStackOffset);
3473+
pslIL->EmitSTFLD(pslIL->GetToken(CoreLibBinder::GetField(FIELD__COPY_CONSTRUCTOR_COOKIE__DESTINATION_OFFSET)));
3474+
3475+
if (pargs->mm.m_pCopyCtor)
3476+
{
3477+
pslIL->EmitLDLOCA(ctorCookie);
3478+
pslIL->EmitLDFTN(pslIL->GetToken(pargs->mm.m_pCopyCtor));
3479+
pslIL->EmitSTFLD(pslIL->GetToken(CoreLibBinder::GetField(FIELD__COPY_CONSTRUCTOR_COOKIE__COPY_CONSTRUCTOR)));
3480+
}
3481+
3482+
if (pargs->mm.m_pDtor)
3483+
{
3484+
pslIL->EmitLDLOCA(ctorCookie);
3485+
pslIL->EmitLDFTN(pslIL->GetToken(pargs->mm.m_pDtor));
3486+
pslIL->EmitSTFLD(pslIL->GetToken(CoreLibBinder::GetField(FIELD__COPY_CONSTRUCTOR_COOKIE__DESTRUCTOR)));
3487+
}
3488+
3489+
pslIL->EmitLDLOCA(psl->GetCopyCtorChainLocalNum());
3490+
pslIL->EmitLDLOCA(ctorCookie);
3491+
pslIL->EmitCALL(METHOD__COPY_CONSTRUCTOR_CHAIN__ADD, 2, 0);
3492+
34623493
#else
34633494
pslIL->SetStubTargetArgType(ELEMENT_TYPE_I); // native type is a pointer
34643495
EmitLoadNativeLocalAddrForByRefDispatch(pslILDispatch, dwNewValueTypeLocal);
@@ -3477,9 +3508,7 @@ MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOver
34773508

34783509
DWORD dwNewValueTypeLocal;
34793510
dwNewValueTypeLocal = pslIL->NewLocal(locDesc);
3480-
pslILDispatch->EmitLDARG(argidx);
3481-
pslILDispatch->EmitSTLOC(dwNewValueTypeLocal);
3482-
pslILDispatch->EmitLDLOCA(dwNewValueTypeLocal);
3511+
pslILDispatch->EmitLDARGA(argidx);
34833512
#else
34843513
LocalDesc locDesc(pargs->mm.m_pMT);
34853514
locDesc.MakePointer();
@@ -3491,6 +3520,7 @@ MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOver
34913520
return OVERRIDDEN;
34923521
}
34933522
}
3523+
#endif // defined(TARGET_WINDOWS)
34943524

34953525
LocalDesc ILArgIteratorMarshaler::GetNativeType()
34963526
{

‎src/coreclr/vm/ilmarshalers.h

+2
Original file line numberDiff line numberDiff line change
@@ -2923,6 +2923,7 @@ class ILBlittableLayoutClassMarshaler : public ILMarshaler
29232923
void EmitConvertContentsNativeToCLR(ILCodeStream* pslILEmit) override;
29242924
};
29252925

2926+
#if defined(TARGET_WINDOWS)
29262927
class ILBlittableValueClassWithCopyCtorMarshaler : public ILMarshaler
29272928
{
29282929
public:
@@ -2956,6 +2957,7 @@ class ILBlittableValueClassWithCopyCtorMarshaler : public ILMarshaler
29562957

29572958

29582959
};
2960+
#endif // defined(TARGET_WINDOWS)
29592961

29602962
class ILArgIteratorMarshaler : public ILMarshaler
29612963
{

‎src/coreclr/vm/metasig.h

+7
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,13 @@ DEFINE_METASIG_T(SM(RefCleanupWorkListElement_RetVoid, r(C(CLEANUP_WORK_LIST_ELE
588588
DEFINE_METASIG_T(SM(RefCleanupWorkListElement_SafeHandle_RetIntPtr, r(C(CLEANUP_WORK_LIST_ELEMENT)) C(SAFE_HANDLE), I))
589589
DEFINE_METASIG_T(SM(RefCleanupWorkListElement_Obj_RetVoid, r(C(CLEANUP_WORK_LIST_ELEMENT)) j, v))
590590

591+
DEFINE_METASIG(SM(PtrVoid_RetPtrVoid, P(v), P(v)))
592+
DEFINE_METASIG(IM(PtrVoid_RetVoid, P(v), v))
593+
#if defined(TARGET_X86) && defined(TARGET_WINDOWS)
594+
DEFINE_METASIG_T(IM(PtrCopyConstructorCookie_RetVoid, P(g(COPY_CONSTRUCTOR_COOKIE)), v))
595+
#endif // defined(TARGET_X86) && defined(TARGET_WINDOWS)
596+
597+
591598
#ifdef FEATURE_ICASTABLE
592599
DEFINE_METASIG_T(SM(ICastable_RtType_RefException_RetBool, C(ICASTABLE) C(CLASS) r(C(EXCEPTION)), F))
593600
DEFINE_METASIG_T(SM(ICastable_RtType_RetRtType, C(ICASTABLE) C(CLASS), C(CLASS)))

‎src/coreclr/vm/mlinfo.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -2383,6 +2383,7 @@ MarshalInfo::MarshalInfo(Module* pModule,
23832383
{
23842384
if (fNeedsCopyCtor && !IsFieldScenario()) // We don't support automatically discovering copy constructors for fields.
23852385
{
2386+
#if defined(TARGET_WINDOWS)
23862387
MethodDesc *pCopyCtor;
23872388
MethodDesc *pDtor;
23882389
FindCopyCtor(pModule, m_pMT, &pCopyCtor);
@@ -2392,6 +2393,10 @@ MarshalInfo::MarshalInfo(Module* pModule,
23922393
m_args.mm.m_pCopyCtor = pCopyCtor;
23932394
m_args.mm.m_pDtor = pDtor;
23942395
m_type = MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR;
2396+
#else // !defined(TARGET_WINDOWS)
2397+
m_resID = IDS_EE_BADMARSHAL_BADMANAGED;
2398+
IfFailGoto(E_FAIL, lFail);
2399+
#endif // defined(TARGET_WINDOWS)
23952400
}
23962401
else
23972402
{
@@ -3121,7 +3126,9 @@ bool MarshalInfo::IsValueClass(MarshalType mtype)
31213126
{
31223127
case MARSHAL_TYPE_BLITTABLEVALUECLASS:
31233128
case MARSHAL_TYPE_VALUECLASS:
3129+
#if defined(TARGET_WINDOWS)
31243130
case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR:
3131+
#endif // defined(TARGET_WINDOWS)
31253132
return true;
31263133

31273134
default:
@@ -3605,7 +3612,9 @@ DispParamMarshaler *MarshalInfo::GenerateDispParamMarshaler()
36053612
case MARSHAL_TYPE_BLITTABLEVALUECLASS:
36063613
case MARSHAL_TYPE_BLITTABLEPTR:
36073614
case MARSHAL_TYPE_LAYOUTCLASSPTR:
3615+
#if defined(TARGET_WINDOWS)
36083616
case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR:
3617+
#endif // defined(TARGET_WINDOWS)
36093618
pDispParamMarshaler = new DispParamRecordMarshaler(m_pMT);
36103619
break;
36113620

‎src/coreclr/vm/mtypes.h

+3
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_VALUECLASS, ValueClassMa
7979

8080
DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_REFERENCECUSTOMMARSHALER, ReferenceCustomMarshaler)
8181
DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_ARGITERATOR, ArgIteratorMarshaler)
82+
83+
#if defined(TARGET_WINDOWS)
8284
DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR, BlittableValueClassWithCopyCtorMarshaler)
85+
#endif // defined(TARGET_WINDOWS)
8386

8487
#ifdef FEATURE_COMINTEROP
8588
DEFINE_MARSHALER_TYPE(MARSHAL_TYPE_OBJECT, ObjectMarshaler)

‎src/tests/Interop/IJW/CopyConstructorMarshaler/CopyConstructorMarshaler.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,32 @@ public static int TestEntryPoint()
2626
object testInstance = Activator.CreateInstance(testType);
2727
MethodInfo testMethod = testType.GetMethod("PInvokeNumCopies");
2828

29+
// On x86, we have an additional copy on every P/Invoke from the "native" parameter to the actual location on the stack.
30+
int platformExtra = 0;
31+
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
32+
{
33+
platformExtra = 1;
34+
}
35+
2936
// PInvoke will copy twice. Once from argument to parameter, and once from the managed to native parameter.
30-
Assert.Equal(2, (int)testMethod.Invoke(testInstance, null));
37+
Assert.Equal(2 + platformExtra, (int)testMethod.Invoke(testInstance, null));
3138

3239
testMethod = testType.GetMethod("ReversePInvokeNumCopies");
3340

3441
// Reverse PInvoke will copy 3 times. Two are from the same paths as the PInvoke,
3542
// and the third is from the reverse P/Invoke call.
36-
Assert.Equal(3, (int)testMethod.Invoke(testInstance, null));
43+
Assert.Equal(3 + platformExtra, (int)testMethod.Invoke(testInstance, null));
3744

3845
testMethod = testType.GetMethod("PInvokeNumCopiesDerivedType");
3946

4047
// PInvoke will copy twice. Once from argument to parameter, and once from the managed to native parameter.
41-
Assert.Equal(2, (int)testMethod.Invoke(testInstance, null));
48+
Assert.Equal(2 + platformExtra, (int)testMethod.Invoke(testInstance, null));
4249

4350
testMethod = testType.GetMethod("ReversePInvokeNumCopiesDerivedType");
4451

4552
// Reverse PInvoke will copy 3 times. Two are from the same paths as the PInvoke,
4653
// and the third is from the reverse P/Invoke call.
47-
Assert.Equal(3, (int)testMethod.Invoke(testInstance, null));
54+
Assert.Equal(3 + platformExtra, (int)testMethod.Invoke(testInstance, null));
4855
}
4956
catch (Exception ex)
5057
{
@@ -54,6 +61,17 @@ public static int TestEntryPoint()
5461
return 100;
5562
}
5663

64+
[Fact]
65+
public static void CopyConstructorsInArgumentStackSlots()
66+
{
67+
Assembly ijwNativeDll = Assembly.Load("IjwCopyConstructorMarshaler");
68+
Type testType = ijwNativeDll.GetType("TestClass");
69+
object testInstance = Activator.CreateInstance(testType);
70+
MethodInfo testMethod = testType.GetMethod("ExposedThisCopyConstructorScenario");
71+
72+
Assert.Equal(0, (int)testMethod.Invoke(testInstance, null));
73+
}
74+
5775
[DllImport("kernel32.dll")]
5876
static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull, int dwFlags);
5977

‎src/tests/Interop/IJW/CopyConstructorMarshaler/IjwCopyConstructorMarshaler.cpp

+90
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,90 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
3+
#pragma unmanaged
4+
#include <vector>
5+
#include <iostream>
6+
7+
namespace ExposedThis
8+
{
9+
struct Relative;
10+
11+
std::vector<Relative*> relatives;
12+
13+
int numMissedCopies = 0;
14+
15+
struct Relative
16+
{
17+
void* relative;
18+
Relative()
19+
{
20+
std::cout << "Registering " << std::hex << this << "\n";
21+
relatives.push_back(this);
22+
relative = this - 1;
23+
}
24+
25+
Relative(const Relative& other)
26+
{
27+
std::cout << "Registering copy of " << std::hex << &other << " at " << this << "\n";
28+
relatives.push_back(this);
29+
relative = this - 1;
30+
}
31+
32+
~Relative()
33+
{
34+
auto location = std::find(relatives.begin(), relatives.end(), this);
35+
if (location != relatives.end())
36+
{
37+
std::cout << "Unregistering " << std::hex << this << "\n";
38+
relatives.erase(location);
39+
}
40+
else
41+
{
42+
std::cout << "Error: Relative object " << std::hex << this << " not registered\n";
43+
numMissedCopies++;
44+
}
45+
46+
if (relative != this - 1)
47+
{
48+
std::cout << " Error: Relative object " << std::hex << this << " has invalid relative pointer " << std::hex << relative << "\n";
49+
numMissedCopies++;
50+
}
51+
}
52+
};
53+
54+
void UseRelative(Relative rel)
55+
{
56+
std::cout << "Unmanaged: Using relative at address " << std::hex << &rel << "\n";
57+
}
58+
59+
void UseRelativeManaged(Relative rel);
60+
61+
void CallRelative()
62+
{
63+
Relative rel;
64+
UseRelativeManaged(rel);
65+
}
66+
67+
#pragma managed
68+
69+
int RunScenario()
70+
{
71+
// Managed to unmanaged
72+
{
73+
Relative rel;
74+
UseRelative(rel);
75+
}
76+
77+
// Unmanaged to managed
78+
CallRelative();
79+
80+
return numMissedCopies;
81+
}
82+
83+
void UseRelativeManaged(Relative rel)
84+
{
85+
std::cout << "Managed: Using relative at address " << std::hex << &rel << "\n";
86+
}
87+
}
388

489
#pragma managed
590
class A
@@ -102,4 +187,9 @@ public ref class TestClass
102187
B b;
103188
return GetCopyCount_ViaManaged(b);
104189
}
190+
191+
int ExposedThisCopyConstructorScenario()
192+
{
193+
return ExposedThis::RunScenario();
194+
}
105195
};

‎src/tests/Interop/PInvoke/Miscellaneous/CopyCtor/CopyCtorTest.cs

+21-4
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,37 @@ public static unsafe class CopyCtor
1515
public static unsafe int StructWithCtorTest(StructWithCtor* ptrStruct, ref StructWithCtor refStruct)
1616
{
1717
if (ptrStruct->_instanceField != 1)
18+
{
19+
Console.WriteLine($"Fail: {ptrStruct->_instanceField} != {1}");
1820
return 1;
21+
}
1922
if (refStruct._instanceField != 2)
23+
{
24+
Console.WriteLine($"Fail: {refStruct._instanceField} != {2}");
2025
return 2;
26+
}
2127

22-
if (StructWithCtor.CopyCtorCallCount != 2)
28+
int expectedCallCount = 2;
29+
if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
30+
{
31+
expectedCallCount = 4;
32+
}
33+
34+
if (StructWithCtor.CopyCtorCallCount != expectedCallCount)
35+
{
36+
Console.WriteLine($"Fail: {StructWithCtor.CopyCtorCallCount} != {expectedCallCount}");
2337
return 3;
24-
if (StructWithCtor.DtorCallCount != 2)
38+
}
39+
if (StructWithCtor.DtorCallCount != expectedCallCount)
40+
{
41+
Console.WriteLine($"Fail: {StructWithCtor.DtorCallCount} != {expectedCallCount}");
2542
return 4;
26-
43+
}
2744

2845
return 100;
2946
}
3047

31-
[Fact]
48+
[ConditionalFact(typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.IsWindows))]
3249
[SkipOnMono("Not supported on Mono")]
3350
[ActiveIssue("https://github.com/dotnet/runtimelab/issues/155", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))]
3451
public static unsafe void ValidateCopyConstructorAndDestructorCalled()

0 commit comments

Comments
 (0)
Please sign in to comment.