// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // File: CLASS.CPP // #include "common.h" #include "dllimport.h" #include "dllimportcallback.h" #include "fieldmarshaler.h" #include "customattribute.h" #include "encee.h" #include "typestring.h" #include "dbginterface.h" #ifdef FEATURE_COMINTEROP #include "comcallablewrapper.h" #include "clrtocomcall.h" #include "runtimecallablewrapper.h" #endif // FEATURE_COMINTEROP //#define DEBUG_LAYOUT #define SORT_BY_RID #ifndef DACCESS_COMPILE #include "methodtablebuilder.h" #endif #ifndef DACCESS_COMPILE //******************************************************************************* void *EEClass::operator new( size_t size, LoaderHeap *pHeap, AllocMemTracker *pamTracker) { CONTRACTL { THROWS; GC_NOTRIGGER; INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; void *p = pamTracker->Track(pHeap->AllocMem(S_SIZE_T(size))); // No need to memset since this memory came from VirtualAlloc'ed memory // memset (p, 0, size); return p; } //******************************************************************************* void EEClass::Destruct(MethodTable * pOwningMT) { CONTRACTL { NOTHROW; GC_TRIGGERS; FORBID_FAULT; PRECONDITION(pOwningMT != NULL); } CONTRACTL_END #ifdef PROFILING_SUPPORTED // If profiling, then notify the class is getting unloaded. { BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses()); { // Calls to the profiler callback may throw, or otherwise fail, if // the profiler AVs/throws an unhandled exception/etc. We don't want // those failures to affect the runtime, so we'll ignore them. // // Note that the profiler callback may turn around and make calls into // the profiling runtime that may throw. This try/catch block doesn't // protect the profiler against such failures. To protect the profiler // against that, we will need try/catch blocks around all calls into the // profiling API. // // (Bug #26467) // FAULT_NOT_FATAL(); EX_TRY { GCX_PREEMP(); (&g_profControlBlock)->ClassUnloadStarted((ClassID) pOwningMT); } EX_CATCH { // The exception here came from the profiler itself. We'll just // swallow the exception, since we don't want the profiler to bring // down the runtime. } EX_END_CATCH(RethrowTerminalExceptions); } END_PROFILER_CALLBACK(); } #endif // PROFILING_SUPPORTED #ifdef FEATURE_COMINTEROP // clean up any COM Data if (m_pccwTemplate) { m_pccwTemplate->Release(); m_pccwTemplate = NULL; } #ifdef FEATURE_COMINTEROP_UNMANAGED_ACTIVATION if (GetComClassFactory()) { GetComClassFactory()->Cleanup(); } #endif // FEATURE_COMINTEROP_UNMANAGED_ACTIVATION #endif // FEATURE_COMINTEROP if (IsDelegate()) { DelegateEEClass* pDelegateEEClass = (DelegateEEClass*)this; if (pDelegateEEClass->m_pStaticCallStub) { // Collect data to remove stub entry from StubManager if // stub is deleted. BYTE* entry = (BYTE*)pDelegateEEClass->m_pStaticCallStub->GetEntryPoint(); UINT length = pDelegateEEClass->m_pStaticCallStub->GetNumCodeBytes(); ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pStaticCallStub, sizeof(Stub)); BOOL fStubDeleted = stubWriterHolder.GetRW()->DecRef(); if (fStubDeleted) { StubLinkStubManager::g_pManager->RemoveStubRange(entry, length); } } if (pDelegateEEClass->m_pInstRetBuffCallStub) { ExecutableWriterHolder stubWriterHolder(pDelegateEEClass->m_pInstRetBuffCallStub, sizeof(Stub)); stubWriterHolder.GetRW()->DecRef(); } // While m_pMultiCastInvokeStub is also a member, // it is owned by the m_pMulticastStubCache, not by the class // - it is shared across classes. So we don't decrement // its ref count here } #ifdef FEATURE_COMINTEROP if (GetSparseCOMInteropVTableMap() != NULL) delete GetSparseCOMInteropVTableMap(); #endif // FEATURE_COMINTEROP #ifdef PROFILING_SUPPORTED // If profiling, then notify the class is getting unloaded. { BEGIN_PROFILER_CALLBACK(CORProfilerTrackClasses()); { // See comments in the call to ClassUnloadStarted for details on this // FAULT_NOT_FATAL marker and exception swallowing. FAULT_NOT_FATAL(); EX_TRY { GCX_PREEMP(); (&g_profControlBlock)->ClassUnloadFinished((ClassID) pOwningMT, S_OK); } EX_CATCH { } EX_END_CATCH(RethrowTerminalExceptions); } END_PROFILER_CALLBACK(); } #endif // PROFILING_SUPPORTED } //******************************************************************************* /*static*/ EEClass * EEClass::CreateMinimalClass(LoaderHeap *pHeap, AllocMemTracker *pamTracker) { CONTRACTL { THROWS; GC_NOTRIGGER; } CONTRACTL_END; return new (pHeap, pamTracker) EEClass(); } //******************************************************************************* //----------------------------------------------------------------------------------- // Note: this only loads the type to CLASS_DEPENDENCIES_LOADED as this can be called // indirectly from DoFullyLoad() as part of accessibility checking. //----------------------------------------------------------------------------------- MethodTable *MethodTable::LoadEnclosingMethodTable(ClassLoadLevel targetLevel) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; } CONTRACTL_END mdTypeDef tdEnclosing = GetEnclosingCl(); if (tdEnclosing == mdTypeDefNil) { return NULL; } return ClassLoader::LoadTypeDefThrowing(GetModule(), tdEnclosing, ClassLoader::ThrowIfNotFound, ClassLoader::PermitUninstDefOrRef, tdNoTypes, targetLevel ).GetMethodTable(); } #ifdef FEATURE_METADATA_UPDATER //******************************************************************************* VOID EEClass::FixupFieldDescForEnC(MethodTable * pMT, EnCFieldDesc *pFD, mdFieldDef fieldDef) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END Module * pModule = pMT->GetModule(); IMDInternalImport *pImport = pModule->GetMDImport(); #ifdef LOGGING if (LoggingEnabled()) { LPCSTR szFieldName; if (FAILED(pImport->GetNameOfFieldDef(fieldDef, &szFieldName))) { szFieldName = "Invalid FieldDef record"; } LOG((LF_ENC, LL_INFO100, "EEClass::FixupFieldDescForEnC '%s' (0x%08x)\n", szFieldName, fieldDef)); } #endif //LOGGING #ifdef _DEBUG BOOL shouldBreak = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_EncFixupFieldBreak); if (shouldBreak > 0) { _ASSERTE(!"EncFixupFieldBreak"); } #endif // _DEBUG // MethodTableBuilder uses the stacking allocator for most of it's // working memory requirements, so this makes sure to free the memory // once this function is out of scope. ACQUIRE_STACKING_ALLOCATOR(pStackingAllocator); // Collect the attributes for the field mdToken fieldDefs[1] = { fieldDef }; DWORD fieldAttrs[1]; IfFailThrow(pImport->GetFieldDefProps(fieldDefs[0], &fieldAttrs[0])); MethodTableBuilder::bmtMetaDataInfo bmtMetaData; bmtMetaData.cFields = ARRAY_SIZE(fieldDefs); bmtMetaData.pFields = fieldDefs; bmtMetaData.pFieldAttrs = fieldAttrs; // We need to alloc the memory, but don't have to fill it in. InitializeFieldDescs // will copy pFD (1st arg) into here. FieldDesc* fieldDescs[1]; MethodTableBuilder::bmtMethAndFieldDescs bmtMFDescs; bmtMFDescs.ppFieldDescList = fieldDescs; MethodTableBuilder::bmtFieldPlacement bmtFP; // This simulates the environment that BuildMethodTableThrowing creates // just enough to run InitializeFieldDescs MethodTableBuilder::bmtErrorInfo bmtError; bmtError.pModule = pModule; bmtError.cl = pMT->GetCl(); bmtError.dMethodDefInError = mdTokenNil; bmtError.szMethodNameForError = NULL; MethodTableBuilder::bmtInternalInfo bmtInternal; bmtInternal.pModule = pModule; bmtInternal.pInternalImport = pImport; bmtInternal.pParentMT = pMT->GetParentMethodTable(); MethodTableBuilder::bmtProperties bmtProp; bmtProp.fIsValueClass = !!pMT->IsValueType(); MethodTableBuilder::bmtEnumFieldInfo bmtEnumFields(bmtInternal.pInternalImport); if (pFD->IsStatic()) { bmtEnumFields.dwNumStaticFields = 1; } else { _ASSERTE(!pMT->IsValueType()); bmtEnumFields.dwNumInstanceFields = 1; } // We shouldn't have to fill this in b/c we're not allowed to EnC value classes, or // anything else with layout info associated with it. // Provide 2, 1 placeholder and 1 for the actual field - see BuildMethodTableThrowing(). LayoutRawFieldInfo layoutRawFieldInfos[2]; // If not NULL, it means there are some by-value fields, and this contains an entry for each instance or static field, // which is NULL if not a by value field, and points to the EEClass of the field if a by value field. Instance fields // come first, statics come second. MethodTable** pByValueClassCache = NULL; AllocMemTracker dummyAmTracker; EEClass* pClass = pMT->GetClass(); MethodTableBuilder builder(pMT, pClass, pStackingAllocator, &dummyAmTracker); TypeHandle thisTH(pMT); SigTypeContext typeContext(thisTH); MethodTableBuilder::bmtGenericsInfo genericsInfo; genericsInfo.typeContext = typeContext; builder.SetBMTData(pMT->GetLoaderAllocator(), &bmtError, &bmtProp, NULL, NULL, NULL, &bmtMetaData, NULL, &bmtMFDescs, &bmtFP, &bmtInternal, NULL, NULL, &genericsInfo, &bmtEnumFields); { GCX_PREEMP(); unsigned totalDeclaredFieldSize = 0; builder.InitializeFieldDescs(pFD, layoutRawFieldInfos, &bmtInternal, &genericsInfo, &bmtMetaData, &bmtEnumFields, &bmtError, &pByValueClassCache, &bmtMFDescs, &bmtFP, &totalDeclaredFieldSize); } dummyAmTracker.SuppressRelease(); pFD->SetMethodTable(pMT); // We set this when we first created the FieldDesc, but initializing the FieldDesc // may have overwritten it so we need to set it again. pFD->SetEnCNew(); return; } //--------------------------------------------------------------------------------------- // // AddField - called when a new field is added by EnC // // Since instances of this class may already exist on the heap, we can't change the // runtime layout of the object to accommodate the new field. Instead we hang the field // off the syncblock (for instance fields) or in the FieldDesc for static fields. // // Here we just create the FieldDesc and link it to the class. The actual storage will // be created lazily on demand. // HRESULT EEClass::AddField(MethodTable* pMT, mdFieldDef fieldDef, FieldDesc** ppNewFD) { CONTRACTL { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(pMT != NULL); PRECONDITION(ppNewFD != NULL); } CONTRACTL_END; HRESULT hr; Module * pModule = pMT->GetModule(); IMDInternalImport *pImport = pModule->GetMDImport(); #ifdef LOGGING if (LoggingEnabled()) { LPCSTR szFieldName; if (FAILED(pImport->GetNameOfFieldDef(fieldDef, &szFieldName))) { szFieldName = "Invalid FieldDef record"; } LOG((LF_ENC, LL_INFO100, "EEClass::AddField '%s' tok:0x%08x\n", szFieldName, fieldDef)); } #endif //LOGGING // We can only add fields to normal classes if (pMT->HasLayout() || pMT->IsValueType()) { return CORDBG_E_ENC_CANT_ADD_FIELD_TO_VALUE_OR_LAYOUT_CLASS; } // We only add private fields. // This may not be strictly necessary, but helps avoid any semantic confusion with // existing code etc. DWORD dwFieldAttrs; IfFailThrow(pImport->GetFieldDefProps(fieldDef, &dwFieldAttrs)); FieldDesc* pNewFD; if (FAILED(hr = AddFieldDesc(pMT, fieldDef, dwFieldAttrs, &pNewFD))) { LOG((LF_ENC, LL_INFO100, "EEClass::AddField failed: 0x%08x\n", hr)); return hr; } // Store the FieldDesc into the module's field list // This should not be done for instantiated types. Only fields on the // open type are added to the module directly. This check is a // consequence of calling AddField() for EnC static fields on generics. if (!pMT->HasInstantiation()) { pModule->EnsureFieldDefCanBeStored(fieldDef); pModule->EnsuredStoreFieldDef(fieldDef, pNewFD); } LOG((LF_ENC, LL_INFO100, "EEClass::AddField Added pFD:%p for token 0x%08x\n", pNewFD, fieldDef)); // If the type is generic, then we need to update all existing instantiated types if (pMT->IsGenericTypeDefinition()) { LOG((LF_ENC, LL_INFO100, "EEClass::AddField Looking for existing instantiations in all assemblies\n")); PTR_AppDomain pDomain = AppDomain::GetCurrentDomain(); AppDomain::AssemblyIterator appIt = pDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); bool isStaticField = !!pNewFD->IsStatic(); CollectibleAssemblyHolder pDomainAssembly; while (appIt.Next(pDomainAssembly.This()) && SUCCEEDED(hr)) { Module* pMod = pDomainAssembly->GetModule(); LOG((LF_ENC, LL_INFO100, "EEClass::AddField Checking: %s mod:%p\n", pMod->GetDebugName(), pMod)); EETypeHashTable* paramTypes = pMod->GetAvailableParamTypes(); EETypeHashTable::Iterator it(paramTypes); EETypeHashEntry* pEntry; while (paramTypes->FindNext(&it, &pEntry)) { TypeHandle th = pEntry->GetTypeHandle(); if (th.IsTypeDesc()) continue; // For instance fields we only update instantiations of the generic MethodTable we updated above. // For static fields we update the the canonical version and instantiations. MethodTable* pMTMaybe = th.AsMethodTable(); if ((!isStaticField && !pMTMaybe->IsCanonicalMethodTable()) || !pMT->HasSameTypeDefAs(pMTMaybe)) { continue; } FieldDesc* pNewFDUnused; if (FAILED(AddFieldDesc(pMTMaybe, fieldDef, dwFieldAttrs, &pNewFDUnused))) { LOG((LF_ENC, LL_INFO100, "EEClass::AddField failed: 0x%08x\n", hr)); EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_FAILFAST, W("Failed to add field to existing instantiated type instance")); return E_FAIL; } } } } // Success, return the new FieldDesc *ppNewFD = pNewFD; return S_OK; } //--------------------------------------------------------------------------------------- // // AddFieldDesc - called when a new FieldDesc needs to be created and added for EnC // HRESULT EEClass::AddFieldDesc( MethodTable* pMT, mdMethodDef fieldDef, DWORD dwFieldAttrs, FieldDesc** ppNewFD) { CONTRACTL { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(pMT != NULL); PRECONDITION(ppNewFD != NULL); } CONTRACTL_END; LOG((LF_ENC, LL_INFO100, "EEClass::AddFieldDesc pMT:%p, %s <- tok:0x%08x attrs:%u\n", pMT, pMT->debug_m_szClassName, fieldDef, dwFieldAttrs)); Module* pModule = pMT->GetModule(); IMDInternalImport* pImport = pModule->GetMDImport(); LoaderAllocator* pAllocator = pMT->GetLoaderAllocator(); // Here we allocate a FieldDesc and set just enough info to be able to fix it up later // when we're running in managed code. EnCAddedFieldElement *pAddedField = (EnCAddedFieldElement *) (void*)pAllocator->GetHighFrequencyHeap()->AllocMem_NoThrow(S_SIZE_T(sizeof(EnCAddedFieldElement))); if (!pAddedField) { return E_OUTOFMEMORY; } pAddedField->Init( fieldDef, IsFdStatic(dwFieldAttrs) ); EnCFieldDesc *pNewFD = &pAddedField->m_fieldDesc; // Get the EnCEEClassData for this class // Don't adjust EEClass stats b/c EnC fields shouldn't touch EE data structures. // We'll just update our private EnC structures instead. _ASSERTE(pModule->IsEditAndContinueEnabled()); EnCEEClassData* pEnCClass = ((EditAndContinueModule*)pModule)->GetEnCEEClassData(pMT); if (!pEnCClass) return E_FAIL; // Add the field element to the list of added fields for this class pEnCClass->AddField(pAddedField); pNewFD->SetMethodTable(pMT); // Record that we are adding a new static field. Static generic fields // are added for currently non-instantiated types during type construction. // We want to limit the cost of making the check at that time so we use // a bit on the EEClass to indicate we've added a static field and it should // be checked. if (IsFdStatic(dwFieldAttrs)) pMT->GetClass()->SetHasEnCStaticFields(); // Success, return the new FieldDesc *ppNewFD = pNewFD; return S_OK; } //--------------------------------------------------------------------------------------- // // AddMethod - called when a new method is added by EnC // // The method has already been added to the metadata with token methodDef. // Create a new MethodDesc for the method, add to the associated EEClass and // update any existing Generic instantiations if the MethodTable represents a // generic type. // HRESULT EEClass::AddMethod(MethodTable* pMT, mdMethodDef methodDef, MethodDesc** ppMethod) { CONTRACTL { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(pMT != NULL); PRECONDITION(methodDef != mdTokenNil); } CONTRACTL_END; HRESULT hr; Module* pModule = pMT->GetModule(); IMDInternalImport* pImport = pModule->GetMDImport(); #ifdef LOGGING if (LoggingEnabled()) { LPCSTR szMethodName; if (FAILED(pImport->GetNameOfMethodDef(methodDef, &szMethodName))) { szMethodName = "Invalid MethodDef record"; } LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod '%s' tok:0x%08x\n", szMethodName, methodDef)); } #endif //LOGGING DWORD dwDescrOffset; DWORD dwImplFlags; if (FAILED(pImport->GetMethodImplProps(methodDef, &dwDescrOffset, &dwImplFlags))) return COR_E_BADIMAGEFORMAT; DWORD dwMemberAttrs; if (FAILED(pImport->GetMethodDefProps(methodDef, &dwMemberAttrs))) return COR_E_BADIMAGEFORMAT; // Refuse to add other special cases if (IsReallyMdPinvokeImpl(dwMemberAttrs) || (pMT->IsInterface() && !IsMdStatic(dwMemberAttrs)) || IsMiRuntime(dwImplFlags)) { _ASSERTE(! "**Error** EEClass::AddMethod only IL private non-virtual methods are supported"); LOG((LF_ENC, LL_INFO100, "**Error** EEClass::AddMethod only IL private non-virtual methods are supported\n")); return CORDBG_E_ENC_EDIT_NOT_SUPPORTED; } #ifdef _DEBUG // Validate that this methodDef correctly has a parent typeDef mdTypeDef parentTypeDef; if (FAILED(pImport->GetParentToken(methodDef, &parentTypeDef))) { _ASSERTE(! "**Error** EEClass::AddMethod parent token not found"); LOG((LF_ENC, LL_INFO100, "**Error** EEClass::AddMethod parent token not found\n")); return E_FAIL; } #endif // _DEBUG MethodDesc* pNewMD; if (FAILED(hr = AddMethodDesc(pMT, methodDef, dwImplFlags, dwMemberAttrs, &pNewMD))) { LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod failed: 0x%08x\n", hr)); return hr; } // Store the new MethodDesc into the collection for this class pModule->EnsureMethodDefCanBeStored(methodDef); pModule->EnsuredStoreMethodDef(methodDef, pNewMD); LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod Added pMD:%p for token 0x%08x\n", pNewMD, methodDef)); // If the type is generic, then we need to update all existing instantiated types if (pMT->IsGenericTypeDefinition()) { LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod Looking for existing instantiations in all assemblies\n")); PTR_AppDomain pDomain = AppDomain::GetCurrentDomain(); AppDomain::AssemblyIterator appIt = pDomain->IterateAssembliesEx((AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; while (appIt.Next(pDomainAssembly.This()) && SUCCEEDED(hr)) { Module* pMod = pDomainAssembly->GetModule(); LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod Checking: %s mod:%p\n", pMod->GetDebugName(), pMod)); EETypeHashTable* paramTypes = pMod->GetAvailableParamTypes(); EETypeHashTable::Iterator it(paramTypes); EETypeHashEntry* pEntry; while (paramTypes->FindNext(&it, &pEntry)) { TypeHandle th = pEntry->GetTypeHandle(); if (th.IsTypeDesc()) continue; // Only update instantiations of the generic MethodTable we updated above. MethodTable* pMTMaybe = th.AsMethodTable(); if (!pMTMaybe->IsCanonicalMethodTable() || !pMT->HasSameTypeDefAs(pMTMaybe)) { continue; } MethodDesc* pNewMDUnused; if (FAILED(AddMethodDesc(pMTMaybe, methodDef, dwImplFlags, dwMemberAttrs, &pNewMDUnused))) { LOG((LF_ENC, LL_INFO100, "EEClass::AddMethod failed: 0x%08x\n", hr)); EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_FAILFAST, W("Failed to add method to existing instantiated type instance")); return E_FAIL; } } } } // Success - return the new MethodDesc if (ppMethod) *ppMethod = pNewMD; return S_OK; } //--------------------------------------------------------------------------------------- // // AddMethodDesc - called when a new MethodDesc needs to be created and added for EnC // HRESULT EEClass::AddMethodDesc( MethodTable* pMT, mdMethodDef methodDef, DWORD dwImplFlags, DWORD dwMemberAttrs, MethodDesc** ppNewMD) { CONTRACTL { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(pMT != NULL); PRECONDITION(methodDef != mdTokenNil); PRECONDITION(ppNewMD != NULL); } CONTRACTL_END; LOG((LF_ENC, LL_INFO100, "EEClass::AddMethodDesc pMT:%p, %s <- tok:0x%08x flags:%u attrs:%u\n", pMT, pMT->debug_m_szClassName, methodDef, dwImplFlags, dwMemberAttrs)); HRESULT hr; Module* pModule = pMT->GetModule(); IMDInternalImport* pImport = pModule->GetMDImport(); // Check if signature is generic. ULONG sigLen; PCCOR_SIGNATURE sig; if (FAILED(hr = pImport->GetSigOfMethodDef(methodDef, &sigLen, &sig))) return hr; uint32_t callConv = CorSigUncompressData(sig); DWORD classification = (callConv & IMAGE_CEE_CS_CALLCONV_GENERIC) ? mcInstantiated : mcIL; LoaderAllocator* pAllocator = pMT->GetLoaderAllocator(); // [TODO] OOM: InitMethodDesc will allocate loaderheap memory but leak it // on failure. This AllocMemTracker should be replaced with a real one. AllocMemTracker dummyAmTracker; // Create a new MethodDescChunk to hold the new MethodDesc. // Create the chunk somewhere we'll know is within range of the VTable. MethodDescChunk *pChunk = MethodDescChunk::CreateChunk(pAllocator->GetHighFrequencyHeap(), 1, // methodDescCount classification, TRUE, // fNonVtableSlot TRUE, // fNativeCodeSlot pMT, &dummyAmTracker); // Get the new MethodDesc (Note: The method desc memory is zero initialized) MethodDesc* pNewMD = pChunk->GetFirstMethodDesc(); EEClass* pClass = pMT->GetClass(); // This method runs on a debugger thread. Debugger threads do not have Thread object // that caches StackingAllocator, use a local StackingAllocator instead. StackingAllocator stackingAllocator; MethodTableBuilder::bmtInternalInfo bmtInternal; bmtInternal.pModule = pModule; bmtInternal.pInternalImport = NULL; bmtInternal.pParentMT = NULL; MethodTableBuilder builder(pMT, pClass, &stackingAllocator, &dummyAmTracker); builder.SetBMTData(pMT->GetLoaderAllocator(), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &bmtInternal); // Initialize the new MethodDesc EX_TRY { INDEBUG(LPCSTR debug_szMethodName); INDEBUG(if (FAILED(pImport->GetNameOfMethodDef(methodDef, &debug_szMethodName))) { debug_szMethodName = "Invalid MethodDef record"; }); builder.InitMethodDesc(pNewMD, classification, methodDef, dwImplFlags, dwMemberAttrs, TRUE, // fEnC 0, // RVA - non-zero only for NDirect pImport, NULL COMMA_INDEBUG(debug_szMethodName) COMMA_INDEBUG(pMT->GetDebugClassName()) COMMA_INDEBUG(NULL) ); pNewMD->SetTemporaryEntryPoint(&dummyAmTracker); // [TODO] if an exception is thrown, asserts will fire in EX_CATCH_HRESULT() // during an EnC operation due to the debugger thread not being able to // transition to COOP mode. } EX_CATCH_HRESULT(hr); if (S_OK != hr) return hr; dummyAmTracker.SuppressRelease(); _ASSERTE(pNewMD->IsEnCAddedMethod()); pNewMD->SetSlot(MethodTable::NO_SLOT); // we can't ever use the slot for EnC methods pClass->AddChunk(pChunk); *ppNewMD = pNewMD; return S_OK; } #endif // FEATURE_METADATA_UPDATER //--------------------------------------------------------------------------------------- // // Check that the class type parameters are used consistently in this signature blob // in accordance with their variance annotations // The signature is assumed to be well-formed but indices and arities might not be correct // BOOL EEClass::CheckVarianceInSig( DWORD numGenericArgs, BYTE * pVarianceInfo, Module * pModule, SigPointer psig, CorGenericParamAttr position) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; if (pVarianceInfo == NULL) return TRUE; CorElementType typ; IfFailThrow(psig.GetElemType(&typ)); switch (typ) { case ELEMENT_TYPE_STRING: case ELEMENT_TYPE_U: case ELEMENT_TYPE_I: case ELEMENT_TYPE_I1: case ELEMENT_TYPE_U1: case ELEMENT_TYPE_BOOLEAN: case ELEMENT_TYPE_I2: case ELEMENT_TYPE_U2: case ELEMENT_TYPE_CHAR: case ELEMENT_TYPE_I4: case ELEMENT_TYPE_U4: case ELEMENT_TYPE_I8: case ELEMENT_TYPE_U8: case ELEMENT_TYPE_R4: case ELEMENT_TYPE_R8: case ELEMENT_TYPE_VOID: case ELEMENT_TYPE_OBJECT: case ELEMENT_TYPE_TYPEDBYREF: case ELEMENT_TYPE_MVAR: case ELEMENT_TYPE_CLASS: case ELEMENT_TYPE_VALUETYPE: return TRUE; case ELEMENT_TYPE_VAR: { uint32_t index; IfFailThrow(psig.GetData(&index)); // This will be checked later anyway; so give up and don't indicate a variance failure if (index < 0 || index >= numGenericArgs) return TRUE; // Non-variant parameters are allowed to appear anywhere if (pVarianceInfo[index] == gpNonVariant) return TRUE; // Covariant and contravariant parameters can *only* appear in resp. covariant and contravariant positions return ((CorGenericParamAttr) (pVarianceInfo[index]) == position); } case ELEMENT_TYPE_GENERICINST: { IfFailThrow(psig.GetElemType(&typ)); mdTypeRef typeref; IfFailThrow(psig.GetToken(&typeref)); // The number of type parameters follows uint32_t ntypars; IfFailThrow(psig.GetData(&ntypars)); // If this is a value type, or position == gpNonVariant, then // we're disallowing covariant and contravariant completely if (typ == ELEMENT_TYPE_VALUETYPE || position == gpNonVariant) { for (unsigned i = 0; i < ntypars; i++) { if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant)) return FALSE; IfFailThrow(psig.SkipExactlyOne()); } } // Otherwise we need to take notice of the variance annotation on each type parameter to the generic type else { mdTypeDef typeDef; Module * pDefModule; // This will also be resolved later; so, give up and don't indicate a variance failure if (!ClassLoader::ResolveTokenToTypeDefThrowing(pModule, typeref, &pDefModule, &typeDef)) return TRUE; bool foundHasVarianceResult; if (!pDefModule->m_pTypeGenericInfoMap->HasVariance(typeDef, &foundHasVarianceResult) && foundHasVarianceResult) { // Fast path, now that we know there isn't variance uint32_t genericArgCount = pDefModule->m_pTypeGenericInfoMap->GetGenericArgumentCount(typeDef, pDefModule->GetMDImport()); for (uint32_t iGenericArgCount = 0; iGenericArgCount < genericArgCount; iGenericArgCount++) { if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant)) return FALSE; IfFailThrow(psig.SkipExactlyOne()); } } else { HENUMInternal hEnumGenericPars; if (FAILED(pDefModule->GetMDImport()->EnumInit(mdtGenericParam, typeDef, &hEnumGenericPars))) { pDefModule->GetAssembly()->ThrowTypeLoadException(pDefModule->GetMDImport(), typeDef, IDS_CLASSLOAD_BADFORMAT); } for (unsigned i = 0; i < ntypars; i++) { mdGenericParam tkTyPar; pDefModule->GetMDImport()->EnumNext(&hEnumGenericPars, &tkTyPar); DWORD flags; if (FAILED(pDefModule->GetMDImport()->GetGenericParamProps(tkTyPar, NULL, &flags, NULL, NULL, NULL))) { pDefModule->GetAssembly()->ThrowTypeLoadException(pDefModule->GetMDImport(), typeDef, IDS_CLASSLOAD_BADFORMAT); } CorGenericParamAttr genPosition = (CorGenericParamAttr) (flags & gpVarianceMask); // If the surrounding context is contravariant then we need to flip the variance of this parameter if (position == gpContravariant) { genPosition = genPosition == gpCovariant ? gpContravariant : genPosition == gpContravariant ? gpCovariant : gpNonVariant; } if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, genPosition)) return FALSE; IfFailThrow(psig.SkipExactlyOne()); } pDefModule->GetMDImport()->EnumClose(&hEnumGenericPars); } } return TRUE; } // Arrays behave covariantly case ELEMENT_TYPE_ARRAY: case ELEMENT_TYPE_SZARRAY: return CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, position); // Pointers behave non-variantly case ELEMENT_TYPE_BYREF: case ELEMENT_TYPE_PTR: return CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant); case ELEMENT_TYPE_FNPTR: { // Calling convention IfFailThrow(psig.GetData(NULL)); // Get arg count; uint32_t cArgs; IfFailThrow(psig.GetData(&cArgs)); // Conservatively, assume non-variance of function pointer types if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant)) return FALSE; IfFailThrow(psig.SkipExactlyOne()); for (unsigned i = 0; i < cArgs; i++) { if (!CheckVarianceInSig(numGenericArgs, pVarianceInfo, pModule, psig, gpNonVariant)) return FALSE; IfFailThrow(psig.SkipExactlyOne()); } return TRUE; } default: THROW_BAD_FORMAT(IDS_CLASSLOAD_BAD_VARIANCE_SIG, pModule); } return FALSE; } // EEClass::CheckVarianceInSig void ClassLoader::LoadExactParentAndInterfacesTransitively(MethodTable *pMT) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; TypeHandle thisTH(pMT); SigTypeContext typeContext(thisTH); IMDInternalImport* pInternalImport = pMT->GetMDImport(); MethodTable *pParentMT = pMT->GetParentMethodTable(); if (pParentMT != NULL && pParentMT->HasInstantiation()) { // Fill in exact parent if it's instantiated mdToken crExtends; IfFailThrow(pInternalImport->GetTypeDefProps( pMT->GetCl(), NULL, &crExtends)); _ASSERTE(!IsNilToken(crExtends)); _ASSERTE(TypeFromToken(crExtends) == mdtTypeSpec); TypeHandle newParent = ClassLoader::LoadTypeDefOrRefOrSpecThrowing(pMT->GetModule(), crExtends, &typeContext, ClassLoader::ThrowIfNotFound, ClassLoader::FailIfUninstDefOrRef, ClassLoader::LoadTypes, CLASS_LOAD_EXACTPARENTS, TRUE); MethodTable* pNewParentMT = newParent.AsMethodTable(); if (pNewParentMT != pParentMT) { LOG((LF_CLASSLOADER, LL_INFO1000, "GENERICS: Replaced approximate parent %s with exact parent %s from token %x\n", pParentMT->GetDebugClassName(), pNewParentMT->GetDebugClassName(), crExtends)); // SetParentMethodTable is not used here since we want to update the indirection cell in the NGen case *pMT->GetParentMethodTableValuePtr() = pNewParentMT; pParentMT = pNewParentMT; } } if (pParentMT != NULL) { EnsureLoaded(pParentMT, CLASS_LOAD_EXACTPARENTS); } if (pParentMT != NULL && pParentMT->HasPerInstInfo()) { // Copy down all inherited dictionary pointers which we // could not embed. DWORD nDicts = pParentMT->GetNumDicts(); for (DWORD iDict = 0; iDict < nDicts; iDict++) { if (pMT->GetPerInstInfo()[iDict] != pParentMT->GetPerInstInfo()[iDict]) { pMT->GetPerInstInfo()[iDict] = pParentMT->GetPerInstInfo()[iDict]; } } } MethodTableBuilder::LoadExactInterfaceMap(pMT); #ifdef _DEBUG if (g_pConfig->ShouldDumpOnClassLoad(pMT->GetDebugClassName())) { pMT->Debug_DumpInterfaceMap("Exact"); } #endif //_DEBUG } // ClassLoader::LoadExactParentAndInterfacesTransitively namespace { #ifdef FEATURE_METADATA_UPDATER void CreateAllEnCStaticFields(MethodTable* pMT, MethodTable* pMTCanon, EditAndContinueModule* pModule) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMT)); PRECONDITION(pMT->HasInstantiation()); PRECONDITION(CheckPointer(pMTCanon)); PRECONDITION(pMTCanon->IsCanonicalMethodTable()); PRECONDITION(CheckPointer(pModule)); } CONTRACTL_END; LOG((LF_ENC, LL_INFO100, "CreateAllEnCStaticFields: pMT:%p pMTCanon:%p\n", pMT, pMTCanon)); #ifdef _DEBUG // Sanity check there is relevant EnC data. EnCEEClassData* pEnCClass = pModule->GetEnCEEClassData(pMTCanon); _ASSERTE(pEnCClass != NULL && pEnCClass->GetAddedStaticFields() > 0); #endif // _DEBUG // Iterate over the Canonical MethodTable and see if there are any EnC static fields // we need to add to the current MethodTable. EncApproxFieldDescIterator canonFieldIter( pMTCanon, ApproxFieldDescIterator::STATIC_FIELDS, (EncApproxFieldDescIterator::FixUpEncFields | EncApproxFieldDescIterator::OnlyEncFields)); PTR_FieldDesc pCanonFD; while ((pCanonFD = canonFieldIter.Next()) != NULL) { mdFieldDef canonTok = pCanonFD->GetMemberDef(); // Check if the current MethodTable already has an entry for // this FieldDesc. bool shouldAdd = true; EncApproxFieldDescIterator mtFieldIter( pMT, ApproxFieldDescIterator::STATIC_FIELDS, (EncApproxFieldDescIterator::FixUpEncFields | EncApproxFieldDescIterator::OnlyEncFields)); PTR_FieldDesc pFD; while ((pFD = mtFieldIter.Next()) != NULL) { mdFieldDef tok = pFD->GetMemberDef(); if (tok == canonTok) { shouldAdd = false; break; } } // The FieldDesc already exists, no need to add. if (!shouldAdd) continue; LOG((LF_ENC, LL_INFO100, "CreateAllEnCStaticFields: Must add pCanonFD:%p\n", pCanonFD)); { GCX_COOP(); PTR_FieldDesc pNewFD; HRESULT hr = EEClass::AddField(pMT, canonTok, &pNewFD); if (FAILED(hr)) { LOG((LF_ENC, LL_INFO100, "CreateAllEnCStaticFields failed: 0x%08x\n", hr)); EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_FAILFAST, W("Failed to add static field to instantiated type instance")); } } } } #endif // FEATURE_METADATA_UPDATER } // CLASS_LOAD_EXACTPARENTS phase of loading: // * Load the base class at exact instantiation // * Recurse LoadExactParents up parent hierarchy // * Load explicitly declared interfaces on this class at exact instantiation // * Fixup vtable // /*static*/ void ClassLoader::LoadExactParents(MethodTable* pMT) { CONTRACT_VOID { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMT)); POSTCONDITION(pMT->CheckLoadLevel(CLASS_LOAD_EXACTPARENTS)); } CONTRACT_END; if (!pMT->IsCanonicalMethodTable()) { EnsureLoaded(TypeHandle(pMT->GetCanonicalMethodTable()), CLASS_LOAD_EXACTPARENTS); } LoadExactParentAndInterfacesTransitively(pMT); if (pMT->GetClass()->HasVTableMethodImpl()) { MethodTableBuilder::CopyExactParentSlots(pMT); PropagateCovariantReturnMethodImplSlots(pMT); } #ifdef FEATURE_METADATA_UPDATER // Generics for EnC - create static FieldDescs. // Instance FieldDescs don't need to be created here because they // are added during type load by reading the updated metadata tables. if (pMT->HasInstantiation()) { // Check if the MethodTable has any EnC static fields PTR_MethodTable pMTCanon = pMT->GetCanonicalMethodTable(); if (pMTCanon->GetClass()->HasEnCStaticFields()) { Module* pModule = pMT->GetModule(); if (pModule->IsEditAndContinueEnabled()) CreateAllEnCStaticFields(pMT, pMTCanon, (EditAndContinueModule*)pModule); } } #endif // FEATURE_METADATA_UPDATER // We can now mark this type as having exact parents pMT->SetHasExactParent(); RETURN; } // Get CorElementType of the reduced type of a type. // The reduced type concept is described in ECMA 335 chapter I.8.7 // /*static*/ CorElementType ClassLoader::GetReducedTypeElementType(TypeHandle hType) { CorElementType elemType = hType.GetVerifierCorElementType(); switch (elemType) { case ELEMENT_TYPE_U1: return ELEMENT_TYPE_I1; case ELEMENT_TYPE_U2: return ELEMENT_TYPE_I2; case ELEMENT_TYPE_U4: return ELEMENT_TYPE_I4; case ELEMENT_TYPE_U8: return ELEMENT_TYPE_I8; case ELEMENT_TYPE_U: return ELEMENT_TYPE_I; default: return elemType; } } // Get CorElementType of the verification type of a type. // The verification type concepts is described in ECMA 335 chapter I.8.7 // /*static*/ CorElementType ClassLoader::GetVerificationTypeElementType(TypeHandle hType) { CorElementType reducedTypeElementType = GetReducedTypeElementType(hType); switch (reducedTypeElementType) { case ELEMENT_TYPE_BOOLEAN: return ELEMENT_TYPE_I1; case ELEMENT_TYPE_CHAR: return ELEMENT_TYPE_I2; default: return reducedTypeElementType; } } // Check if verification types of two types are equal // /*static*/ bool ClassLoader::AreVerificationTypesEqual(TypeHandle hType1, TypeHandle hType2) { if (hType1 == hType2) { return true; } CorElementType e1 = GetVerificationTypeElementType(hType1); if (!CorIsPrimitiveType(e1)) { return false; } CorElementType e2 = GetVerificationTypeElementType(hType2); return e1 == e2; } // Check if signatures of two function pointers are compatible // Note - this is a simplified version of what's described in the ECMA spec and it considers // pointers to be method-signature-compatible-with only if the signatures are the same. // /*static*/ bool ClassLoader::IsMethodSignatureCompatibleWith(FnPtrTypeDesc* fn1TD, FnPtrTypeDesc* fn2TD) { if (fn1TD->GetCallConv() != fn1TD->GetCallConv()) { return false; } if (fn1TD->GetNumArgs() != fn2TD->GetNumArgs()) { return false; } TypeHandle* pFn1ArgTH = fn1TD->GetRetAndArgTypes(); TypeHandle* pFn2ArgTH = fn2TD->GetRetAndArgTypes(); for (DWORD i = 0; i < fn1TD->GetNumArgs() + 1; i++) { if (pFn1ArgTH[i] != pFn2ArgTH[i]) { return false; } } return true; } // Checks if two types are compatible according to compatible-with as described in ECMA 335 I.8.7.1 // Most of the checks are performed by the CanCastTo, but with some cases pre-filtered out. // /*static*/ bool ClassLoader::IsCompatibleWith(TypeHandle hType1, TypeHandle hType2) { // Structs can be cast to the interfaces they implement, but they are not compatible according to ECMA I.8.7.1 bool isCastFromValueTypeToReferenceType = hType2.IsValueType() && !hType1.IsValueType(); if (isCastFromValueTypeToReferenceType) { return false; } // Managed pointers are compatible only if they are pointer-element-compatible-with as described in ECMA I.8.7.2 if (hType1.IsByRef() && hType2.IsByRef()) { return AreVerificationTypesEqual(hType1.GetTypeParam(), hType2.GetTypeParam()); } // Unmanaged pointers are handled the same way as managed pointers if (hType1.IsPointer() && hType2.IsPointer()) { return AreVerificationTypesEqual(hType1.GetTypeParam(), hType2.GetTypeParam()); } // Function pointers are compatible only if they are method-signature-compatible-with as described in ECMA I.8.7.1 if (hType1.IsFnPtrType() && hType2.IsFnPtrType()) { return IsMethodSignatureCompatibleWith(hType1.AsFnPtrType(), hType2.AsFnPtrType()); } // None of the types can be a managed pointer, a pointer or a function pointer here, // all the valid cases were handled above. if (hType1.IsByRef() || hType2.IsByRef() || hType1.IsPointer() || hType2.IsPointer() || hType1.IsFnPtrType() || hType2.IsFnPtrType()) { return false; } MethodTable* pMT1 = hType1.GetMethodTable(); if (pMT1 != NULL) { // Nullable can be cast to T, but this is not compatible according to ECMA I.8.7.1 bool isCastFromNullableOfTtoT = pMT1->IsNullable() && hType2.IsEquivalentTo(pMT1->GetInstantiation()[0]); if (isCastFromNullableOfTtoT) { return false; } } return hType2.CanCastTo(hType1, NULL); } /*static*/ void ClassLoader::ValidateMethodsWithCovariantReturnTypes(MethodTable* pMT) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; // Validate that the return types on overriding methods with covariant return types are // compatible with the return type of the method being overridden. Compatibility rules are defined by // ECMA I.8.7.1, which is what the CanCastTo() API checks. // // Validation not applicable to interface types and value types, since these are not currently // supported with the covariant return feature if (pMT->IsInterface() || pMT->IsValueType()) return; MethodTable* pParentMT = pMT->GetParentMethodTable(); if (pParentMT == NULL) return; // Step 1: Validate compatibility of return types on overriding methods if (pMT->GetClass()->HasCovariantOverride() && (!pMT->GetModule()->IsReadyToRun() || !pMT->GetModule()->GetReadyToRunInfo()->SkipTypeValidation())) { for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++) { if (pMT->GetRestoredSlot(i) == pParentMT->GetRestoredSlot(i)) { // The real check is that the MethodDesc's must not match, but a simple VTable check will // work most of the time, and is far faster than the GetMethodDescForSlot method. _ASSERTE(pMT->GetMethodDescForSlot_NoThrow(i) == pParentMT->GetMethodDescForSlot_NoThrow(i)); continue; } MethodDesc* pMD = pMT->GetMethodDescForSlot(i); MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i); if (pMD == pParentMD) continue; if (!pMD->RequiresCovariantReturnTypeChecking() && !pParentMD->RequiresCovariantReturnTypeChecking()) continue; // Locate the MethodTable defining the pParentMD. MethodTable* pDefinitionParentMT = pParentMT; while (pDefinitionParentMT->GetCanonicalMethodTable() != pParentMD->GetMethodTable()) { pDefinitionParentMT = pDefinitionParentMT->GetParentMethodTable(); } SigTypeContext context1(pDefinitionParentMT->GetInstantiation(), pMD->GetMethodInstantiation()); MetaSig methodSig1(pParentMD); TypeHandle hType1 = methodSig1.GetReturnProps().GetTypeHandleThrowing(pParentMD->GetModule(), &context1, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS); SigTypeContext context2(pMT->GetInstantiation(), pMD->GetMethodInstantiation()); MetaSig methodSig2(pMD); TypeHandle hType2 = methodSig2.GetReturnProps().GetTypeHandleThrowing(pMD->GetModule(), &context2, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS); if (!IsCompatibleWith(hType1, hType2)) { SString strAssemblyName; pMD->GetAssembly()->GetDisplayName(strAssemblyName); SString strInvalidTypeName; TypeString::AppendType(strInvalidTypeName, TypeHandle(pMD->GetMethodTable())); SString strInvalidMethodName; SString strParentMethodName; { CONTRACT_VIOLATION(LoadsTypeViolation); TypeString::AppendMethod(strInvalidMethodName, pMD, pMD->GetMethodInstantiation()); TypeString::AppendMethod(strParentMethodName, pParentMD, pParentMD->GetMethodInstantiation()); } COMPlusThrow( kTypeLoadException, IDS_CLASSLOAD_MI_BADRETURNTYPE, strInvalidMethodName, strInvalidTypeName, strAssemblyName, strParentMethodName); } } } } /*static*/ void ClassLoader::PropagateCovariantReturnMethodImplSlots(MethodTable* pMT) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; // Propagate an overriding MethodImpl to all applicable vtable slots if the MethodImpl // has the PreserveBaseOverrides attribute. This is to ensure that if we use the signature of one of // the base type methods to call the overriding method, we still execute the overriding method. // // Consider this case: // // class A { // RetType VirtualFunction() { } // } // class B : A { // [PreserveBaseOverrides] // DerivedRetType VirtualFunction() { .override A.VirtualFunction } // } // class C : B { // MoreDerivedRetType VirtualFunction() { .override A.VirtualFunction } // } // // NOTE: Typically the attribute would be added to the MethodImpl on C, but was omitted in this example to // illustrate how its presence on a MethodImpl on the base type can propagate as well. In other words, // think of it as applying to the vtable slot itself, so any MethodImpl that overrides this slot on a // derived type will propagate to all other applicable vtable slots. // // Given an object of type C, the attribute will ensure that: // callvirt RetType A::VirtualFunc() -> executes the MethodImpl on C // callvirt DerivedRetType B::VirtualFunc() -> executes the MethodImpl on C // callvirt MoreDerivedRetType C::VirtualFunc() -> executes the MethodImpl on C // // Without the attribute, the second callvirt would normally execute the MethodImpl on B (the MethodImpl on // C does not override the vtable slot of B's MethodImpl, but only overrides the declaring method's vtable slot. // // Validation not applicable to interface types and value types, since these are not currently // supported with the covariant return feature if (pMT->IsInterface() || pMT->IsValueType()) return; MethodTable* pParentMT = pMT->GetParentMethodTable(); if (pParentMT == NULL) return; // Propagate overriding MethodImpls to applicable vtable slots if the declaring method has the attribute if (pMT->GetClass()->HasVTableMethodImpl()) { MethodTable::MethodDataWrapper hMTData(MethodTable::GetMethodData(pMT, MethodDataComputeOptions::CacheOnly)); for (WORD i = 0; i < pParentMT->GetNumVirtuals(); i++) { if (pMT->GetRestoredSlot(i) == pParentMT->GetRestoredSlot(i)) { // The real check is that the MethodDesc's must not match, but a simple VTable check will // work most of the time, and is far faster than the GetMethodDescForSlot method. _ASSERTE(pMT->GetMethodDescForSlot_NoThrow(i) == pParentMT->GetMethodDescForSlot_NoThrow(i)); continue; } MethodDesc* pMD = pMT->GetMethodDescForSlot(i); MethodDesc* pParentMD = pParentMT->GetMethodDescForSlot(i); if (pMD == pParentMD) continue; // If the bit is not set on this method, but we reach here because it's been set on the method at the same slot on // the base type, set the bit for the current method to ensure any future overriding method down the chain gets checked. if (!pMD->RequiresCovariantReturnTypeChecking() && pParentMD->RequiresCovariantReturnTypeChecking()) pMD->SetRequiresCovariantReturnTypeChecking(); // The attribute is only applicable to MethodImpls. For anything else, it will be treated as a no-op if (!pMD->IsMethodImpl()) continue; // Search if the attribute has been applied on this vtable slot, either by the current MethodImpl, or by a previous // MethodImpl somewhere in the base type hierarchy. bool foundAttribute = false; MethodTable* pCurrentMT = pMT; while (!foundAttribute && pCurrentMT != NULL && i < pCurrentMT->GetNumVirtuals()) { MethodDesc* pCurrentMD = pCurrentMT->GetMethodDescForSlot(i); // The attribute is only applicable to MethodImpls. For anything else, it will be treated as a no-op if (pCurrentMD->IsMethodImpl()) { BYTE* pVal = NULL; ULONG cbVal = 0; if (pCurrentMD->GetCustomAttribute(WellKnownAttribute::PreserveBaseOverridesAttribute, (const void**)&pVal, &cbVal) == S_OK) foundAttribute = true; } pCurrentMT = pCurrentMT->GetParentMethodTable(); } if (!foundAttribute) continue; // Search for any vtable slot still pointing at the parent method, and update it with the current overriding method for (WORD j = i; j < pParentMT->GetNumVirtuals(); j++) { MethodDesc* pCurrentMD = pMT->GetMethodDescForSlot(j); if (pCurrentMD == pParentMD) { // This is a vtable slot that needs to be updated to the new overriding method because of the // presence of the attribute. pMT->SetSlot(j, pMT->GetSlot(i)); _ASSERT(pMT->GetMethodDescForSlot_NoThrow(j) == pMD); if (!hMTData.IsNull()) hMTData->UpdateImplMethodDesc(pMD, j); } } } } } //******************************************************************************* // // Debugger notification // BOOL TypeHandle::NotifyDebuggerLoad(AppDomain *pDomain, BOOL attaching) const { LIMITED_METHOD_CONTRACT; if (!CORDebuggerAttached()) { return FALSE; } if (!GetModule()->IsVisibleToDebugger()) { return FALSE; } return g_pDebugInterface->LoadClass( *this, GetCl(), GetModule(), pDomain); } //******************************************************************************* void TypeHandle::NotifyDebuggerUnload(AppDomain *pDomain) const { LIMITED_METHOD_CONTRACT; if (!GetModule()->IsVisibleToDebugger()) return; if (!pDomain->IsDebuggerAttached()) return; g_pDebugInterface->UnloadClass(GetCl(), GetModule(), pDomain); } //******************************************************************************* // Given the (generics-shared or generics-exact) value class method, find the // (generics-shared) unboxing Stub for the given method . We search the vtable. // // This is needed when creating a delegate to an instance method in a value type MethodDesc* MethodTable::GetBoxedEntryPointMD(MethodDesc *pMD) { CONTRACT (MethodDesc *) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(IsValueType()); PRECONDITION(!pMD->ContainsGenericVariables()); PRECONDITION(!pMD->IsUnboxingStub()); POSTCONDITION(RETVAL->IsUnboxingStub()); } CONTRACT_END; RETURN MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, pMD->GetMethodTable(), TRUE /* get unboxing entry point */, pMD->GetMethodInstantiation(), FALSE /* no allowInstParam */ ); } //******************************************************************************* // Given the unboxing value class method, find the non-unboxing method // This is used when generating the code for an BoxedEntryPointStub. MethodDesc* MethodTable::GetUnboxedEntryPointMD(MethodDesc *pMD) { CONTRACT (MethodDesc *) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(IsValueType()); // reflection needs to call this for methods in non instantiated classes, // so move the assert to the caller when needed //PRECONDITION(!pMD->ContainsGenericVariables()); PRECONDITION(pMD->IsUnboxingStub()); POSTCONDITION(!RETVAL->IsUnboxingStub()); } CONTRACT_END; BOOL allowInstParam = (pMD->GetNumGenericMethodArgs() == 0); RETURN MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, this, FALSE /* don't get unboxing entry point */, pMD->GetMethodInstantiation(), allowInstParam); } //******************************************************************************* // Given the unboxing value class method, find the non-unboxing method // This is used when generating the code for an BoxedEntryPointStub. MethodDesc* MethodTable::GetExistingUnboxedEntryPointMD(MethodDesc *pMD) { CONTRACT (MethodDesc *) { THROWS; GC_NOTRIGGER; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(IsValueType()); // reflection needs to call this for methods in non instantiated classes, // so move the assert to the caller when needed //PRECONDITION(!pMD->ContainsGenericVariables()); PRECONDITION(pMD->IsUnboxingStub()); POSTCONDITION(!RETVAL->IsUnboxingStub()); } CONTRACT_END; BOOL allowInstParam = (pMD->GetNumGenericMethodArgs() == 0); RETURN MethodDesc::FindOrCreateAssociatedMethodDesc(pMD, this, FALSE /* don't get unboxing entry point */, pMD->GetMethodInstantiation(), allowInstParam, FALSE, /* forceRemotableMethod */ FALSE /* allowCreate */ ); } #endif // !DACCESS_COMPILE //******************************************************************************* #if !defined(FEATURE_HFA) bool MethodTable::IsHFA() { LIMITED_METHOD_CONTRACT; #ifdef DACCESS_COMPILE return false; #else if (GetClass()->GetMethodTable()->IsValueType()) { return GetClass()->CheckForHFA(); } else { return false; } #endif } #endif // !FEATURE_HFA //******************************************************************************* int MethodTable::GetVectorSize() { // This is supported for finding HVA types for Arm64. In order to support the altjit, // we support this on 64-bit platforms (i.e. Arm64 and X64). #ifdef TARGET_64BIT if (IsIntrinsicType()) { LPCUTF8 namespaceName; LPCUTF8 className = GetFullyQualifiedNameInfo(&namespaceName); int vectorSize = 0; if (strcmp(className, "Vector`1") == 0) { _ASSERTE(strcmp(namespaceName, "System.Numerics") == 0); vectorSize = GetNumInstanceFieldBytes(); } else if (strcmp(className, "Vector128`1") == 0) { _ASSERTE(strcmp(namespaceName, "System.Runtime.Intrinsics") == 0); vectorSize = 16; } else if (strcmp(className, "Vector64`1") == 0) { _ASSERTE(strcmp(namespaceName, "System.Runtime.Intrinsics") == 0); vectorSize = 8; } if (vectorSize != 0) { // We need to verify that T (the element or "base" type) is a primitive type. TypeHandle typeArg = GetInstantiation()[0]; CorElementType corType = typeArg.GetSignatureCorElementType(); if (((corType >= ELEMENT_TYPE_I1) && (corType <= ELEMENT_TYPE_R8)) || (corType == ELEMENT_TYPE_I) || (corType == ELEMENT_TYPE_U)) { return vectorSize; } } } #endif // TARGET_64BIT return 0; } //******************************************************************************* CorInfoHFAElemType MethodTable::GetHFAType() { CONTRACTL { WRAPPER(THROWS); // we end up in the class loader which has the conditional contracts WRAPPER(GC_TRIGGERS); } CONTRACTL_END; if (!IsHFA()) return CORINFO_HFA_ELEM_NONE; MethodTable * pMT = this; for (;;) { _ASSERTE(pMT->IsValueType()); _ASSERTE(pMT->GetNumInstanceFields() > 0); int vectorSize = pMT->GetVectorSize(); if (vectorSize != 0) { return (vectorSize == 8) ? CORINFO_HFA_ELEM_VECTOR64 : CORINFO_HFA_ELEM_VECTOR128; } PTR_FieldDesc pFirstField = pMT->GetApproxFieldDescListRaw(); CorElementType fieldType = pFirstField->GetFieldType(); // All HFA fields have to be of the same type, so we can just return the type of the first field switch (fieldType) { case ELEMENT_TYPE_VALUETYPE: pMT = pFirstField->LookupApproxFieldTypeHandle().GetMethodTable(); break; case ELEMENT_TYPE_R4: return CORINFO_HFA_ELEM_FLOAT; case ELEMENT_TYPE_R8: return CORINFO_HFA_ELEM_DOUBLE; default: // This should never happen. MethodTable::IsHFA() should be set only on types // that have a valid HFA type when the flag is used to track HFA status. _ASSERTE(false); return CORINFO_HFA_ELEM_NONE; } } } bool MethodTable::IsNativeHFA() { LIMITED_METHOD_CONTRACT; if (!HasLayout() || IsBlittable()) { return IsHFA(); } return GetNativeLayoutInfo()->IsNativeHFA(); } CorInfoHFAElemType MethodTable::GetNativeHFAType() { LIMITED_METHOD_CONTRACT; if (!HasLayout() || IsBlittable()) { return GetHFAType(); } return GetNativeLayoutInfo()->GetNativeHFAType(); } //--------------------------------------------------------------------------------------- // // When FEATURE_HFA is defined, we cache the value; otherwise we recompute it with each // call. The latter is only for the armaltjit and the arm64altjit. // bool #if defined(FEATURE_HFA) EEClass::CheckForHFA(MethodTable ** pByValueClassCache) #else EEClass::CheckForHFA() #endif { STANDARD_VM_CONTRACT; // This method should be called for valuetypes only _ASSERTE(GetMethodTable()->IsValueType()); // The opaque Vector types appear to have multiple fields, but need to be treated // as an opaque type of a single vector. if (GetMethodTable()->GetVectorSize() != 0) { #if defined(FEATURE_HFA) GetMethodTable()->SetIsHFA(); #endif return true; } CorInfoHFAElemType hfaType = CORINFO_HFA_ELEM_NONE; FieldDesc *pFieldDescList = GetFieldDescList(); bool hasZeroOffsetField = false; for (UINT i = 0; i < GetNumInstanceFields(); i++) { FieldDesc *pFD = &pFieldDescList[i]; hasZeroOffsetField |= (pFD->GetOffset() == 0); CorElementType fieldType = pFD->GetFieldType(); CorInfoHFAElemType fieldHFAType = CORINFO_HFA_ELEM_NONE; switch (fieldType) { case ELEMENT_TYPE_VALUETYPE: { #ifdef TARGET_ARM64 MethodTable* pMT; #if defined(FEATURE_HFA) pMT = pByValueClassCache[i]; #else pMT = pFD->LookupApproxFieldTypeHandle().AsMethodTable(); #endif int thisElemSize = pMT->GetVectorSize(); if (thisElemSize != 0) { fieldHFAType = (thisElemSize == 8) ? CORINFO_HFA_ELEM_VECTOR64 : CORINFO_HFA_ELEM_VECTOR128; } else #endif // TARGET_ARM64 { #if defined(FEATURE_HFA) fieldHFAType = pByValueClassCache[i]->GetHFAType(); #else fieldHFAType = pFD->LookupApproxFieldTypeHandle().AsMethodTable()->GetHFAType(); #endif } } break; case ELEMENT_TYPE_R4: { static const int REQUIRED_FLOAT_ALIGNMENT = 4; if (pFD->GetOffset() % REQUIRED_FLOAT_ALIGNMENT != 0) // HFAs don't have unaligned fields. { return false; } fieldHFAType = CORINFO_HFA_ELEM_FLOAT; } break; case ELEMENT_TYPE_R8: { static const int REQUIRED_DOUBLE_ALIGNMENT = 8; if (pFD->GetOffset() % REQUIRED_DOUBLE_ALIGNMENT != 0) // HFAs don't have unaligned fields. { return false; } fieldHFAType = CORINFO_HFA_ELEM_DOUBLE; } break; default: // Not HFA return false; } // Field type should be a valid HFA type. if (fieldHFAType == CORINFO_HFA_ELEM_NONE) { return false; } // Initialize with a valid HFA type. if (hfaType == CORINFO_HFA_ELEM_NONE) { hfaType = fieldHFAType; } // All field types should be equal. else if (fieldHFAType != hfaType) { return false; } } int elemSize = 0; switch (hfaType) { case CORINFO_HFA_ELEM_FLOAT: elemSize = 4; break; case CORINFO_HFA_ELEM_DOUBLE: case CORINFO_HFA_ELEM_VECTOR64: elemSize = 8; break; #ifdef TARGET_ARM64 case CORINFO_HFA_ELEM_VECTOR128: elemSize = 16; break; #endif default: // ELEMENT_TYPE_END return false; } if (!hasZeroOffsetField) // If the struct doesn't have a zero-offset field, it's not an HFA. return false; // Note that we check the total size, but do not perform any checks on number of fields: // - Type of fields can be HFA valuetype itself // - Managed C++ HFA valuetypes have just one of type float to signal that // the valuetype is HFA and explicitly specified size DWORD totalSize = GetMethodTable()->GetNumInstanceFieldBytes(); if (totalSize % elemSize != 0) return false; // On ARM, HFAs can have a maximum of four fields regardless of whether those are float or double. if (totalSize / elemSize > 4) return false; // All the above tests passed. It's HFA(/HVA)! #if defined(FEATURE_HFA) GetMethodTable()->SetIsHFA(); #endif return true; } #ifdef FEATURE_64BIT_ALIGNMENT // Returns true iff the native view of this type requires 64-bit alignment. bool MethodTable::NativeRequiresAlign8() { LIMITED_METHOD_CONTRACT; if (HasLayout() && !IsBlittable()) { return (GetNativeLayoutInfo()->GetLargestAlignmentRequirement() >= 8); } return RequiresAlign8(); } #endif // FEATURE_64BIT_ALIGNMENT #ifndef DACCESS_COMPILE #ifdef FEATURE_COMINTEROP //========================================================================================== TypeHandle MethodTable::GetCoClassForInterface() { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END EEClass * pClass = GetClass(); if (!pClass->IsComClassInterface()) return TypeHandle(); _ASSERTE(IsInterface()); TypeHandle th = pClass->GetCoClassForInterface(); if (!th.IsNull()) return th; return SetupCoClassForInterface(); } //******************************************************************************* TypeHandle MethodTable::SetupCoClassForInterface() { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(IsComClassInterface()); } CONTRACTL_END TypeHandle CoClassType; const BYTE *pVal = NULL; ULONG cbVal = 0; HRESULT hr = GetCustomAttribute(WellKnownAttribute::CoClass, (const void **)&pVal, &cbVal); if (hr == S_OK) { CustomAttributeParser cap(pVal, cbVal); IfFailThrow(cap.SkipProlog()); // Retrieve the COM source interface class name. ULONG cbName; LPCUTF8 szName; IfFailThrow(cap.GetNonNullString(&szName, &cbName)); // Copy the name to a temporary buffer and NULL terminate it. StackSString ss(SString::Utf8, szName, cbName); // Try to load the class using its name as a fully qualified name. If that fails, // then we try to load it in the assembly of the current class. CoClassType = TypeName::GetTypeReferencedByCustomAttribute(ss.GetUnicode(), GetAssembly()); // Cache the coclass type GetClass()->SetCoClassForInterface(CoClassType); } return CoClassType; } //******************************************************************************* void MethodTable::GetEventInterfaceInfo(MethodTable **ppSrcItfClass, MethodTable **ppEvProvClass) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END TypeHandle EventProvType; TypeHandle SrcItfType; const BYTE *pVal = NULL; ULONG cbVal = 0; // Retrieve the ComEventProviderAttribute CA. HRESULT hr = GetMDImport()->GetCustomAttributeByName(GetCl(), INTEROP_COMEVENTINTERFACE_TYPE, (const void**)&pVal, &cbVal); if (FAILED(hr)) { COMPlusThrowHR(hr); } CustomAttributeParser cap(pVal, cbVal); // Skip the CA type prefix. IfFailThrow(cap.SkipProlog()); // Retrieve the COM source interface class name. LPCUTF8 szName; ULONG cbName; IfFailThrow(cap.GetNonNullString(&szName, &cbName)); // Copy the name to a temporary buffer and NULL terminate it. StackSString ss(SString::Utf8, szName, cbName); // Try to load the class using its name as a fully qualified name. If that fails, // then we try to load it in the assembly of the current class. SrcItfType = TypeName::GetTypeReferencedByCustomAttribute(ss.GetUnicode(), GetAssembly()); // Retrieve the COM event provider class name. IfFailThrow(cap.GetNonNullString(&szName, &cbName)); // Copy the name to a temporary buffer and NULL terminate it. ss.SetUTF8(szName, cbName); // Try to load the class using its name as a fully qualified name. If that fails, // then we try to load it in the assembly of the current class. EventProvType = TypeName::GetTypeReferencedByCustomAttribute(ss.GetUnicode(), GetAssembly()); // Set the source interface and event provider classes. *ppSrcItfClass = SrcItfType.GetMethodTable(); *ppEvProvClass = EventProvType.GetMethodTable(); } //******************************************************************************* TypeHandle MethodTable::GetDefItfForComClassItf() { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END BAD_FORMAT_NOTHROW_ASSERT(GetClass()->IsComClassInterface()); // The COM class interface uses the normal scheme which is to have no // methods and to implement default interface and optionnally the // default source interface. In this scheme, the first implemented // interface is the default interface which we return. InterfaceMapIterator it = IterateInterfaceMap(); if (it.Next()) { // Can use GetInterfaceApprox, as there are no generic default interfaces return TypeHandle(it.GetInterfaceApprox()); } else { // The COM class interface has the methods directly on the itself. // Because of this we need to consider it to be the default interface. return TypeHandle(this); } } #endif // FEATURE_COMINTEROP #endif // !DACCESS_COMPILE //--------------------------------------------------------------------------------------- // // Get the metadata token of the outer type for a nested type // // Return Value: // The token of the outer class if this EEClass is nested, or mdTypeDefNil if the // EEClass is not a nested type // mdTypeDef MethodTable::GetEnclosingCl() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; mdTypeDef tdEnclosing = mdTypeDefNil; if (GetClass()->IsNested()) { HRESULT hr = GetMDImport()->GetNestedClassProps(GetCl(), &tdEnclosing); if (FAILED(hr)) { ThrowHR(hr, BFA_UNABLE_TO_GET_NESTED_PROPS); } } return tdEnclosing; } CorNativeLinkType MethodTable::GetCharSet() { IMDInternalImport* pInternalImport = GetModule()->GetMDImport(); DWORD clFlags; CorNativeLinkType charSet = nltAnsi; // Initialize to ANSI to make the compiler happy for the case that always asserts bool success = true; if (FAILED(pInternalImport->GetTypeDefProps(GetTypeDefRid(), &clFlags, NULL))) { success = false; } if (IsTdAnsiClass(clFlags)) { charSet = nltAnsi; } else if (IsTdUnicodeClass(clFlags)) { charSet = nltUnicode; } else if (IsTdAutoClass(clFlags)) { #ifdef TARGET_WINDOWS charSet = nltUnicode; #else charSet = nltAnsi; // We don't have a utf8 charset in metadata yet, but ANSI == UTF-8 off-Windows #endif } else { success = false; } _ASSERTE_MSG(success, "Charset metadata for this type should have already been verified at type-load time"); return charSet; } //******************************************************************************* // // Helper routines for the macros defined at the top of this class. // You probably should not use these functions directly. // template SString &MethodTable::_GetFullyQualifiedNameForClassNestedAwareInternal(SString &ssBuf) { CONTRACTL { THROWS; GC_NOTRIGGER; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END; ssBuf.Clear(); LPCUTF8 pszNamespace; LPCUTF8 pszName; pszName = GetFullyQualifiedNameInfo(&pszNamespace); if (pszName == NULL) { return ssBuf; } StackSString ssName(SString::Utf8, pszName); mdTypeDef mdEncl = GetCl(); IMDInternalImport *pImport = GetMDImport(); // Check if the type is nested DWORD dwAttr; IfFailThrow(pImport->GetTypeDefProps(GetCl(), &dwAttr, NULL)); RedirectFunctor redirectFunctor; if (IsTdNested(dwAttr)) { StackSString ssFullyQualifiedName; StackSString ssPath; // Build the nesting chain. while (SUCCEEDED(pImport->GetNestedClassProps(mdEncl, &mdEncl))) { LPCUTF8 szEnclName; LPCUTF8 szEnclNameSpace; IfFailThrow(pImport->GetNameOfTypeDef( mdEncl, &szEnclName, &szEnclNameSpace)); ns::MakePath(ssPath, StackSString(SString::Utf8, redirectFunctor(szEnclNameSpace)), StackSString(SString::Utf8, szEnclName)); ns::MakeNestedTypeName(ssFullyQualifiedName, ssPath, ssName); ssName = ssFullyQualifiedName; } } ns::MakePath( ssBuf, StackSString(SString::Utf8, redirectFunctor(pszNamespace)), ssName); return ssBuf; } class PassThrough { public : LPCUTF8 operator() (LPCUTF8 szEnclNamespace) { LIMITED_METHOD_CONTRACT; return szEnclNamespace; } }; SString &MethodTable::_GetFullyQualifiedNameForClassNestedAware(SString &ssBuf) { LIMITED_METHOD_CONTRACT; return _GetFullyQualifiedNameForClassNestedAwareInternal(ssBuf); } //******************************************************************************* SString &MethodTable::_GetFullyQualifiedNameForClass(SString &ssBuf) { CONTRACTL { THROWS; GC_NOTRIGGER; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END ssBuf.Clear(); if (IsArray()) { TypeDesc::ConstructName(GetInternalCorElementType(), GetArrayElementTypeHandle(), GetRank(), ssBuf); } else if (!IsNilToken(GetCl())) { LPCUTF8 szNamespace; LPCUTF8 szName; IfFailThrow(GetMDImport()->GetNameOfTypeDef(GetCl(), &szName, &szNamespace)); ns::MakePath(ssBuf, StackSString(SString::Utf8, szNamespace), StackSString(SString::Utf8, szName)); } return ssBuf; } //******************************************************************************* // // Gets the namespace and class name for the class. The namespace // can legitimately come back NULL, however a return value of NULL indicates // an error. // // NOTE: this used to return array class names, which were sometimes squirreled away by the // class loader hash table. It's been removed because it wasted space and was basically broken // in general (sometimes wasn't set, sometimes set wrong). If you need array class names, // use GetFullyQualifiedNameForClass instead. // LPCUTF8 MethodTable::GetFullyQualifiedNameInfo(LPCUTF8 *ppszNamespace) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; } CONTRACTL_END if (IsArray()) { *ppszNamespace = NULL; return NULL; } else { LPCUTF8 szName; if (FAILED(GetMDImport()->GetNameOfTypeDef(GetCl(), &szName, ppszNamespace))) { *ppszNamespace = NULL; return NULL; } return szName; } } #ifndef DACCESS_COMPILE #ifdef FEATURE_COMINTEROP //******************************************************************************* CorIfaceAttr MethodTable::GetComInterfaceType() { CONTRACTL { THROWS; GC_NOTRIGGER; FORBID_FAULT; } CONTRACTL_END // This should only be called on interfaces. BAD_FORMAT_NOTHROW_ASSERT(IsInterface()); // Check to see if we have already determined the COM interface type // of this interface. CorIfaceAttr ItfType = GetClass()->GetComInterfaceType(); if (ItfType != (CorIfaceAttr)-1) return ItfType; // Retrieve the interface type from the metadata. HRESULT hr = GetMDImport()->GetIfaceTypeOfTypeDef(GetCl(), (ULONG*)&ItfType); IfFailThrow(hr); if (hr != S_OK) { // if not found in metadata, use the default ItfType = ifDual; } if (ItfType == ifInspectable) { COMPlusThrow(kPlatformNotSupportedException, IDS_EE_NO_IINSPECTABLE); } // Cache the interface type GetClass()->SetComInterfaceType(ItfType); return ItfType; } #endif // FEATURE_COMINTEROP //******************************************************************************* void EEClass::GetBestFitMapping(MethodTable * pMT, BOOL *pfBestFitMapping, BOOL *pfThrowOnUnmappableChar) { CONTRACTL { THROWS; // OOM only GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; EEClass * pClass = pMT->GetClass(); // lazy init if (!(pClass->m_VMFlags & VMFLAG_BESTFITMAPPING_INITED)) { *pfBestFitMapping = FALSE; *pfThrowOnUnmappableChar = FALSE; ReadBestFitCustomAttribute(pMT->GetModule(), pMT->GetCl(), pfBestFitMapping, pfThrowOnUnmappableChar); DWORD flags = VMFLAG_BESTFITMAPPING_INITED; if (*pfBestFitMapping) flags |= VMFLAG_BESTFITMAPPING; if (*pfThrowOnUnmappableChar) flags |= VMFLAG_THROWONUNMAPPABLECHAR; InterlockedOr((LONG*)&pClass->m_VMFlags, flags); } else { *pfBestFitMapping = (pClass->m_VMFlags & VMFLAG_BESTFITMAPPING); *pfThrowOnUnmappableChar = (pClass->m_VMFlags & VMFLAG_THROWONUNMAPPABLECHAR); } } #ifdef _DEBUG //******************************************************************************* void MethodTable::DebugRecursivelyDumpInstanceFields(LPCUTF8 pszClassName, BOOL debug) { WRAPPER_NO_CONTRACT; // It's a dev helper, who cares about contracts EX_TRY { StackSString ssBuff; DWORD cParentInstanceFields; DWORD i; CONSISTENCY_CHECK(CheckLoadLevel(CLASS_LOAD_APPROXPARENTS)); MethodTable *pParentMT = GetParentMethodTable(); if (pParentMT != NULL) { cParentInstanceFields = pParentMT->GetClass()->GetNumInstanceFields(); DefineFullyQualifiedNameForClass(); LPCUTF8 name = GetFullyQualifiedNameForClass(pParentMT); pParentMT->DebugRecursivelyDumpInstanceFields(name, debug); } else { cParentInstanceFields = 0; } // Are there any new instance fields declared by this class? if (GetNumInstanceFields() > cParentInstanceFields) { // Display them if(debug) { ssBuff.Printf("%s:\n", pszClassName); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { LOG((LF_CLASSLOADER, LL_ALWAYS, "%s:\n", pszClassName)); } for (i = 0; i < (GetNumInstanceFields()-cParentInstanceFields); i++) { FieldDesc *pFD = &GetClass()->GetFieldDescList()[i]; #ifdef DEBUG_LAYOUT printf("offset %s%3d %s\n", pFD->IsByValue() ? "byvalue " : "", pFD->GetOffset(), pFD->GetName()); #endif if(debug) { ssBuff.Printf("offset %3d %s\n", pFD->GetOffset(), pFD->GetName()); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { LOG((LF_CLASSLOADER, LL_ALWAYS, "offset %3d %s\n", pFD->GetOffset(), pFD->GetName())); } } } } EX_CATCH { if(debug) { OutputDebugStringUtf8("\n"); } else { LOG((LF_CLASSLOADER, LL_ALWAYS, "\n")); } } EX_END_CATCH(SwallowAllExceptions); } //******************************************************************************* void MethodTable::DebugDumpFieldLayout(LPCUTF8 pszClassName, BOOL debug) { WRAPPER_NO_CONTRACT; // It's a dev helper, who cares about contracts if (GetNumStaticFields() == 0 && GetNumInstanceFields() == 0) return; EX_TRY { StackSString ssBuff; DWORD i; DWORD cParentInstanceFields; CONSISTENCY_CHECK(CheckLoadLevel(CLASS_LOAD_APPROXPARENTS)); if (GetParentMethodTable() != NULL) cParentInstanceFields = GetParentMethodTable()->GetNumInstanceFields(); else { cParentInstanceFields = 0; } if (debug) { ssBuff.Printf("Field layout for '%s':\n\n", pszClassName); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "Field layout for '%s':\n\n", pszClassName)); } if (GetNumStaticFields() > 0) { if (debug) { OutputDebugStringUtf8("Static fields (stored at vtable offsets)\n"); OutputDebugStringUtf8("----------------------------------------\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "Static fields (stored at vtable offsets)\n")); LOG((LF_ALWAYS, LL_ALWAYS, "----------------------------------------\n")); } for (i = 0; i < GetNumStaticFields(); i++) { FieldDesc *pFD = GetClass()->GetFieldDescList() + ((GetNumInstanceFields()-cParentInstanceFields) + i); if(debug) { ssBuff.Printf("offset %3d %s\n", pFD->GetOffset(), pFD->GetName()); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "offset %3d %s\n", pFD->GetOffset(), pFD->GetName())); } } } if (GetNumInstanceFields() > 0) { if (GetNumStaticFields()) { if(debug) { OutputDebugStringUtf8("\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "\n")); } } if (debug) { OutputDebugStringUtf8("Instance fields\n"); OutputDebugStringUtf8("---------------\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "Instance fields\n")); LOG((LF_ALWAYS, LL_ALWAYS, "---------------\n")); } DebugRecursivelyDumpInstanceFields(pszClassName, debug); } if (debug) { OutputDebugStringUtf8("\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "\n")); } } EX_CATCH { if (debug) { OutputDebugStringUtf8("\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "\n")); } } EX_END_CATCH(SwallowAllExceptions); } // MethodTable::DebugDumpFieldLayout //******************************************************************************* void MethodTable::DebugDumpGCDesc( LPCUTF8 pszClassName, BOOL fDebug) { WRAPPER_NO_CONTRACT; // It's a dev helper, who cares about contracts EX_TRY { StackSString ssBuff; if (fDebug) { ssBuff.Printf("GC description for '%s':\n\n", pszClassName); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "GC description for '%s':\n\n", pszClassName)); } if (ContainsGCPointers()) { CGCDescSeries *pSeries; CGCDescSeries *pHighest; if (fDebug) { OutputDebugStringUtf8("GCDesc:\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "GCDesc:\n")); } pSeries = CGCDesc::GetCGCDescFromMT(this)->GetLowestSeries(); pHighest = CGCDesc::GetCGCDescFromMT(this)->GetHighestSeries(); while (pSeries <= pHighest) { if (fDebug) { ssBuff.Printf(" offset %5d (%d w/o Object), size %5d (%5d w/o BaseSize subtr)\n", pSeries->GetSeriesOffset(), pSeries->GetSeriesOffset() - OBJECT_SIZE, pSeries->GetSeriesSize(), pSeries->GetSeriesSize() + GetBaseSize() ); OutputDebugStringUtf8(ssBuff.GetUTF8()); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, " offset %5d (%d w/o Object), size %5d (%5d w/o BaseSize subtr)\n", pSeries->GetSeriesOffset(), pSeries->GetSeriesOffset() - OBJECT_SIZE, pSeries->GetSeriesSize(), pSeries->GetSeriesSize() + GetBaseSize() )); } pSeries++; } if (fDebug) { OutputDebugStringUtf8("\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "\n")); } } } EX_CATCH { if (fDebug) { OutputDebugStringUtf8("\n"); } else { //LF_ALWAYS allowed here because this is controlled by special env var ShouldDumpOnClassLoad LOG((LF_ALWAYS, LL_ALWAYS, "\n")); } } EX_END_CATCH(SwallowAllExceptions); } // MethodTable::DebugDumpGCDesc #endif // _DEBUG #ifdef FEATURE_COMINTEROP //******************************************************************************* CorClassIfaceAttr MethodTable::GetComClassInterfaceType() { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(!IsInterface()); } CONTRACTL_END // If the type is an open generic type, then it is considered ClassInterfaceType.None. if (ContainsGenericVariables()) return clsIfNone; // Classes that either have generic instantiations (G) or derive from classes // with generic instantiations (D : B) are always considered ClassInterfaceType.None. if (HasGenericClassInstantiationInHierarchy()) return clsIfNone; // If the class does not support IClassX, // then it is considered ClassInterfaceType.None unless explicitly overridden by the CA if (!ClassSupportsIClassX(this)) return clsIfNone; return ReadClassInterfaceTypeCustomAttribute(TypeHandle(this)); } #endif // FEATURE_COMINTEROP //--------------------------------------------------------------------------------------- // Substitution MethodTable::GetSubstitutionForParent( const Substitution * pSubst) { CONTRACTL { THROWS; GC_NOTRIGGER; FORBID_FAULT; } CONTRACTL_END mdToken crExtends; DWORD dwAttrClass; if (IsArray()) { return Substitution(GetModule(), SigPointer(), pSubst); } IfFailThrow(GetMDImport()->GetTypeDefProps( GetCl(), &dwAttrClass, &crExtends)); return Substitution(crExtends, GetModule(), pSubst); } // MethodTable::GetSubstitutionForParent #endif //!DACCESS_COMPILE #ifndef DACCESS_COMPILE #ifdef FEATURE_COMINTEROP // // Implementations of SparseVTableMap methods. // //******************************************************************************* SparseVTableMap::SparseVTableMap() { LIMITED_METHOD_CONTRACT; // Note that this will also zero out all gaps. It is important for NGen determinism. ZeroMemory(this, sizeof(*this)); } //******************************************************************************* SparseVTableMap::~SparseVTableMap() { LIMITED_METHOD_CONTRACT; if (m_MapList != NULL) { delete [] m_MapList; m_MapList = NULL; } } //******************************************************************************* // Allocate or expand the mapping list for a new entry. void SparseVTableMap::AllocOrExpand() { STANDARD_VM_CONTRACT; if (m_MapEntries == m_Allocated) { Entry *maplist = new Entry[m_Allocated + MapGrow]; if (m_MapList != NULL) memcpy(maplist, m_MapList, m_MapEntries * sizeof(Entry)); m_Allocated += MapGrow; delete [] m_MapList; m_MapList = maplist; } } //******************************************************************************* // While building mapping list, record a gap in VTable slot numbers. void SparseVTableMap::RecordGap(WORD StartMTSlot, WORD NumSkipSlots) { STANDARD_VM_CONTRACT; _ASSERTE((StartMTSlot == 0) || (StartMTSlot > m_MTSlot)); _ASSERTE(NumSkipSlots > 0); // We use the information about the current gap to complete a map entry for // the last non-gap. There is a special case where the vtable begins with a // gap, so we don't have a non-gap to record. if (StartMTSlot == 0) { _ASSERTE((m_MTSlot == 0) && (m_VTSlot == 0)); m_VTSlot = NumSkipSlots; return; } // We need an entry, allocate or expand the list as necessary. AllocOrExpand(); // Update the list with an entry describing the last non-gap in vtable // entries. m_MapList[m_MapEntries].m_Start = m_MTSlot; m_MapList[m_MapEntries].m_Span = StartMTSlot - m_MTSlot; m_MapList[m_MapEntries].m_MapTo = m_VTSlot; m_VTSlot += (StartMTSlot - m_MTSlot) + NumSkipSlots; m_MTSlot = StartMTSlot; m_MapEntries++; } //******************************************************************************* // Finish creation of mapping list. void SparseVTableMap::FinalizeMapping(WORD TotalMTSlots) { STANDARD_VM_CONTRACT; _ASSERTE(TotalMTSlots >= m_MTSlot); // If mapping ended with a gap, we have nothing else to record. if (TotalMTSlots == m_MTSlot) return; // Allocate or expand the list as necessary. AllocOrExpand(); // Update the list with an entry describing the last non-gap in vtable // entries. m_MapList[m_MapEntries].m_Start = m_MTSlot; m_MapList[m_MapEntries].m_Span = TotalMTSlots - m_MTSlot; m_MapList[m_MapEntries].m_MapTo = m_VTSlot; // Update VT slot cursor, because we use it to determine total number of // vtable slots for GetNumVirtuals m_VTSlot += TotalMTSlots - m_MTSlot; m_MapEntries++; } //******************************************************************************* // Lookup a VTable slot number from a method table slot number. WORD SparseVTableMap::LookupVTSlot(WORD MTSlot) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; } CONTRACTL_END // As an optimization, check the last entry which yielded a correct result. if ((MTSlot >= m_MapList[m_LastUsed].m_Start) && (MTSlot < (m_MapList[m_LastUsed].m_Start + m_MapList[m_LastUsed].m_Span))) return (MTSlot - m_MapList[m_LastUsed].m_Start) + m_MapList[m_LastUsed].m_MapTo; // Check all MT slots spans to see which one our input slot lies in. for (WORD i = 0; i < m_MapEntries; i++) { if ((MTSlot >= m_MapList[i].m_Start) && (MTSlot < (m_MapList[i].m_Start + m_MapList[i].m_Span))) { m_LastUsed = i; return (MTSlot - m_MapList[i].m_Start) + m_MapList[i].m_MapTo; } } _ASSERTE(!"Invalid MethodTable slot"); return ~0; } //******************************************************************************* // Retrieve the number of slots in the vtable (both empty and full). WORD SparseVTableMap::GetNumVTableSlots() { LIMITED_METHOD_CONTRACT; return m_VTSlot; } #endif //FEATURE_COMINTEROP //******************************************************************************* void EEClass::AddChunk (MethodDescChunk* pNewChunk) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_FORBID_FAULT; _ASSERTE(pNewChunk->GetNextChunk() == NULL); MethodDescChunk* head = GetChunks(); if (head == NULL) { SetChunks(pNewChunk); } else { // Current chunk needs to be added to the end of the list so that // when reflection is iterating all methods, they would come in declared order while (head->GetNextChunk() != NULL) head = head->GetNextChunk(); head->SetNextChunk(pNewChunk); } } //******************************************************************************* void EEClass::AddChunkIfItHasNotBeenAdded (MethodDescChunk* pNewChunk) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_NOTRIGGER; STATIC_CONTRACT_FORBID_FAULT; // return if the chunk has been added if (pNewChunk->GetNextChunk() != NULL) return; // even if pNewChunk->GetNextChunk() is NULL, this may still be the first chunk we added // (last in the list) so find the end of the list and verify that MethodDescChunk *chunk = GetChunks(); if (chunk != NULL) { while (chunk->GetNextChunk() != NULL) chunk = chunk->GetNextChunk(); if (chunk == pNewChunk) return; } pNewChunk->SetNextChunk(GetChunks()); SetChunks(pNewChunk); } #endif // !DACCESS_COMPILE //******************************************************************************* // ApproxFieldDescIterator is used to iterate over fields in a given class. // It does not includes EnC fields, and not inherited fields. // ApproxFieldDescIterator is only used to iterate over static fields in one place, // and this will probably change anyway. After // we clean this up we should make ApproxFieldDescIterator work // over instance fields only ApproxFieldDescIterator::ApproxFieldDescIterator() { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; } CONTRACTL_END m_iteratorType = 0; m_pFieldDescList = NULL; m_currField = -1; m_totalFields = 0; } //******************************************************************************* void ApproxFieldDescIterator::Init(MethodTable *pMT, int iteratorType) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; SUPPORTS_DAC; } CONTRACTL_END m_iteratorType = iteratorType; m_pFieldDescList = pMT->GetApproxFieldDescListRaw(); m_currField = -1; // This gets non-EnC fields. m_totalFields = pMT->GetNumIntroducedInstanceFields(); if (!(iteratorType & (int)INSTANCE_FIELDS)) { // if not handling instances then skip them by setting curr to last one m_currField = m_totalFields - 1; } if (iteratorType & (int)STATIC_FIELDS) { m_totalFields += pMT->GetNumStaticFields(); } } //******************************************************************************* PTR_FieldDesc ApproxFieldDescIterator::Next() { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; SUPPORTS_DAC; } CONTRACTL_END // This will iterate through all non-inherited and non-EnC fields. ++m_currField; if (m_currField >= m_totalFields) { return NULL; } return m_pFieldDescList + m_currField; } //******************************************************************************* bool DeepFieldDescIterator::NextClass() { WRAPPER_NO_CONTRACT; if (m_curClass <= 0) { return false; } if (m_numClasses <= 0) { _ASSERTE(m_numClasses > 0); return false; } MethodTable * pMT; // // If we're in the cache just grab the cache entry. // // If we're deeper in the hierarchy than the // portion we cached we need to take the // deepest cache entry and search down manually. // if (--m_curClass < m_numClasses) { pMT = m_classes[m_curClass]; } else { pMT = m_classes[m_numClasses - 1]; int depthDiff = m_curClass - m_numClasses + 1; while (depthDiff--) { pMT = pMT->GetParentMethodTable(); } } m_fieldIter.Init(pMT, m_fieldIter.GetIteratorType()); return true; } //******************************************************************************* void DeepFieldDescIterator::Init(MethodTable* pMT, int iteratorType, bool includeParents) { WRAPPER_NO_CONTRACT; MethodTable * lastClass = NULL; int numClasses; // // Walk up the parent chain, collecting // parent pointers and counting fields. // numClasses = 0; m_numClasses = 0; m_deepTotalFields = 0; m_lastNextFromParentClass = false; while (pMT) { if (m_numClasses < (int)ARRAY_SIZE(m_classes)) { m_classes[m_numClasses++] = pMT; } if ((iteratorType & ApproxFieldDescIterator::INSTANCE_FIELDS) != 0) { m_deepTotalFields += pMT->GetNumIntroducedInstanceFields(); } if ((iteratorType & ApproxFieldDescIterator::STATIC_FIELDS) != 0) { m_deepTotalFields += pMT->GetNumStaticFields(); } numClasses++; lastClass = pMT; if (includeParents) { pMT = pMT->GetParentMethodTable(); } else { break; } } // Start the per-class field iterator on the base-most parent. if (numClasses) { m_curClass = numClasses - 1; m_fieldIter.Init(lastClass, iteratorType); } else { m_curClass = 0; } } //******************************************************************************* FieldDesc* DeepFieldDescIterator::Next() { WRAPPER_NO_CONTRACT; FieldDesc* field; do { m_lastNextFromParentClass = m_curClass > 0; field = m_fieldIter.Next(); if (!field && !NextClass()) { return NULL; } } while (!field); return field; } //******************************************************************************* bool DeepFieldDescIterator::Skip(int numSkip) { WRAPPER_NO_CONTRACT; while (numSkip >= m_fieldIter.CountRemaining()) { numSkip -= m_fieldIter.CountRemaining(); if (!NextClass()) { return false; } } while (numSkip--) { m_fieldIter.Next(); } return true; } #ifdef DACCESS_COMPILE //******************************************************************************* void EEClass::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, MethodTable * pMT) { SUPPORTS_DAC; DAC_ENUM_DTHIS(); EMEM_OUT(("MEM: %p EEClass\n", dac_cast(this))); if (HasOptionalFields()) DacEnumMemoryRegion(dac_cast(GetOptionalFields()), sizeof(EEClassOptionalFields)); if (flags != CLRDATA_ENUM_MEM_MINI && flags != CLRDATA_ENUM_MEM_TRIAGE && flags != CLRDATA_ENUM_MEM_HEAP2) { PTR_Module pModule = pMT->GetModule(); if (pModule.IsValid()) { pModule->EnumMemoryRegions(flags, true); } PTR_MethodDescChunk chunk = GetChunks(); while (chunk.IsValid()) { chunk->EnumMemoryRegions(flags); chunk = chunk->GetNextChunk(); } } PTR_FieldDesc pFieldDescList = GetFieldDescList(); if (pFieldDescList.IsValid()) { // add one to make sos's code happy. DacEnumMemoryRegion(dac_cast(pFieldDescList), (pMT->GetNumIntroducedInstanceFields() + GetNumStaticFields() + 1) * sizeof(FieldDesc)); } } #endif // DACCESS_COMPILE