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
9namespace
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
303void 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
381void 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