1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #include "common.h" |
6 | #include "object.h" |
7 | #include "callsiteinspect.h" |
8 | |
9 | namespace |
10 | { |
11 | // Given a frame and value, get a reference to the object |
12 | OBJECTREF GetOBJECTREFFromStack( |
13 | _In_ FramedMethodFrame *frame, |
14 | _In_ PVOID val, |
15 | _In_ const CorElementType eType, |
16 | _In_ TypeHandle ty, |
17 | _In_ BOOL fIsByRef) |
18 | { |
19 | CONTRACT(OBJECTREF) |
20 | { |
21 | THROWS; |
22 | GC_TRIGGERS; |
23 | MODE_COOPERATIVE; |
24 | PRECONDITION(CheckPointer(frame)); |
25 | PRECONDITION(CheckPointer(val)); |
26 | } |
27 | CONTRACT_END; |
28 | |
29 | // Value types like Nullable<T> have special unboxing semantics |
30 | if (eType == ELEMENT_TYPE_VALUETYPE) |
31 | { |
32 | // box the value class |
33 | _ASSERTE(ty.GetMethodTable()->IsValueType() || ty.GetMethodTable()->IsEnum()); |
34 | |
35 | MethodTable* pMT = ty.GetMethodTable(); |
36 | |
37 | // What happens when the type contains a stack pointer? |
38 | _ASSERTE(!pMT->IsByRefLike()); |
39 | |
40 | PVOID* pVal = (PVOID *)val; |
41 | if (!fIsByRef) |
42 | { |
43 | val = StackElemEndianessFixup(val, pMT->GetNumInstanceFieldBytes()); |
44 | pVal = &val; |
45 | } |
46 | |
47 | RETURN (pMT->FastBox(pVal)); |
48 | } |
49 | |
50 | switch (CorTypeInfo::GetGCType(eType)) |
51 | { |
52 | case TYPE_GC_NONE: |
53 | { |
54 | if (ELEMENT_TYPE_PTR == eType) |
55 | COMPlusThrow(kNotSupportedException); |
56 | |
57 | MethodTable *pMT = MscorlibBinder::GetElementType(eType); |
58 | |
59 | OBJECTREF pObj = pMT->Allocate(); |
60 | if (fIsByRef) |
61 | { |
62 | val = *((PVOID *)val); |
63 | } |
64 | else |
65 | { |
66 | val = StackElemEndianessFixup(val, CorTypeInfo::Size(eType)); |
67 | } |
68 | |
69 | void *pDest = pObj->UnBox(); |
70 | |
71 | #ifdef COM_STUBS_SEPARATE_FP_LOCATIONS |
72 | if (!fIsByRef |
73 | && (ELEMENT_TYPE_R4 == eType || ELEMENT_TYPE_R8 == eType) |
74 | && frame != nullptr |
75 | && !TransitionBlock::IsStackArgumentOffset(static_cast<int>((TADDR) val - frame->GetTransitionBlock()))) |
76 | { |
77 | if (ELEMENT_TYPE_R4 == eType) |
78 | *(UINT32*)pDest = (UINT32)FPSpillToR4(val); |
79 | else |
80 | *(UINT64*)pDest = (UINT64)FPSpillToR8(val); |
81 | } |
82 | else |
83 | #endif // COM_STUBS_SEPARATE_FP_LOCATIONS |
84 | { |
85 | memcpyNoGCRefs(pDest, val, CorTypeInfo::Size(eType)); |
86 | } |
87 | |
88 | RETURN (pObj); |
89 | } |
90 | |
91 | case TYPE_GC_REF: |
92 | if (fIsByRef) |
93 | val = *((PVOID *)val); |
94 | RETURN (ObjectToOBJECTREF(*(Object **)val)); |
95 | |
96 | default: |
97 | COMPlusThrow(kInvalidOperationException, W("InvalidOperation_TypeCannotBeBoxed" )); |
98 | } |
99 | } |
100 | |
101 | struct ArgDetails |
102 | { |
103 | int Offset; |
104 | BOOL IsByRef; |
105 | CorElementType ElementType; |
106 | TypeHandle Type; |
107 | }; |
108 | |
109 | ArgDetails GetArgDetails( |
110 | _In_ FramedMethodFrame *frame, |
111 | _In_ ArgIterator &pArgIter) |
112 | { |
113 | CONTRACT(ArgDetails) |
114 | { |
115 | THROWS; |
116 | GC_TRIGGERS; |
117 | MODE_ANY; |
118 | PRECONDITION(CheckPointer(frame)); |
119 | } |
120 | CONTRACT_END; |
121 | |
122 | ArgDetails details{}; |
123 | details.Offset = pArgIter.GetNextOffset(); |
124 | details.ElementType = pArgIter.GetArgType(); |
125 | |
126 | #ifdef COM_STUBS_SEPARATE_FP_LOCATIONS |
127 | // BUGBUG do we need to handle this? |
128 | if ((ELEMENT_TYPE_R4 == details.ElementType || ELEMENT_TYPE_R8 == details.ElementType) |
129 | && TransitionBlock::IsArgumentRegisterOffset(details.Offset)) |
130 | { |
131 | int iFPArg = TransitionBlock::GetArgumentIndexFromOffset(details.Offset); |
132 | details.Offset = static_cast<int>(frame->GetFPArgOffset(iFPArg)); |
133 | } |
134 | #endif // COM_STUBS_SEPARATE_FP_LOCATIONS |
135 | |
136 | // Get the TypeHandle for the argument's type. |
137 | MetaSig *pSig = pArgIter.GetSig(); |
138 | details.Type = pSig->GetLastTypeHandleThrowing(); |
139 | |
140 | if (details.ElementType == ELEMENT_TYPE_BYREF) |
141 | { |
142 | details.IsByRef = TRUE; |
143 | // If this is a by-ref arg, GetOBJECTREFFromStack() will dereference "addr" to |
144 | // get the real argument address. Dereferencing now will open a gc hole if "addr" |
145 | // points into the gc heap, and we trigger gc between here and the point where |
146 | // we return the arguments. |
147 | |
148 | TypeHandle tycopy; |
149 | details.ElementType = pSig->GetByRefType(&tycopy); |
150 | if (details.ElementType == ELEMENT_TYPE_VALUETYPE) |
151 | details.Type = tycopy; |
152 | } |
153 | #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE |
154 | else if (details.ElementType == ELEMENT_TYPE_VALUETYPE) |
155 | { |
156 | details.IsByRef = ArgIterator::IsArgPassedByRef(details.Type); |
157 | } |
158 | #endif // ENREGISTERED_PARAMTYPE_MAXSIZE |
159 | |
160 | RETURN (details); |
161 | } |
162 | |
163 | INT64 CopyOBJECTREFToStack( |
164 | _In_ OBJECTREF *src, |
165 | _In_opt_ PVOID pvDest, |
166 | _In_ CorElementType typ, |
167 | _In_ TypeHandle ty, |
168 | _In_ MetaSig *pSig, |
169 | _In_ BOOL fCopyClassContents) |
170 | { |
171 | // Use local to ensure proper alignment |
172 | INT64 ret = 0; |
173 | |
174 | CONTRACT(INT64) |
175 | { |
176 | THROWS; |
177 | GC_TRIGGERS; |
178 | MODE_COOPERATIVE; |
179 | INJECT_FAULT(COMPlusThrowOM()); |
180 | PRECONDITION(CheckPointer(pvDest, NULL_OK)); |
181 | PRECONDITION(CheckPointer(pSig)); |
182 | PRECONDITION(typ != ELEMENT_TYPE_VOID); |
183 | } |
184 | CONTRACT_END; |
185 | |
186 | if (fCopyClassContents) |
187 | { |
188 | // We have to copy the contents of a value class to pvDest |
189 | |
190 | // write unboxed version back to memory provided by the caller |
191 | if (pvDest) |
192 | { |
193 | if (ty.IsNull()) |
194 | ty = pSig->GetRetTypeHandleThrowing(); |
195 | |
196 | _ASSERTE((*src) != NULL || Nullable::IsNullableType(ty)); |
197 | #ifdef PLATFORM_UNIX |
198 | // Unboxing on non-Windows ABIs must be special cased |
199 | COMPlusThrowHR(COR_E_NOTSUPPORTED); |
200 | #else |
201 | ty.GetMethodTable()->UnBoxIntoUnchecked(pvDest, (*src)); |
202 | #endif |
203 | |
204 | // return the object so it can be stored in the frame and |
205 | // propagated to the root set |
206 | *(OBJECTREF*)&ret = (*src); |
207 | } |
208 | } |
209 | else if (CorTypeInfo::IsObjRef(typ)) |
210 | { |
211 | // We have a real OBJECTREF |
212 | |
213 | // Check if it is an OBJECTREF (from the GC heap) |
214 | if (pvDest) |
215 | SetObjectReferenceUnchecked((OBJECTREF *)pvDest, *src); |
216 | |
217 | *(OBJECTREF*)&ret = (*src); |
218 | } |
219 | else |
220 | { |
221 | // We have something that does not have a return buffer associated. |
222 | |
223 | // Note: this assert includes ELEMENT_TYPE_VALUETYPE because for enums, |
224 | // ArgIterator::HasRetBuffArg() returns 'false'. This occurs because the |
225 | // normalized type for enums is ELEMENT_TYPE_I4 even though |
226 | // MetaSig::GetReturnType() returns ELEMENT_TYPE_VALUETYPE. |
227 | // Almost all ELEMENT_TYPE_VALUETYPEs will go through the copy class |
228 | // contents codepath above. |
229 | // Also, CorTypeInfo::IsPrimitiveType() does not check for IntPtr, UIntPtr |
230 | // hence we have ELEMENT_TYPE_I and ELEMENT_TYPE_U. |
231 | _ASSERTE( |
232 | CorTypeInfo::IsPrimitiveType(typ) |
233 | || (typ == ELEMENT_TYPE_VALUETYPE) |
234 | || (typ == ELEMENT_TYPE_I) |
235 | || (typ == ELEMENT_TYPE_U) |
236 | || (typ == ELEMENT_TYPE_FNPTR)); |
237 | |
238 | // For a "ref int" arg, if a nasty sink replaces the boxed int with |
239 | // a null OBJECTREF, this is where we check. We need to be uniform |
240 | // in our policy w.r.t. this (throw vs ignore). |
241 | // The branch above throws. |
242 | if ((*src) != NULL) |
243 | { |
244 | PVOID srcData = (*src)->GetData(); |
245 | int cbsize = gElementTypeInfo[typ].m_cbSize; |
246 | decltype(ret) retBuff; |
247 | |
248 | // ElementTypeInfo.m_cbSize can be less than zero for cases that need |
249 | // special handling (e.g. value types) to be sure of the size (see siginfo.cpp). |
250 | // The type handle has the actual byte count, so we look there for such cases. |
251 | if (cbsize < 0) |
252 | { |
253 | if (ty.IsNull()) |
254 | ty = pSig->GetRetTypeHandleThrowing(); |
255 | |
256 | _ASSERTE(!ty.IsNull()); |
257 | cbsize = ty.GetSize(); |
258 | |
259 | // Assert the value class fits in the buffer |
260 | _ASSERTE(cbsize <= (int) sizeof(retBuff)); |
261 | |
262 | // Unbox value into a local buffer, this covers the Nullable<T> case. |
263 | ty.GetMethodTable()->UnBoxIntoUnchecked(&retBuff, *src); |
264 | |
265 | srcData = &retBuff; |
266 | } |
267 | |
268 | if (pvDest) |
269 | memcpyNoGCRefs(pvDest, srcData, cbsize); |
270 | |
271 | // need to sign-extend signed types |
272 | bool fEndianessFixup = false; |
273 | switch (typ) |
274 | { |
275 | case ELEMENT_TYPE_I1: |
276 | ret = *(INT8*)srcData; |
277 | fEndianessFixup = true; |
278 | break; |
279 | case ELEMENT_TYPE_I2: |
280 | ret = *(INT16*)srcData; |
281 | fEndianessFixup = true; |
282 | break; |
283 | case ELEMENT_TYPE_I4: |
284 | ret = *(INT32*)srcData; |
285 | fEndianessFixup = true; |
286 | break; |
287 | default: |
288 | memcpyNoGCRefs(StackElemEndianessFixup(&ret, cbsize), srcData, cbsize); |
289 | break; |
290 | } |
291 | |
292 | #if !defined(_WIN64) && BIGENDIAN |
293 | if (fEndianessFixup) |
294 | ret <<= 32; |
295 | #endif |
296 | } |
297 | } |
298 | |
299 | RETURN (ret); |
300 | } |
301 | } |
302 | |
303 | void CallsiteInspect::GetCallsiteArgs( |
304 | _In_ CallsiteDetails &callsite, |
305 | _Outptr_ PTRARRAYREF *args, |
306 | _Outptr_ BOOLARRAYREF *argsIsByRef, |
307 | _Outptr_ PTRARRAYREF *argsTypes) |
308 | { |
309 | CONTRACTL |
310 | { |
311 | THROWS; |
312 | GC_TRIGGERS; |
313 | MODE_COOPERATIVE; |
314 | PRECONDITION(CheckPointer(args)); |
315 | PRECONDITION(CheckPointer(argsIsByRef)); |
316 | PRECONDITION(CheckPointer(argsTypes)); |
317 | } |
318 | CONTRACTL_END; |
319 | |
320 | struct _gc |
321 | { |
322 | PTRARRAYREF Args; |
323 | PTRARRAYREF ArgsTypes; |
324 | BOOLARRAYREF ArgsIsByRef; |
325 | OBJECTREF CurrArgType; |
326 | OBJECTREF CurrArg; |
327 | } gc; |
328 | ZeroMemory(&gc, sizeof(gc)); |
329 | GCPROTECT_BEGIN(gc); |
330 | { |
331 | // Ensure the sig is in a known state |
332 | callsite.MetaSig.Reset(); |
333 | |
334 | // scan the sig for the argument count |
335 | INT32 numArgs = callsite.MetaSig.NumFixedArgs(); |
336 | if (callsite.IsDelegate) |
337 | numArgs -= 2; // Delegates have 2 implicit additional arguments |
338 | |
339 | // Allocate all needed arrays for callsite arg details |
340 | gc.Args = (PTRARRAYREF)AllocateObjectArray(numArgs, g_pObjectClass); |
341 | MethodTable *typeMT = MscorlibBinder::GetClass(CLASS__TYPE); |
342 | gc.ArgsTypes = (PTRARRAYREF)AllocateObjectArray(numArgs, typeMT); |
343 | gc.ArgsIsByRef = (BOOLARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_BOOLEAN, numArgs); |
344 | |
345 | ArgIterator iter{ &callsite.MetaSig }; |
346 | for (int index = 0; index < numArgs; index++) |
347 | { |
348 | ArgDetails details = GetArgDetails(callsite.Frame, iter); |
349 | PVOID addr = (LPBYTE)callsite.Frame->GetTransitionBlock() + details.Offset; |
350 | |
351 | // How do we handle pointer types? |
352 | _ASSERTE(details.ElementType != ELEMENT_TYPE_PTR); |
353 | |
354 | gc.CurrArg = GetOBJECTREFFromStack( |
355 | callsite.Frame, |
356 | addr, |
357 | details.ElementType, |
358 | details.Type, |
359 | details.IsByRef); |
360 | |
361 | // Store argument |
362 | gc.Args->SetAt(index, gc.CurrArg); |
363 | |
364 | // Record the argument's type |
365 | gc.CurrArgType = details.Type.GetManagedClassObject(); |
366 | _ASSERTE(gc.CurrArgType != NULL); |
367 | gc.ArgsTypes->SetAt(index, gc.CurrArgType); |
368 | |
369 | // Record if the argument is ByRef |
370 | *((UCHAR*)gc.ArgsIsByRef->GetDataPtr() + index) = (!!details.IsByRef); |
371 | } |
372 | } |
373 | GCPROTECT_END(); |
374 | |
375 | // Return details |
376 | *args = gc.Args; |
377 | *argsTypes = gc.ArgsTypes; |
378 | *argsIsByRef = gc.ArgsIsByRef; |
379 | } |
380 | |
381 | void CallsiteInspect::PropagateOutParametersBackToCallsite( |
382 | _In_ PTRARRAYREF outArgs, |
383 | _In_ OBJECTREF retVal, |
384 | _In_ CallsiteDetails &callsite) |
385 | { |
386 | CONTRACTL |
387 | { |
388 | THROWS; |
389 | GC_TRIGGERS; |
390 | MODE_COOPERATIVE; |
391 | } |
392 | CONTRACTL_END; |
393 | |
394 | struct _gc |
395 | { |
396 | OBJECTREF RetVal; |
397 | PTRARRAYREF OutArgs; |
398 | OBJECTREF CurrArg; |
399 | } gc; |
400 | ZeroMemory(&gc, sizeof(gc)); |
401 | gc.OutArgs = outArgs; |
402 | gc.RetVal = retVal; |
403 | GCPROTECT_BEGIN(gc); |
404 | { |
405 | FramedMethodFrame *frame = callsite.Frame; |
406 | const INT32 flags = callsite.Flags; |
407 | MetaSig *pSig = &callsite.MetaSig; |
408 | pSig->Reset(); // Ensure the sig is in a known state |
409 | |
410 | // Construct an ArgIterator from the sig |
411 | ArgIterator argit{ pSig }; |
412 | |
413 | // Propagate the return value only if the call is not a constructor call |
414 | // and the return type is non-void |
415 | if ((flags & CallsiteDetails::Ctor) == 0 |
416 | && pSig->GetReturnType() != ELEMENT_TYPE_VOID) |
417 | { |
418 | if (argit.HasRetBuffArg()) |
419 | { |
420 | // Copy from RetVal into the retBuff. |
421 | INT64 retVal = CopyOBJECTREFToStack( |
422 | &gc.RetVal, |
423 | *(void**)(frame->GetTransitionBlock() + argit.GetRetBuffArgOffset()), |
424 | pSig->GetReturnType(), |
425 | TypeHandle{}, |
426 | pSig, |
427 | TRUE /* should copy */); |
428 | |
429 | // Copy the return value |
430 | *(ARG_SLOT *)(frame->GetReturnValuePtr()) = retVal; |
431 | } |
432 | #ifdef ENREGISTERED_RETURNTYPE_MAXSIZE |
433 | else if (argit.HasNonStandardByvalReturn()) |
434 | { |
435 | // In these cases, put the pointer to the return buffer into |
436 | // the frame's return value slot. |
437 | CopyOBJECTREFToStack( |
438 | &gc.RetVal, |
439 | frame->GetReturnValuePtr(), |
440 | pSig->GetReturnType(), |
441 | TypeHandle(), |
442 | pSig, |
443 | TRUE /* should copy */); |
444 | } |
445 | #endif // ENREGISTERED_RETURNTYPE_MAXSIZE |
446 | else |
447 | { |
448 | // There is no separate return buffer, |
449 | // the retVal should fit in an INT64. |
450 | INT64 retVal = CopyOBJECTREFToStack( |
451 | &gc.RetVal, |
452 | nullptr, |
453 | pSig->GetReturnType(), |
454 | TypeHandle{}, |
455 | pSig, |
456 | FALSE /* should copy */); |
457 | |
458 | // Copy the return value |
459 | *(ARG_SLOT *)(frame->GetReturnValuePtr()) = retVal; |
460 | } |
461 | } |
462 | |
463 | // Refetch all the variables as GC could have happened |
464 | // after copying the return value. |
465 | UINT32 cOutArgs = (gc.OutArgs != NULL) ? gc.OutArgs->GetNumComponents() : 0; |
466 | if (cOutArgs > 0) |
467 | { |
468 | MetaSig syncSig{ callsite.MethodDesc }; |
469 | MetaSig *pSyncSig = nullptr; |
470 | |
471 | if (flags & CallsiteDetails::EndInvoke) |
472 | pSyncSig = &syncSig; |
473 | |
474 | PVOID *argAddr; |
475 | for (UINT32 i = 0; i < cOutArgs; ++i) |
476 | { |
477 | // Determine the address of the argument |
478 | if (pSyncSig) |
479 | { |
480 | CorElementType typ = pSyncSig->NextArg(); |
481 | if (typ == ELEMENT_TYPE_END) |
482 | break; |
483 | |
484 | if (typ != ELEMENT_TYPE_BYREF) |
485 | continue; |
486 | |
487 | argAddr = reinterpret_cast<PVOID *>(frame->GetTransitionBlock() + argit.GetNextOffset()); |
488 | } |
489 | else |
490 | { |
491 | int ofs = argit.GetNextOffset(); |
492 | if (ofs == TransitionBlock::InvalidOffset) |
493 | break; |
494 | |
495 | if (argit.GetArgType() != ELEMENT_TYPE_BYREF) |
496 | continue; |
497 | |
498 | argAddr = reinterpret_cast<PVOID *>(frame->GetTransitionBlock() + ofs); |
499 | } |
500 | |
501 | TypeHandle ty; |
502 | CorElementType brType = pSig->GetByRefType(&ty); |
503 | |
504 | gc.CurrArg = gc.OutArgs->GetAt(i); |
505 | CopyOBJECTREFToStack( |
506 | &gc.CurrArg, |
507 | *argAddr, |
508 | brType, |
509 | ty, |
510 | pSig, |
511 | ty.IsNull() ? FALSE : ty.IsValueType()); |
512 | } |
513 | } |
514 | } |
515 | GCPROTECT_END(); |
516 | } |
517 | |