// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. // // File: clsload.cpp // // ============================================================================ #include "common.h" #include "winwrap.h" #include "ceeload.h" #include "siginfo.hpp" #include "vars.hpp" #include "clsload.hpp" #include "classhash.inl" #include "class.h" #include "method.hpp" #include "ecall.h" #include "stublink.h" #include "object.h" #include "excep.h" #include "threads.h" #include "comsynchronizable.h" #include "threads.h" #include "dllimport.h" #include "dbginterface.h" #include "log.h" #include "eeconfig.h" #include "fieldmarshaler.h" #include "jitinterface.h" #include "vars.hpp" #include "assembly.hpp" #include "perfcounters.h" #include "eeprofinterfaces.h" #include "eehash.h" #include "typehash.h" #include "comdelegate.h" #include "array.h" #include "stackprobe.h" #include "posterror.h" #include "wrappers.h" #include "generics.h" #include "typestring.h" #include "typedesc.h" #include "cgencpu.h" #include "eventtrace.h" #include "typekey.h" #include "pendingload.h" #include "proftoeeinterfaceimpl.h" #include "mdaassistants.h" #include "virtualcallstub.h" #include "stringarraylist.h" // This method determines the "loader module" for an instantiated type // or method. The rule must ensure that any types involved in the // instantiated type or method do not outlive the loader module itself // with respect to app-domain unloading (e.g. MyList can't be // put in the module of MyList if MyList's assembly is // app-domain-neutral but MyType's assembly is app-domain-specific). // The rule we use is: // // * Pick the first type in the class instantiation, followed by // method instantiation, whose loader module is non-shared (app-domain-bound) // * If no type is app-domain-bound, return the module containing the generic type itself // // Some useful effects of this rule (for ngen purposes) are: // // * G lives in the module defining G // * non-mscorlib instantiations of mscorlib-defined generic types live in the module // of the instantiation (when only one module is invloved in the instantiation) // /* static */ PTR_Module ClassLoader::ComputeLoaderModuleWorker( Module * pDefinitionModule, // the module that declares the generic type or method mdToken token, // method or class token for this item Instantiation classInst, // the type arguments to the type (if any) Instantiation methodInst) // the type arguments to the method (if any) { CONTRACT(Module*) { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; MODE_ANY; PRECONDITION(CheckPointer(pDefinitionModule, NULL_OK)); POSTCONDITION(CheckPointer(RETVAL)); SO_INTOLERANT; SUPPORTS_DAC; } CONTRACT_END if (classInst.IsEmpty() && methodInst.IsEmpty()) RETURN PTR_Module(pDefinitionModule); #ifndef DACCESS_COMPILE #ifdef FEATURE_NATIVE_IMAGE_GENERATION // // Use special loader module placement during compilation of fragile native images. // // ComputeLoaderModuleForCompilation algorithm assumes that we are using fragile native image // for CoreLib (or compiling CoreLib itself). It is not the case for ReadyToRun compilation because // CoreLib as always treated as IL there (see code:PEFile::ShouldTreatNIAsMSIL for details). // if (IsCompilationProcess() && !IsReadyToRunCompilation()) { RETURN(ComputeLoaderModuleForCompilation(pDefinitionModule, token, classInst, methodInst)); } #endif // FEATURE_PREJIT #endif // #ifndef DACCESS_COMPILE Module *pFirstNonSharedLoaderModule = NULL; Module *pFirstNonSystemSharedModule = NULL; Module *pLoaderModule = NULL; if (pDefinitionModule) { if (pDefinitionModule->IsCollectible()) goto ComputeCollectibleLoaderModule; if (!pDefinitionModule->GetAssembly()->IsDomainNeutral()) { pFirstNonSharedLoaderModule = pDefinitionModule; } else if (!pDefinitionModule->IsSystem()) { pFirstNonSystemSharedModule = pDefinitionModule; } } for (DWORD i = 0; i < classInst.GetNumArgs(); i++) { TypeHandle classArg = classInst[i]; _ASSERTE(!classArg.IsEncodedFixup()); Module* pModule = classArg.GetLoaderModule(); if (pModule->IsCollectible()) goto ComputeCollectibleLoaderModule; if (!pModule->GetAssembly()->IsDomainNeutral()) { if (pFirstNonSharedLoaderModule == NULL) pFirstNonSharedLoaderModule = pModule; } else if (!pModule->IsSystem()) { if (pFirstNonSystemSharedModule == NULL) pFirstNonSystemSharedModule = pModule; } } for (DWORD i = 0; i < methodInst.GetNumArgs(); i++) { TypeHandle methodArg = methodInst[i]; _ASSERTE(!methodArg.IsEncodedFixup()); Module *pModule = methodArg.GetLoaderModule(); if (pModule->IsCollectible()) goto ComputeCollectibleLoaderModule; if (!pModule->GetAssembly()->IsDomainNeutral()) { if (pFirstNonSharedLoaderModule == NULL) pFirstNonSharedLoaderModule = pModule; } else if (!pModule->IsSystem()) { if (pFirstNonSystemSharedModule == NULL) pFirstNonSystemSharedModule = pModule; } } // RULE: Prefer modules in non-shared assemblies. // This ensures safety of app-domain unloading. if (pFirstNonSharedLoaderModule != NULL) { pLoaderModule = pFirstNonSharedLoaderModule; } else if (pFirstNonSystemSharedModule != NULL) { #ifdef FEATURE_FULL_NGEN // pFirstNonSystemSharedModule may be module of speculative generic instantiation. // If we are domain neutral, we have to use constituent of the instantiation to store // statics. We need to ensure that we can create DomainModule in all domains // that this instantiations may get activated in. PZM is good approximation of such constituent. pLoaderModule = Module::ComputePreferredZapModule(pDefinitionModule, classInst, methodInst); #else // Use pFirstNonSystemSharedModule just so C ends up in module C - it // shouldn't actually matter at all though. pLoaderModule = pFirstNonSystemSharedModule; #endif } else { CONSISTENCY_CHECK(MscorlibBinder::GetModule() && MscorlibBinder::GetModule()->IsSystem()); pLoaderModule = MscorlibBinder::GetModule(); } if (FALSE) { ComputeCollectibleLoaderModule: LoaderAllocator *pLoaderAllocatorOfDefiningType = NULL; LoaderAllocator *pOldestLoaderAllocator = NULL; Module *pOldestLoaderModule = NULL; UINT64 oldestFoundAge = 0; DWORD classArgsCount = classInst.GetNumArgs(); DWORD totalArgsCount = classArgsCount + methodInst.GetNumArgs(); if (pDefinitionModule != NULL) pLoaderAllocatorOfDefiningType = pDefinitionModule->GetLoaderAllocator(); for (DWORD i = 0; i < totalArgsCount; i++) { TypeHandle arg; if (i < classArgsCount) arg = classInst[i]; else arg = methodInst[i - classArgsCount]; Module *pModuleCheck = arg.GetLoaderModule(); LoaderAllocator *pLoaderAllocatorCheck = pModuleCheck->GetLoaderAllocator(); if (pLoaderAllocatorCheck != pLoaderAllocatorOfDefiningType && pLoaderAllocatorCheck->IsCollectible() && pLoaderAllocatorCheck->GetCreationNumber() > oldestFoundAge) { pOldestLoaderModule = pModuleCheck; pOldestLoaderAllocator = pLoaderAllocatorCheck; oldestFoundAge = pLoaderAllocatorCheck->GetCreationNumber(); } } // Only if we didn't find a different loader allocator than the defining loader allocator do we // use the defining loader allocator if (pOldestLoaderModule != NULL) pLoaderModule = pOldestLoaderModule; else pLoaderModule = pDefinitionModule; } RETURN PTR_Module(pLoaderModule); } #ifndef DACCESS_COMPILE #ifdef FEATURE_NATIVE_IMAGE_GENERATION /* static */ PTR_Module ClassLoader::ComputeLoaderModuleForCompilation( Module * pDefinitionModule, // the module that declares the generic type or method mdToken token, // method or class token for this item Instantiation classInst, // the type arguments to the type (if any) Instantiation methodInst) // the type arguments to the method (if any) { CONTRACT(Module*) { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; MODE_ANY; PRECONDITION(CheckPointer(pDefinitionModule, NULL_OK)); POSTCONDITION(CheckPointer(RETVAL)); SO_INTOLERANT; } CONTRACT_END // The NGEN rule for compiling constructed types and instantiated methods // into modules other than their "natural" LoaderModule. This is at the heart of // "full generics NGEN". // // If this instantiation doesn't have a unique home then use the ngen module // OK, we're certainly NGEN'ing. And if we're NGEN'ing then we're not on the debugger thread. CONSISTENCY_CHECK(((GetThread() && GetAppDomain()) || IsGCThread()) && "unexpected: running a load on debug thread but IsCompilationProcess() returned TRUE"); // Save it into its PreferredZapModule if it's always going to be saved there. // This is a stable choice - no need to record it in the table (as we do for others below) if (Module::IsAlwaysSavedInPreferredZapModule(classInst, methodInst)) { RETURN (Module::ComputePreferredZapModule(pDefinitionModule, classInst, methodInst)); } // Check if this compilation process has already decided on an adjustment. Once we decide // on the LoaderModule for an item it must be stable for the duration of a // compilation process, no matter how many modules get NGEN'd. ZapperLoaderModuleTableKey key(pDefinitionModule, token, classInst, methodInst); Module * pZapperLoaderModule = g_pCEECompileInfo->LookupZapperLoaderModule(&key); if (pZapperLoaderModule != NULL) { RETURN (pZapperLoaderModule); } // OK, we need to compute a non-standard zapping module. Module * pPreferredZapModule = Module::ComputePreferredZapModule(pDefinitionModule, classInst, methodInst); // Check if we're NGEN'ing but where perhaps the compilation domain // isn't set up yet. This can happen in following situations: // - Managed code running during startup before compilation domain is setup. // - Exceptions (e.g. invalid program exceptions) thrown from compilation domain and caught in default domain // We're a little stuck - we can't force the item into an NGEN image at this point. So just bail out // and use the loader module we've computed without recording the choice. The loader module should always // be mscorlib in this case. AppDomain * pAppDomain = GetAppDomain(); if (!pAppDomain->IsCompilationDomain() || !pAppDomain->ToCompilationDomain()->GetTargetModule()) { _ASSERTE(pPreferredZapModule->IsSystem() || IsNgenPDBCompilationProcess()); RETURN (pPreferredZapModule); } Module * pTargetModule = pAppDomain->ToCompilationDomain()->GetTargetModule(); // If it is multi-module assembly and we have not saved PZM yet, do not create // speculative instantiation - just save it in PZM. if (pTargetModule->GetAssembly() == pPreferredZapModule->GetAssembly() && !pPreferredZapModule->IsModuleSaved()) { pZapperLoaderModule = pPreferredZapModule; } else { // Everything else can be saved into the current module. pZapperLoaderModule = pTargetModule; } // If generating WinMD resilient code and we so far choose to use the target module, // we need to check if the definition module or any of the instantiation type can // cause version resilient problems. if (g_fNGenWinMDResilient && pZapperLoaderModule == pTargetModule) { if (pDefinitionModule != NULL && !pDefinitionModule->IsInCurrentVersionBubble()) { pZapperLoaderModule = pDefinitionModule; goto ModuleAdjustedForVersionResiliency; } for (DWORD i = 0; i < classInst.GetNumArgs(); i++) { Module * pModule = classInst[i].GetLoaderModule(); if (!pModule->IsInCurrentVersionBubble()) { pZapperLoaderModule = pModule; goto ModuleAdjustedForVersionResiliency; } } for (DWORD i = 0; i < methodInst.GetNumArgs(); i++) { Module * pModule = methodInst[i].GetLoaderModule(); if (!pModule->IsInCurrentVersionBubble()) { pZapperLoaderModule = pModule; goto ModuleAdjustedForVersionResiliency; } } ModuleAdjustedForVersionResiliency: ; } // Record this choice just in case we're NGEN'ing multiple modules // to make sure we always do the same thing if we're asked to compute // the loader module again. // Note this whole code path only happens while NGEN'ing, so this violation // is not so bad. It is needed since we allocate stuff on the heap. CONTRACT_VIOLATION(ThrowsViolation|FaultViolation); // Copy the instantiation arrays so they can escape the scope of this method. // Since this is a permanent entry in a table for this compilation process // we do not need to collect these. If we did have to we would do it when we deleteed the // ZapperLoaderModuleTable. NewArrayHolder pClassArgs = NULL; if (!classInst.IsEmpty()) { pClassArgs = new TypeHandle[classInst.GetNumArgs()]; for (unsigned int i = 0; i < classInst.GetNumArgs(); i++) pClassArgs[i] = classInst[i]; } NewArrayHolder pMethodArgs = NULL; if (!methodInst.IsEmpty()) { pMethodArgs = new TypeHandle[methodInst.GetNumArgs()]; for (unsigned int i = 0; i < methodInst.GetNumArgs(); i++) pMethodArgs[i] = methodInst[i]; } ZapperLoaderModuleTableKey key2(pDefinitionModule, token, Instantiation(pClassArgs, classInst.GetNumArgs()), Instantiation(pMethodArgs, methodInst.GetNumArgs())); g_pCEECompileInfo->RecordZapperLoaderModule(&key2, pZapperLoaderModule); pClassArgs.SuppressRelease(); pMethodArgs.SuppressRelease(); RETURN (pZapperLoaderModule); } #endif // FEATURE_NATIVE_IMAGE_GENERATION #endif // #ifndef DACCESS_COMPILE /*static*/ Module * ClassLoader::ComputeLoaderModule(MethodTable * pMT, mdToken token, Instantiation methodInst) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; return ComputeLoaderModuleWorker(pMT->GetModule(), token, pMT->GetInstantiation(), methodInst); } /*static*/ Module *ClassLoader::ComputeLoaderModule(TypeKey *typeKey) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; if (typeKey->GetKind() == ELEMENT_TYPE_CLASS) return ComputeLoaderModuleWorker(typeKey->GetModule(), typeKey->GetTypeToken(), typeKey->GetInstantiation(), Instantiation()); else if (typeKey->GetKind() == ELEMENT_TYPE_FNPTR) return ComputeLoaderModuleForFunctionPointer(typeKey->GetRetAndArgTypes(), typeKey->GetNumArgs() + 1); else return ComputeLoaderModuleForParamType(typeKey->GetElementType()); } /*static*/ BOOL ClassLoader::IsTypicalInstantiation(Module *pModule, mdToken token, Instantiation inst) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; PRECONDITION(CheckPointer(pModule)); PRECONDITION(TypeFromToken(token) == mdtTypeDef || TypeFromToken(token) == mdtMethodDef); SUPPORTS_DAC; } CONTRACTL_END for (DWORD i = 0; i < inst.GetNumArgs(); i++) { TypeHandle thArg = inst[i]; if (thArg.IsGenericVariable()) { TypeVarTypeDesc* tyvar = thArg.AsGenericVariable(); PREFIX_ASSUME(tyvar!=NULL); if ((tyvar->GetTypeOrMethodDef() != token) || (tyvar->GetModule() != dac_cast(pModule)) || (tyvar->GetIndex() != i)) return FALSE; } else { return FALSE; } } return TRUE; } // External class loader entry point: load a type by name /*static*/ TypeHandle ClassLoader::LoadTypeByNameThrowing(Assembly *pAssembly, LPCUTF8 nameSpace, LPCUTF8 name, NotFoundAction fNotFound, ClassLoader::LoadTypesFlag fLoadTypes, ClassLoadLevel level) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } PRECONDITION(CheckPointer(pAssembly)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); POSTCONDITION(CheckPointer(RETVAL, (fNotFound == ThrowIfNotFound && fLoadTypes == LoadTypes )? NULL_NOT_OK : NULL_OK)); POSTCONDITION(RETVAL.IsNull() || RETVAL.CheckLoadLevel(level)); SUPPORTS_DAC; #ifdef DACCESS_COMPILE PRECONDITION((fNotFound == ClassLoader::ReturnNullIfNotFound) && (fLoadTypes == DontLoadTypes)); #endif } CONTRACT_END NameHandle nameHandle(nameSpace, name); if (fLoadTypes == DontLoadTypes) nameHandle.SetTokenNotToLoad(tdAllTypes); if (fNotFound == ThrowIfNotFound) RETURN pAssembly->GetLoader()->LoadTypeHandleThrowIfFailed(&nameHandle, level); else RETURN pAssembly->GetLoader()->LoadTypeHandleThrowing(&nameHandle, level); } #ifndef DACCESS_COMPILE #define DAC_LOADS_TYPE(level, expression) \ if (FORBIDGC_LOADER_USE_ENABLED() || (expression)) \ { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } #else #define DAC_LOADS_TYPE(level, expression) { LOADS_TYPE(CLASS_LOAD_BEGIN); } #endif // #ifndef DACCESS_COMPILE // // Find a class given name, using the classloader's global list of known classes. // If the type is found, it will be restored unless pName->GetTokenNotToLoad() prohibits that // Returns NULL if class not found AND pName->OKToLoad returns false TypeHandle ClassLoader::LoadTypeHandleThrowIfFailed(NameHandle* pName, ClassLoadLevel level, Module* pLookInThisModuleOnly/*=NULL*/) { CONTRACT(TypeHandle) { INSTANCE_CHECK; if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } DAC_LOADS_TYPE(level, !pName->OKToLoad()); MODE_ANY; PRECONDITION(CheckPointer(pName)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); POSTCONDITION(CheckPointer(RETVAL, pName->OKToLoad() ? NULL_NOT_OK : NULL_OK)); POSTCONDITION(RETVAL.IsNull() || RETVAL.CheckLoadLevel(level)); SUPPORTS_DAC; } CONTRACT_END; // Lookup in the classes that this class loader knows about TypeHandle typeHnd = LoadTypeHandleThrowing(pName, level, pLookInThisModuleOnly); if(typeHnd.IsNull()) { if ( pName->OKToLoad() ) { #ifdef _DEBUG_IMPL { LPCUTF8 szName = pName->GetName(); if (szName == NULL) szName = ""; StackSString codeBase; GetAssembly()->GetCodeBase(codeBase); LOG((LF_CLASSLOADER, LL_INFO10, "Failed to find class \"%s\" in the manifest for assembly \"%ws\"\n", szName, (LPCWSTR)codeBase)); } #endif #ifndef DACCESS_COMPILE COUNTER_ONLY(GetPerfCounters().m_Loading.cLoadFailures++); m_pAssembly->ThrowTypeLoadException(pName, IDS_CLASSLOAD_GENERAL); #else DacNotImpl(); #endif } } RETURN(typeHnd); } #ifndef DACCESS_COMPILE //@TODO: Need to allow exceptions to be thrown when classloader is cleaned up EEClassHashEntry_t* ClassLoader::InsertValue(EEClassHashTable *pClassHash, EEClassHashTable *pClassCaseInsHash, LPCUTF8 pszNamespace, LPCUTF8 pszClassName, HashDatum Data, EEClassHashEntry_t *pEncloser, AllocMemTracker *pamTracker) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_NOTRIGGER; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END LPUTF8 pszLowerCaseNS = NULL; LPUTF8 pszLowerCaseName = NULL; EEClassHashEntry_t *pCaseInsEntry = NULL; EEClassHashEntry_t *pEntry = pClassHash->AllocNewEntry(pamTracker); if (pClassCaseInsHash) { CreateCanonicallyCasedKey(pszNamespace, pszClassName, &pszLowerCaseNS, &pszLowerCaseName); pCaseInsEntry = pClassCaseInsHash->AllocNewEntry(pamTracker); } { // ! We cannot fail after this point. CANNOTTHROWCOMPLUSEXCEPTION(); FAULT_FORBID(); pClassHash->InsertValueUsingPreallocatedEntry(pEntry, pszNamespace, pszClassName, Data, pEncloser); //If we're keeping a table for case-insensitive lookup, keep that up to date if (pClassCaseInsHash) pClassCaseInsHash->InsertValueUsingPreallocatedEntry(pCaseInsEntry, pszLowerCaseNS, pszLowerCaseName, pEntry, pEncloser); return pEntry; } } #endif // #ifndef DACCESS_COMPILE BOOL ClassLoader::CompareNestedEntryWithExportedType(IMDInternalImport * pImport, mdExportedType mdCurrent, EEClassHashTable * pClassHash, PTR_EEClassHashEntry pEntry) { CONTRACTL { INSTANCE_CHECK; NOTHROW; GC_NOTRIGGER; MODE_ANY; FORBID_FAULT; SUPPORTS_DAC; } CONTRACTL_END; LPCUTF8 Key[2]; do { if (FAILED(pImport->GetExportedTypeProps( mdCurrent, &Key[0], &Key[1], &mdCurrent, NULL, //binding (type def) NULL))) //flags { return FALSE; } if (pClassHash->CompareKeys(pEntry, Key)) { // Reached top level class for mdCurrent - return whether // or not pEntry is a top level class // (pEntry is a top level class if its pEncloser is NULL) if ((TypeFromToken(mdCurrent) != mdtExportedType) || (mdCurrent == mdExportedTypeNil)) { return pEntry->GetEncloser() == NULL; } } else // Keys don't match - wrong entry { return FALSE; } } while ((pEntry = pEntry->GetEncloser()) != NULL); // Reached the top level class for pEntry, but mdCurrent is nested return FALSE; } BOOL ClassLoader::CompareNestedEntryWithTypeDef(IMDInternalImport * pImport, mdTypeDef mdCurrent, EEClassHashTable * pClassHash, PTR_EEClassHashEntry pEntry) { CONTRACTL { INSTANCE_CHECK; NOTHROW; GC_NOTRIGGER; MODE_ANY; FORBID_FAULT; SUPPORTS_DAC; } CONTRACTL_END; LPCUTF8 Key[2]; do { if (FAILED(pImport->GetNameOfTypeDef(mdCurrent, &Key[1], &Key[0]))) { return FALSE; } if (pClassHash->CompareKeys(pEntry, Key)) { // Reached top level class for mdCurrent - return whether // or not pEntry is a top level class // (pEntry is a top level class if its pEncloser is NULL) if (FAILED(pImport->GetNestedClassProps(mdCurrent, &mdCurrent))) return pEntry->GetEncloser() == NULL; } else // Keys don't match - wrong entry return FALSE; } while ((pEntry = pEntry->GetEncloser()) != NULL); // Reached the top level class for pEntry, but mdCurrent is nested return FALSE; } BOOL ClassLoader::CompareNestedEntryWithTypeRef(IMDInternalImport * pImport, mdTypeRef mdCurrent, EEClassHashTable * pClassHash, PTR_EEClassHashEntry pEntry) { CONTRACTL { INSTANCE_CHECK; NOTHROW; GC_NOTRIGGER; MODE_ANY; FORBID_FAULT; SUPPORTS_DAC; } CONTRACTL_END; LPCUTF8 Key[2]; do { if (FAILED(pImport->GetNameOfTypeRef(mdCurrent, &Key[0], &Key[1]))) { return FALSE; } if (pClassHash->CompareKeys(pEntry, Key)) { if (FAILED(pImport->GetResolutionScopeOfTypeRef(mdCurrent, &mdCurrent))) { return FALSE; } // Reached top level class for mdCurrent - return whether // or not pEntry is a top level class // (pEntry is a top level class if its pEncloser is NULL) if ((TypeFromToken(mdCurrent) != mdtTypeRef) || (mdCurrent == mdTypeRefNil)) return pEntry->GetEncloser() == NULL; } else // Keys don't match - wrong entry return FALSE; } while ((pEntry = pEntry->GetEncloser())!=NULL); // Reached the top level class for pEntry, but mdCurrent is nested return FALSE; } /*static*/ BOOL ClassLoader::IsNested(Module *pModule, mdToken token, mdToken *mdEncloser) { CONTRACTL { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; switch(TypeFromToken(token)) { case mdtTypeDef: return (SUCCEEDED(pModule->GetMDImport()->GetNestedClassProps(token, mdEncloser))); case mdtTypeRef: IfFailThrow(pModule->GetMDImport()->GetResolutionScopeOfTypeRef(token, mdEncloser)); return ((TypeFromToken(*mdEncloser) == mdtTypeRef) && (*mdEncloser != mdTypeRefNil)); case mdtExportedType: IfFailThrow(pModule->GetAssembly()->GetManifestImport()->GetExportedTypeProps( token, NULL, // namespace NULL, // name mdEncloser, NULL, //binding (type def) NULL)); //flags return ((TypeFromToken(*mdEncloser) == mdtExportedType) && (*mdEncloser != mdExportedTypeNil)); default: ThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN_TYPE); } } BOOL ClassLoader::IsNested(NameHandle* pName, mdToken *mdEncloser) { CONTRACTL { INSTANCE_CHECK; if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; if (pName->GetTypeModule()) { if (TypeFromToken(pName->GetTypeToken()) == mdtBaseType) { if (!pName->GetBucket().IsNull()) return TRUE; return FALSE; } else return IsNested(pName->GetTypeModule(), pName->GetTypeToken(), mdEncloser); } else return FALSE; } void ClassLoader::GetClassValue(NameHandleTable nhTable, NameHandle *pName, HashDatum *pData, EEClassHashTable **ppTable, Module* pLookInThisModuleOnly, HashedTypeEntry* pFoundEntry, Loader::LoadFlag loadFlag, BOOL& needsToBuildHashtable) { CONTRACTL { INSTANCE_CHECK; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } PRECONDITION(CheckPointer(pName)); SUPPORTS_DAC; } CONTRACTL_END mdToken mdEncloser; EEClassHashEntry_t *pBucket = NULL; needsToBuildHashtable = FALSE; #if _DEBUG if (pName->GetName()) { if (pName->GetNameSpace() == NULL) LOG((LF_CLASSLOADER, LL_INFO1000, "Looking up %s by name.\n", pName->GetName())); else LOG((LF_CLASSLOADER, LL_INFO1000, "Looking up %s.%s by name.\n", pName->GetNameSpace(), pName->GetName())); } #endif BOOL isNested = IsNested(pName, &mdEncloser); PTR_Assembly assembly = GetAssembly(); PREFIX_ASSUME(assembly != NULL); ModuleIterator i = assembly->IterateModules(); while (i.Next()) { Module * pCurrentClsModule = i.GetModule(); PREFIX_ASSUME(pCurrentClsModule != NULL); if (pCurrentClsModule->IsResource()) continue; if (pLookInThisModuleOnly && (pCurrentClsModule != pLookInThisModuleOnly)) continue; #ifdef FEATURE_READYTORUN if (nhTable == nhCaseSensitive && pCurrentClsModule->IsReadyToRun() && pCurrentClsModule->GetReadyToRunInfo()->HasHashtableOfTypes()) { // For R2R modules, we only search the hashtable of token types stored in the module's image, and don't fallback // to searching m_pAvailableClasses or m_pAvailableClassesCaseIns (in fact, we don't even allocate them for R2R modules). // Also note that type lookups in R2R modules only support case sensitive lookups. mdToken mdFoundTypeToken; if (pCurrentClsModule->GetReadyToRunInfo()->TryLookupTypeTokenFromName(pName, &mdFoundTypeToken)) { if (TypeFromToken(mdFoundTypeToken) == mdtExportedType) { mdToken mdUnused; Module * pTargetModule = GetAssembly()->FindModuleByExportedType(mdFoundTypeToken, loadFlag, mdTypeDefNil, &mdUnused); pFoundEntry->SetTokenBasedEntryValue(mdFoundTypeToken, pTargetModule); } else { pFoundEntry->SetTokenBasedEntryValue(mdFoundTypeToken, pCurrentClsModule); } return; // Return on the first success } } else #endif { EEClassHashTable* pTable = NULL; if (nhTable == nhCaseSensitive) { *ppTable = pTable = pCurrentClsModule->GetAvailableClassHash(); #ifdef FEATURE_READYTORUN if (pTable == NULL && pCurrentClsModule->IsReadyToRun() && !pCurrentClsModule->GetReadyToRunInfo()->HasHashtableOfTypes()) { // Old R2R image generated without the hashtable of types. // We fallback to the slow path of creating the hashtable dynamically // at execution time in that scenario. The caller will handle pFoundEntry->SetClassHashBasedEntryValue(NULL); needsToBuildHashtable = TRUE; return; } #endif } else { // currently we expect only these two kinds--for DAC builds, nhTable will be nhCaseSensitive _ASSERTE(nhTable == nhCaseInsensitive); *ppTable = pTable = pCurrentClsModule->GetAvailableClassCaseInsHash(); if (pTable == NULL) { // We have not built the table yet - the caller will handle pFoundEntry->SetClassHashBasedEntryValue(NULL); needsToBuildHashtable = TRUE; return; } } _ASSERTE(pTable); if (isNested) { Module *pNameModule = pName->GetTypeModule(); PREFIX_ASSUME(pNameModule != NULL); EEClassHashTable::LookupContext sContext; if ((pBucket = pTable->GetValue(pName, pData, TRUE, &sContext)) != NULL) { switch (TypeFromToken(pName->GetTypeToken())) { case mdtTypeDef: while ((!CompareNestedEntryWithTypeDef(pNameModule->GetMDImport(), mdEncloser, pCurrentClsModule->GetAvailableClassHash(), pBucket->GetEncloser())) && (pBucket = pTable->FindNextNestedClass(pName, pData, &sContext)) != NULL); break; case mdtTypeRef: while ((!CompareNestedEntryWithTypeRef(pNameModule->GetMDImport(), mdEncloser, pCurrentClsModule->GetAvailableClassHash(), pBucket->GetEncloser())) && (pBucket = pTable->FindNextNestedClass(pName, pData, &sContext)) != NULL); break; case mdtExportedType: while ((!CompareNestedEntryWithExportedType(pNameModule->GetAssembly()->GetManifestImport(), mdEncloser, pCurrentClsModule->GetAvailableClassHash(), pBucket->GetEncloser())) && (pBucket = pTable->FindNextNestedClass(pName, pData, &sContext)) != NULL); break; default: while ((pBucket->GetEncloser() != pName->GetBucket().GetClassHashBasedEntryValue()) && (pBucket = pTable->FindNextNestedClass(pName, pData, &sContext)) != NULL); } } } else { pBucket = pTable->GetValue(pName, pData, FALSE, NULL); } if (pBucket) // Return on the first success { pFoundEntry->SetClassHashBasedEntryValue(pBucket); return; } } } // No results found: default to a NULL EEClassHashEntry_t result pFoundEntry->SetClassHashBasedEntryValue(NULL); } #ifndef DACCESS_COMPILE VOID ClassLoader::PopulateAvailableClassHashTable(Module* pModule, AllocMemTracker *pamTracker) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END; mdTypeDef td; HENUMInternal hTypeDefEnum; IMDInternalImport * pImport = pModule->GetMDImport(); LPCSTR szWinRtNamespacePrefix = NULL; DWORD cchWinRtNamespacePrefix = 0; #ifdef FEATURE_COMINTEROP SString ssFileName; StackScratchBuffer ssFileNameBuffer; if (pModule->GetAssembly()->IsWinMD() && !pModule->IsIntrospectionOnly()) { // WinMD file in execution context (not ReflectionOnly context) - use its file name as WinRT namespace prefix // (Windows requirement) // Note: Reflection can work on 'unfinished' WinMD files where the types are in 'wrong' WinMD file (i.e. // type namespace does not start with the file name) _ASSERTE(pModule->GetFile()->IsAssembly()); // No multi-module WinMD file support _ASSERTE(!pModule->GetFile()->GetPath().IsEmpty()); SplitPath( pModule->GetFile()->GetPath(), NULL, // Drive NULL, // Directory &ssFileName, NULL); // Extension szWinRtNamespacePrefix = ssFileName.GetUTF8(ssFileNameBuffer); cchWinRtNamespacePrefix = (DWORD)strlen(szWinRtNamespacePrefix); } #endif //FEATURE_COMINTEROP IfFailThrow(pImport->EnumTypeDefInit(&hTypeDefEnum)); // Now loop through all the classdefs adding the CVID and scope to the hash while(pImport->EnumTypeDefNext(&hTypeDefEnum, &td)) { AddAvailableClassHaveLock(pModule, td, pamTracker, szWinRtNamespacePrefix, cchWinRtNamespacePrefix); } pImport->EnumTypeDefClose(&hTypeDefEnum); } void ClassLoader::LazyPopulateCaseSensitiveHashTables() { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; AllocMemTracker amTracker; ModuleIterator i = GetAssembly()->IterateModules(); // Create a case-sensitive hashtable for each module, and fill it with the module's typedef entries while (i.Next()) { Module *pModule = i.GetModule(); PREFIX_ASSUME(pModule != NULL); if (pModule->IsResource()) continue; // Lazy construction of the case-sensitive hashtable of types is *only* a scenario for ReadyToRun images // (either images compiled with an old version of crossgen, or for case-insensitive type lookups in R2R modules) _ASSERT(pModule->IsReadyToRun()); EEClassHashTable * pNewClassHash = EEClassHashTable::Create(pModule, AVAILABLE_CLASSES_HASH_BUCKETS, FALSE /* bCaseInsensitive */, &amTracker); pModule->SetAvailableClassHash(pNewClassHash); PopulateAvailableClassHashTable(pModule, &amTracker); } // Add exported types of the manifest module to the hashtable if (!GetAssembly()->GetManifestModule()->IsResource()) { IMDInternalImport * pManifestImport = GetAssembly()->GetManifestImport(); HENUMInternalHolder phEnum(pManifestImport); phEnum.EnumInit(mdtExportedType, mdTokenNil); mdToken mdExportedType; while (pManifestImport->EnumNext(&phEnum, &mdExportedType)) AddExportedTypeHaveLock(GetAssembly()->GetManifestModule(), mdExportedType, &amTracker); } amTracker.SuppressRelease(); } void ClassLoader::LazyPopulateCaseInsensitiveHashTables() { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM()); } CONTRACTL_END; if (!GetAssembly()->GetManifestModule()->IsResource() && GetAssembly()->GetManifestModule()->GetAvailableClassHash() == NULL) { // This is a R2R assembly, and a case insensitive type lookup was triggered. // Construct the case-sensitive table first, since the case-insensitive table // create piggy-backs on the first. LazyPopulateCaseSensitiveHashTables(); } // Add any unhashed modules into our hash tables, and try again. AllocMemTracker amTracker; ModuleIterator i = GetAssembly()->IterateModules(); while (i.Next()) { Module *pModule = i.GetModule(); if (pModule->IsResource()) continue; if (pModule->GetAvailableClassCaseInsHash() == NULL) { EEClassHashTable *pNewClassCaseInsHash = pModule->GetAvailableClassHash()->MakeCaseInsensitiveTable(pModule, &amTracker); LOG((LF_CLASSLOADER, LL_INFO10, "%s's classes being added to case insensitive hash table\n", pModule->GetSimpleName())); { CANNOTTHROWCOMPLUSEXCEPTION(); FAULT_FORBID(); amTracker.SuppressRelease(); pModule->SetAvailableClassCaseInsHash(pNewClassCaseInsHash); FastInterlockDecrement((LONG*)&m_cUnhashedModules); } } } } /*static*/ void DECLSPEC_NORETURN ClassLoader::ThrowTypeLoadException(TypeKey *pKey, UINT resIDWhy) { STATIC_CONTRACT_THROWS; StackSString fullName; StackSString assemblyName; TypeString::AppendTypeKey(fullName, pKey); pKey->GetModule()->GetAssembly()->GetDisplayName(assemblyName); ::ThrowTypeLoadException(fullName, assemblyName, NULL, resIDWhy); } #endif TypeHandle ClassLoader::LoadConstructedTypeThrowing(TypeKey *pKey, LoadTypesFlag fLoadTypes /*= LoadTypes*/, ClassLoadLevel level /*=CLASS_LOADED*/, const InstantiationContext *pInstContext /*=NULL*/) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } if (fLoadTypes == DontLoadTypes) SO_TOLERANT; else SO_INTOLERANT; PRECONDITION(CheckPointer(pKey)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(CheckPointer(pInstContext, NULL_OK)); POSTCONDITION(CheckPointer(RETVAL, fLoadTypes==DontLoadTypes ? NULL_OK : NULL_NOT_OK)); POSTCONDITION(RETVAL.IsNull() || RETVAL.GetLoadLevel() >= level); MODE_ANY; SUPPORTS_DAC; } CONTRACT_END TypeHandle typeHnd; ClassLoadLevel existingLoadLevel = CLASS_LOAD_BEGIN; // Lookup in the classes that this class loader knows about if (pKey->HasInstantiation() && ClassLoader::IsTypicalSharedInstantiation(pKey->GetInstantiation())) { _ASSERTE(pKey->GetModule() == ComputeLoaderModule(pKey)); typeHnd = pKey->GetModule()->LookupFullyCanonicalInstantiation(pKey->GetTypeToken(), &existingLoadLevel); } if (typeHnd.IsNull()) { typeHnd = LookupTypeHandleForTypeKey(pKey); if (!typeHnd.IsNull()) { existingLoadLevel = typeHnd.GetLoadLevel(); if (existingLoadLevel >= level) g_IBCLogger.LogTypeHashTableAccess(&typeHnd); } } // If something has been published in the tables, and it's at the right level, just return it if (!typeHnd.IsNull() && existingLoadLevel >= level) { RETURN typeHnd; } #ifndef DACCESS_COMPILE if (typeHnd.IsNull() && pKey->HasInstantiation()) { if (!Generics::CheckInstantiation(pKey->GetInstantiation())) pKey->GetModule()->GetAssembly()->ThrowTypeLoadException(pKey->GetModule()->GetMDImport(), pKey->GetTypeToken(), IDS_CLASSLOAD_INVALIDINSTANTIATION); } #endif // If we're not loading any types at all, then we're not creating // instantiations either because we're in FORBIDGC_LOADER_USE mode, so // we should bail out here. if (fLoadTypes == DontLoadTypes) RETURN TypeHandle(); #ifndef DACCESS_COMPILE // If we got here, we now have to allocate a new parameterized type. // By definition, forbidgc-users aren't allowed to reach this point. CONSISTENCY_CHECK(!FORBIDGC_LOADER_USE_ENABLED()); Module *pLoaderModule = ComputeLoaderModule(pKey); RETURN(pLoaderModule->GetClassLoader()->LoadTypeHandleForTypeKey(pKey, typeHnd, level, pInstContext)); #else DacNotImpl(); RETURN(typeHnd); #endif } /*static*/ void ClassLoader::EnsureLoaded(TypeHandle typeHnd, ClassLoadLevel level) { CONTRACTL { PRECONDITION(CheckPointer(typeHnd)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED()) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } SUPPORTS_DAC; MODE_ANY; } CONTRACTL_END #ifndef DACCESS_COMPILE // Nothing to do for the DAC case if (typeHnd.GetLoadLevel() < level) { INTERIOR_STACK_PROBE_CHECK_THREAD; #ifdef FEATURE_PREJIT if (typeHnd.GetLoadLevel() == CLASS_LOAD_UNRESTOREDTYPEKEY) { typeHnd.DoRestoreTypeKey(); } #endif if (level > CLASS_LOAD_UNRESTORED) { TypeKey typeKey = typeHnd.GetTypeKey(); Module *pLoaderModule = ComputeLoaderModule(&typeKey); pLoaderModule->GetClassLoader()->LoadTypeHandleForTypeKey(&typeKey, typeHnd, level); } END_INTERIOR_STACK_PROBE; } #endif // DACCESS_COMPILE } /*static*/ void ClassLoader::TryEnsureLoaded(TypeHandle typeHnd, ClassLoadLevel level) { WRAPPER_NO_CONTRACT; #ifndef DACCESS_COMPILE // Nothing to do for the DAC case EX_TRY { ClassLoader::EnsureLoaded(typeHnd, level); } EX_CATCH { // Some type may not load successfully. For eg. generic instantiations // that do not satisfy the constraints of the type arguments. } EX_END_CATCH(RethrowTerminalExceptions); #endif // DACCESS_COMPILE } // This is separated out to avoid the overhead of C++ exception handling in the non-locking case. /* static */ TypeHandle ClassLoader::LookupTypeKeyUnderLock(TypeKey *pKey, EETypeHashTable *pTable, CrstBase *pLock) { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; // m_AvailableTypesLock has to be taken in cooperative mode to avoid deadlocks during GC GCX_MAYBE_COOP_NO_THREAD_BROKEN(!IsGCThread()); CrstHolder ch(pLock); return pTable->GetValue(pKey); } /* static */ TypeHandle ClassLoader::LookupTypeKey(TypeKey *pKey, EETypeHashTable *pTable, CrstBase *pLock, BOOL fCheckUnderLock) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; PRECONDITION(CheckPointer(pKey)); PRECONDITION(pKey->IsConstructed()); PRECONDITION(CheckPointer(pTable)); PRECONDITION(!fCheckUnderLock || CheckPointer(pLock)); MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; TypeHandle th; if (fCheckUnderLock) { th = LookupTypeKeyUnderLock(pKey, pTable, pLock); } else { th = pTable->GetValue(pKey); } return th; } #ifdef FEATURE_PREJIT /* static */ TypeHandle ClassLoader::LookupInPreferredZapModule(TypeKey *pKey, BOOL fCheckUnderLock) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; PRECONDITION(CheckPointer(pKey)); PRECONDITION(pKey->IsConstructed()); MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; // First look for an NGEN'd type in the preferred ngen module TypeHandle th; PTR_Module pPreferredZapModule = Module::ComputePreferredZapModule(pKey); if (pPreferredZapModule != NULL && pPreferredZapModule->HasNativeImage()) { th = LookupTypeKey(pKey, pPreferredZapModule->GetAvailableParamTypes(), &pPreferredZapModule->GetClassLoader()->m_AvailableTypesLock, fCheckUnderLock); } return th; } #endif // FEATURE_PREJIT /* static */ TypeHandle ClassLoader::LookupInLoaderModule(TypeKey *pKey, BOOL fCheckUnderLock) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; PRECONDITION(CheckPointer(pKey)); PRECONDITION(pKey->IsConstructed()); MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END; Module *pLoaderModule = ComputeLoaderModule(pKey); PREFIX_ASSUME(pLoaderModule!=NULL); return LookupTypeKey(pKey, pLoaderModule->GetAvailableParamTypes(), &pLoaderModule->GetClassLoader()->m_AvailableTypesLock, fCheckUnderLock); } /* static */ TypeHandle ClassLoader::LookupTypeHandleForTypeKey(TypeKey *pKey) { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; // Make an initial lookup without taking any locks. TypeHandle th = LookupTypeHandleForTypeKeyInner(pKey, FALSE); // A non-null TypeHandle for the above lookup indicates success // A null TypeHandle only indicates "well, it might have been there, // try again with a lock". This kind of negative result will // only happen while accessing the underlying EETypeHashTable // during a resize, i.e. very rarely. In such a case, we just // perform the lookup again, but indicate that appropriate locks // should be taken. if (th.IsNull()) { th = LookupTypeHandleForTypeKeyInner(pKey, TRUE); } return th; } /* static */ TypeHandle ClassLoader::LookupTypeHandleForTypeKeyInner(TypeKey *pKey, BOOL fCheckUnderLock) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; PRECONDITION(CheckPointer(pKey)); MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END // Check if it's the typical instantiation. In this case it's not stored in the same // way as other constructed types. if (!pKey->IsConstructed() || (pKey->GetKind() == ELEMENT_TYPE_CLASS && ClassLoader::IsTypicalInstantiation(pKey->GetModule(), pKey->GetTypeToken(), pKey->GetInstantiation()))) { return TypeHandle(pKey->GetModule()->LookupTypeDef(pKey->GetTypeToken())); } #ifdef FEATURE_PREJIT // The following ways of finding a constructed type should be mutually exclusive! // 1. Look for a zapped item in the PreferredZapModule // 2. Look for a unzapped (JIT-loaded) item in the LoaderModule TypeHandle thPZM = LookupInPreferredZapModule(pKey, fCheckUnderLock); if (!thPZM.IsNull()) { return thPZM; } #endif // FEATURE_PREJIT // Next look in the loader module. This is where the item is guaranteed to live if // it is not latched from an NGEN image, i.e. if it is JIT loaded. // If the thing is not NGEN'd then this may // be different to pPreferredZapModule. If they are the same then // we can reuse the results of the lookup above. TypeHandle thLM = LookupInLoaderModule(pKey, fCheckUnderLock); if (!thLM.IsNull()) { return thLM; } return TypeHandle(); } //--------------------------------------------------------------------------- // ClassLoader::TryFindDynLinkZapType // // This is a major routine in the process of finding and using // zapped generic instantiations (excluding those which were zapped into // their PreferredZapModule). // // DynLinkZapItems are generic instantiations that may have been NGEN'd // into more than one NGEN image (e.g. the code and TypeHandle for // List may in principle be zapped into several client images - it is theoretically // an NGEN policy decision about how often this done, though for now we // have hard-baked a strategy). // // There are lots of potential problems with this kind of duplication // and the way we get around nearly all of these is to make sure that // we only use one at most one "unique" copy of each item // at runtime. Thus we keep tables in the SharedDomain and the AppDomain indicating // which unique items have been chosen. If an item is "loaded" by this technique // then it will not be loaded by any other technique. // // Note generic instantiations may have the good fortune to be zapped // into the "PreferredZapModule". If so we can eager bind to them and // they will not be considered to be DynLinkZapItems. We always // look in the PreferredZapModule first, and we do not add an entry to the // DynLinkZapItems table for this case. // // Zap references to DynLinkZapItems are always via encoded fixups, except // for a few intra-module references when one DynLinkZapItem is "TightlyBound" // to another, e.g. an canonical DynLinkZap MethodTable may directly refer to // its EEClass - this is because we know that if one is used at runtime then the // other will also be. These items should be thought of as together constituting // one DynLinkedZapItem. // // This function section searches for a copy of the instantiation in various NGEN images. // This is effectively like doing a load since we are choosing which copy of the instantiation // to use from among a number of potential candidates. We have to have the loading lock // for this item before we can do this to make sure no other threads choose a // different copy of the instantiation, and that no other threads are JIT-loading // the instantiation. #ifndef DACCESS_COMPILE #ifdef FEATURE_FULL_NGEN /* static */ TypeHandle ClassLoader::TryFindDynLinkZapType(TypeKey *pKey) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM()); PRECONDITION(CheckPointer(pKey)); PRECONDITION(pKey->IsConstructed()); MODE_ANY; } CONTRACTL_END; // For the introspection-only case, we can skip this step as introspection assemblies // do not use NGEN images. if (pKey->IsIntrospectionOnly()) return TypeHandle(); // Never use dyn link zap items during ngen time. We will independently decide later // whether we want to store the item into ngen image or not. // Note that it is not good idea to make decisions based on the list of depencies here // since their list may not be fully populated yet. if (IsCompilationProcess()) return TypeHandle(); TypeHandle th = TypeHandle(); #ifndef CROSSGEN_COMPILE // We need to know which domain the item must live in (DomainNeutral or AppDomain) // Note we can't use the domain from GetLoaderModule()->GetDomain() because at NGEN // time this may not be accurate (we may be deliberately duplicating a domain-neutral // instantiation into a domain-specific image, in the sense that the LoaderModule // returned by ComputeLoaderModule may be the current module being // NGEN'd).... BaseDomain * pRequiredDomain = BaseDomain::ComputeBaseDomain(pKey); // Next look in each ngen'ed image in turn // Searching the shared domain and the app domain are slightly different. if (pRequiredDomain->IsSharedDomain()) { // This switch to cooperative mode makes the iteration below thread safe. It ensures that the underlying // async HashMap storage is not going to disapper while we are iterating it. Other uses of SharedAssemblyIterator // have same problem, but I have fixed just this one as targeted ask mode fix. GCX_COOP(); // Searching for SharedDomain instantiation involves searching all shared assemblies.... // Note we may choose to use an instantiation from an assembly that is from an NGEN // image that is not logically speaking part of the currently running AppDomain. This // tkaes advantage of the fact that at the moment SharedDomain NGEN images are never unloaded. // Thus SharedDomain NGEN images effectively contribute all their instantiations to all // AppDomains. // // This will have to change if we ever start unloading NGEN images from the SharedDomain SharedDomain::SharedAssemblyIterator assem; while (th.IsNull() && assem.Next()) { ModuleIterator i = assem.GetAssembly()->IterateModules(); while (i.Next()) { Module *pModule = i.GetModule(); if (!pModule->HasNativeImage()) continue; // If the module hasn't reached FILE_LOADED in some domain, it cannot provide candidate instantiations if (!pModule->IsReadyForTypeLoad()) continue; TypeHandle thFromZapModule = pModule->GetAvailableParamTypes()->GetValue(pKey); // Check that the item really is a zapped item, i.e. that it has not been JIT-loaded to the module if (thFromZapModule.IsNull() || !thFromZapModule.IsZapped()) continue; th = thFromZapModule; } } } else { // Searching for domain specific instantiation involves searching all // domain-specific assemblies in the relevant AppDomain.... AppDomain * pDomain = pRequiredDomain->AsAppDomain(); _ASSERTE(!(pKey->IsIntrospectionOnly())); AppDomain::AssemblyIterator assemblyIterator = pDomain->IterateAssembliesEx( (AssemblyIterationFlags)(kIncludeLoaded | kIncludeExecution)); CollectibleAssemblyHolder pDomainAssembly; while (th.IsNull() && assemblyIterator.Next(pDomainAssembly.This())) { CollectibleAssemblyHolder pAssembly = pDomainAssembly->GetLoadedAssembly(); // Make sure the domain of the NGEN'd images associated with the assembly matches... if (pAssembly->GetDomain() == pRequiredDomain) { DomainAssembly::ModuleIterator i = pDomainAssembly->IterateModules(kModIterIncludeLoaded); while (th.IsNull() && i.Next()) { Module * pModule = i.GetLoadedModule(); if (!pModule->HasNativeImage()) continue; // If the module hasn't reached FILE_LOADED in some domain, it cannot provide candidate instantiations if (!pModule->IsReadyForTypeLoad()) continue; TypeHandle thFromZapModule = pModule->GetAvailableParamTypes()->GetValue(pKey); // Check that the item really is a zapped item if (thFromZapModule.IsNull() || !thFromZapModule.IsZapped()) continue; th = thFromZapModule; } } } } #endif // CROSSGEN_COMPILE return th; } #endif // FEATURE_FULL_NGEN #endif // !DACCESS_COMPILE // FindClassModuleThrowing discovers which module the type you're looking for is in and loads the Module if necessary. // Basically, it iterates through all of the assembly's modules until a name match is found in a module's // AvailableClassHashTable. // // The possible outcomes are: // // - Function returns TRUE - class exists and we successfully found/created the containing Module. See below // for how to deconstruct the results. // - Function returns FALSE - class affirmatively NOT found (that means it doesn't exist as a regular type although // it could also be a parameterized type) // - Function throws - OOM or some other reason we couldn't do the job (if it's a case-sensitive search // and you're looking for already loaded type or you've set the TokenNotToLoad. // we are guaranteed not to find a reason to throw.) // // // If it succeeds (returns TRUE), one of the following will occur. Check (*pType)->IsNull() to discriminate. // // 1. *pType: set to the null TypeHandle() // *ppModule: set to the owning Module // *pmdClassToken: set to the typedef // *pmdFoundExportedType: if this name bound to an ExportedType, this contains the mdtExportedType token (otherwise, // it's set to mdTokenNil.) You need this because in this case, *pmdClassToken is just // a best guess and you need to verify it. (The division of labor between this // and LoadTypeHandle could definitely be better!) // // 2. *pType: set to non-null TypeHandle() // This means someone else had already done this same lookup before you and caused the actual // TypeHandle to be cached. Since we know that's what you *really* wanted, we'll just forget the // Module/typedef stuff and give you the actual TypeHandle. // // BOOL ClassLoader::FindClassModuleThrowing( const NameHandle * pOriginalName, TypeHandle * pType, mdToken * pmdClassToken, Module ** ppModule, mdToken * pmdFoundExportedType, HashedTypeEntry * pFoundEntry, Module * pLookInThisModuleOnly, Loader::LoadFlag loadFlag) { CONTRACTL { INSTANCE_CHECK; if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } PRECONDITION(CheckPointer(pOriginalName)); PRECONDITION(CheckPointer(ppModule)); MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END NameHandleTable nhTable = nhCaseSensitive; // just to initialize this ... // Make a copy of the original name which we can modify (to lowercase) NameHandle localName = *pOriginalName; NameHandle * pName = &localName; switch (pName->GetTable()) { case nhCaseInsensitive: { #ifndef DACCESS_COMPILE // GC-type users should only be loading types through tokens. #ifdef _DEBUG_IMPL _ASSERTE(!FORBIDGC_LOADER_USE_ENABLED()); #endif // Use the case insensitive table nhTable = nhCaseInsensitive; // Create a low case version of the namespace and name LPUTF8 pszLowerNameSpace = NULL; LPUTF8 pszLowerClassName = NULL; int allocLen; if (pName->GetNameSpace()) { allocLen = InternalCasingHelper::InvariantToLower( NULL, 0, pName->GetNameSpace()); if (allocLen == 0) { return FALSE; } pszLowerNameSpace = (LPUTF8)_alloca(allocLen); if (allocLen == 1) { *pszLowerNameSpace = '\0'; } else if (!InternalCasingHelper::InvariantToLower( pszLowerNameSpace, allocLen, pName->GetNameSpace())) { return FALSE; } } _ASSERTE(pName->GetName() != NULL); allocLen = InternalCasingHelper::InvariantToLower(NULL, 0, pName->GetName()); if (allocLen == 0) { return FALSE; } pszLowerClassName = (LPUTF8)_alloca(allocLen); if (!InternalCasingHelper::InvariantToLower( pszLowerClassName, allocLen, pName->GetName())) { return FALSE; } // Substitute the lower case version of the name. // The field are will be released when we leave this scope pName->SetName(pszLowerNameSpace, pszLowerClassName); break; #else DacNotImpl(); break; #endif // #ifndef DACCESS_COMPILE } case nhCaseSensitive: nhTable = nhCaseSensitive; break; } // Remember if there are any unhashed modules. We must do this before // the actual look to avoid a race condition with other threads doing lookups. #ifdef LOGGING BOOL incomplete = (m_cUnhashedModules > 0); #endif HashDatum Data; EEClassHashTable * pTable = NULL; HashedTypeEntry foundEntry; BOOL needsToBuildHashtable; GetClassValue(nhTable, pName, &Data, &pTable, pLookInThisModuleOnly, &foundEntry, loadFlag, needsToBuildHashtable); // In the case of R2R modules, the search is only performed in the hashtable saved in the // R2R image, and this is why we return (whether we found a valid typedef token or not). // Note: case insensitive searches are not used/supported in R2R images. if (foundEntry.GetEntryType() == HashedTypeEntry::EntryType::IsHashedTokenEntry) { *pType = TypeHandle(); HashedTypeEntry::TokenTypeEntry tokenAndModulePair = foundEntry.GetTokenBasedEntryValue(); switch (TypeFromToken(tokenAndModulePair.m_TypeToken)) { case mdtTypeDef: *pmdClassToken = tokenAndModulePair.m_TypeToken; *pmdFoundExportedType = mdTokenNil; break; case mdtExportedType: *pmdClassToken = mdTokenNil; *pmdFoundExportedType = tokenAndModulePair.m_TypeToken; break; default: _ASSERT(false); return FALSE; } *ppModule = tokenAndModulePair.m_pModule; if (pFoundEntry != NULL) *pFoundEntry = foundEntry; return TRUE; } EEClassHashEntry_t * pBucket = foundEntry.GetClassHashBasedEntryValue(); if (pBucket == NULL && needsToBuildHashtable) { AvailableClasses_LockHolder lh(this); // Try again with the lock. This will protect against another thread reallocating // the hash table underneath us GetClassValue(nhTable, pName, &Data, &pTable, pLookInThisModuleOnly, &foundEntry, loadFlag, needsToBuildHashtable); pBucket = foundEntry.GetClassHashBasedEntryValue(); #ifndef DACCESS_COMPILE if ((pBucket == NULL) && (m_cUnhashedModules > 0)) { _ASSERT(needsToBuildHashtable); if (nhTable == nhCaseInsensitive) { LazyPopulateCaseInsensitiveHashTables(); } else { // Note: This codepath is only valid for R2R scenarios LazyPopulateCaseSensitiveHashTables(); } // Try yet again with the new classes added GetClassValue(nhTable, pName, &Data, &pTable, pLookInThisModuleOnly, &foundEntry, loadFlag, needsToBuildHashtable); pBucket = foundEntry.GetClassHashBasedEntryValue(); _ASSERT(!needsToBuildHashtable); } #endif } if (pBucket == NULL) { #if defined(_DEBUG_IMPL) && !defined(DACCESS_COMPILE) LPCUTF8 szName = pName->GetName(); if (szName == NULL) szName = ""; LOG((LF_CLASSLOADER, LL_INFO10, "Failed to find type \"%s\", assembly \"%ws\" in hash table. Incomplete = %d\n", szName, GetAssembly()->GetDebugName(), incomplete)); #endif return FALSE; } if (pName->GetTable() == nhCaseInsensitive) { _ASSERTE(Data); pBucket = PTR_EEClassHashEntry(Data); Data = pBucket->GetData(); } // Lower bit is a discriminator. If the lower bit is NOT SET, it means we have // a TypeHandle. Otherwise, we have a Module/CL. if ((dac_cast(Data) & EECLASSHASH_TYPEHANDLE_DISCR) == 0) { TypeHandle t = TypeHandle::FromPtr(Data); _ASSERTE(!t.IsNull()); *pType = t; if (pFoundEntry != NULL) { pFoundEntry->SetClassHashBasedEntryValue(pBucket); } return TRUE; } // We have a Module/CL if (!pTable->UncompressModuleAndClassDef(Data, loadFlag, ppModule, pmdClassToken, pmdFoundExportedType)) { _ASSERTE(loadFlag != Loader::Load); return FALSE; } *pType = TypeHandle(); if (pFoundEntry != NULL) { pFoundEntry->SetClassHashBasedEntryValue(pBucket); } return TRUE; } // ClassLoader::FindClassModuleThrowing #ifndef DACCESS_COMPILE // Returns true if the full name (namespace+name) of pName matches that // of typeHnd; otherwise false. Because this is nothrow, it will default // to false for all exceptions (such as OOM). bool CompareNameHandleWithTypeHandleNoThrow( const NameHandle * pName, TypeHandle typeHnd) { bool fRet = false; EX_TRY { // This block is specifically designed to handle transient faults such // as OOM exceptions. CONTRACT_VIOLATION(FaultViolation | ThrowsViolation); StackSString ssBuiltName; ns::MakePath(ssBuiltName, StackSString(SString::Utf8, pName->GetNameSpace()), StackSString(SString::Utf8, pName->GetName())); StackSString ssName; typeHnd.GetName(ssName); fRet = ssName.Equals(ssBuiltName) == TRUE; } EX_CATCH { // Technically, the above operations should never result in a non-OOM // exception, but we'll put the rethrow line in there just in case. CONSISTENCY_CHECK(!GET_EXCEPTION()->IsTerminal()); RethrowTerminalExceptions; } EX_END_CATCH(SwallowAllExceptions); return fRet; } #endif // #ifndef DACCESS_COMPILE // 1024 seems like a good bet at detecting a loop in the type forwarding. static const UINT32 const_cMaxTypeForwardingChainSize = 1024; // Does not throw an exception if the type was not found. Use LoadTypeHandleThrowIfFailed() // instead if you need that. // // Returns: // pName->m_pBucket // Will be set to the 'final' TypeDef bucket if pName->GetTokenType() is mdtBaseType. // TypeHandle ClassLoader::LoadTypeHandleThrowing( NameHandle * pName, ClassLoadLevel level, Module * pLookInThisModuleOnly /*=NULL*/) { CONTRACT(TypeHandle) { INSTANCE_CHECK; if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } DAC_LOADS_TYPE(level, !pName->OKToLoad()); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(CheckPointer(pName)); POSTCONDITION(RETVAL.IsNull() || RETVAL.GetLoadLevel() >= level); MODE_ANY; SUPPORTS_DAC; } CONTRACT_END TypeHandle typeHnd; INTERIOR_STACK_PROBE_NOTHROW_CHECK_THREAD(RETURN_FROM_INTERIOR_PROBE(TypeHandle())); Module * pFoundModule = NULL; mdToken FoundCl; HashedTypeEntry foundEntry; mdExportedType FoundExportedType = mdTokenNil; UINT32 cLoopIterations = 0; ClassLoader * pClsLdr = this; while (true) { if (cLoopIterations++ >= const_cMaxTypeForwardingChainSize) { // If we've looped too many times due to type forwarding, return null TypeHandle // Would prefer to return a format exception, but the original behaviour // was to detect a stack overflow possibility and return a null, and // so we need to maintain this. typeHnd = TypeHandle(); break; } // Look outside the lock (though we're actually still a long way from the // lock at this point...). This may discover that the type is actually // defined in another module... if (!pClsLdr->FindClassModuleThrowing( pName, &typeHnd, &FoundCl, &pFoundModule, &FoundExportedType, &foundEntry, pLookInThisModuleOnly, pName->OKToLoad() ? Loader::Load : Loader::DontLoad)) { // Didn't find anything, no point looping indefinitely break; } _ASSERTE(!foundEntry.IsNull()); if (pName->GetTypeToken() == mdtBaseType) { // We should return the found bucket in the pName pName->SetBucket(foundEntry); } if (!typeHnd.IsNull()) { // Found the cached value, or a constructedtype if (typeHnd.GetLoadLevel() < level) { typeHnd = pClsLdr->LoadTypeDefThrowing( typeHnd.GetModule(), typeHnd.GetCl(), ClassLoader::ReturnNullIfNotFound, ClassLoader::PermitUninstDefOrRef, // When loading by name we always permit naked type defs/refs pName->GetTokenNotToLoad(), level); } break; } // Found a cl, pModule pair // If the found module's class loader is not the same as the current class loader, // then this is a forwarded type and we want to do something else (see // code:#LoadTypeHandle_TypeForwarded). if (pFoundModule->GetClassLoader() == pClsLdr) { BOOL fTrustTD = TRUE; #ifndef DACCESS_COMPILE CONTRACT_VIOLATION(ThrowsViolation); BOOL fVerifyTD = FALSE; // If this is an exported type with a mdTokenNil class token, then then // exported type did not give a typedefID hint. We won't be able to trust the typedef // here. if ((FoundExportedType != mdTokenNil) && (FoundCl == mdTokenNil)) { fVerifyTD = TRUE; fTrustTD = FALSE; } // verify that FoundCl is a valid token for pFoundModule, because // it may be just the hint saved in an ExportedType in another scope else if (fVerifyTD) { fTrustTD = pFoundModule->GetMDImport()->IsValidToken(FoundCl); } #endif // #ifndef DACCESS_COMPILE if (fTrustTD) { typeHnd = pClsLdr->LoadTypeDefThrowing( pFoundModule, FoundCl, ClassLoader::ReturnNullIfNotFound, ClassLoader::PermitUninstDefOrRef, // when loading by name we always permit naked type defs/refs pName->GetTokenNotToLoad(), level); } #ifndef DACCESS_COMPILE // If we used a TypeDef saved in a ExportedType, if we didn't verify // the hash for this internal module, don't trust the TD value. if (fVerifyTD) { if (typeHnd.IsNull() || !CompareNameHandleWithTypeHandleNoThrow(pName, typeHnd)) { if (SUCCEEDED(pClsLdr->FindTypeDefByExportedType( pClsLdr->GetAssembly()->GetManifestImport(), FoundExportedType, pFoundModule->GetMDImport(), &FoundCl))) { typeHnd = pClsLdr->LoadTypeDefThrowing( pFoundModule, FoundCl, ClassLoader::ReturnNullIfNotFound, ClassLoader::PermitUninstDefOrRef, pName->GetTokenNotToLoad(), level); } else { typeHnd = TypeHandle(); } } } #endif // #ifndef DACCESS_COMPILE break; } else { //#LoadTypeHandle_TypeForwarded // pName is a host instance so it's okay to set fields in it in a DAC build HashedTypeEntry& bucket = pName->GetBucket(); // Reset pName's bucket entry if (bucket.GetEntryType() == HashedTypeEntry::IsHashedClassEntry && bucket.GetClassHashBasedEntryValue()->GetEncloser()) { // We will be searching for the type name again, so set the nesting/context type to the // encloser of just found type pName->SetBucket(HashedTypeEntry().SetClassHashBasedEntryValue(bucket.GetClassHashBasedEntryValue()->GetEncloser())); } else { pName->SetBucket(HashedTypeEntry()); } // Update the class loader for the new module/token pair. pClsLdr = pFoundModule->GetClassLoader(); pLookInThisModuleOnly = NULL; } #ifndef DACCESS_COMPILE // Replace AvailableClasses Module entry with found TypeHandle if (!typeHnd.IsNull() && typeHnd.IsRestored() && foundEntry.GetEntryType() == HashedTypeEntry::EntryType::IsHashedClassEntry && (foundEntry.GetClassHashBasedEntryValue() != NULL) && (foundEntry.GetClassHashBasedEntryValue()->GetData() != typeHnd.AsPtr())) { foundEntry.GetClassHashBasedEntryValue()->SetData(typeHnd.AsPtr()); } #endif // !DACCESS_COMPILE } END_INTERIOR_STACK_PROBE; RETURN typeHnd; } // ClassLoader::LoadTypeHandleThrowing /* static */ TypeHandle ClassLoader::LoadPointerOrByrefTypeThrowing(CorElementType typ, TypeHandle baseType, LoadTypesFlag fLoadTypes/*=LoadTypes*/, ClassLoadLevel level/*=CLASS_LOADED*/) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } MODE_ANY; PRECONDITION(CheckPointer(baseType)); PRECONDITION(typ == ELEMENT_TYPE_BYREF || typ == ELEMENT_TYPE_PTR); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); POSTCONDITION(CheckPointer(RETVAL, ((fLoadTypes == LoadTypes) ? NULL_NOT_OK : NULL_OK))); SUPPORTS_DAC; } CONTRACT_END TypeKey key(typ, baseType); RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level)); } /* static */ TypeHandle ClassLoader::LoadNativeValueTypeThrowing(TypeHandle baseType, LoadTypesFlag fLoadTypes/*=LoadTypes*/, ClassLoadLevel level/*=CLASS_LOADED*/) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } MODE_ANY; PRECONDITION(CheckPointer(baseType)); PRECONDITION(baseType.AsMethodTable()->IsValueType()); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); POSTCONDITION(CheckPointer(RETVAL, ((fLoadTypes == LoadTypes) ? NULL_NOT_OK : NULL_OK))); } CONTRACT_END TypeKey key(ELEMENT_TYPE_VALUETYPE, baseType); RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level)); } /* static */ TypeHandle ClassLoader::LoadFnptrTypeThrowing(BYTE callConv, DWORD ntypars, TypeHandle* inst, LoadTypesFlag fLoadTypes/*=LoadTypes*/, ClassLoadLevel level/*=CLASS_LOADED*/) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); POSTCONDITION(CheckPointer(RETVAL, ((fLoadTypes == LoadTypes) ? NULL_NOT_OK : NULL_OK))); MODE_ANY; SUPPORTS_DAC; } CONTRACT_END TypeKey key(callConv, ntypars, inst); RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level)); } // Find an instantiation of a generic type if it has already been created. // If typeDef is not a generic type or is already instantiated then throw an exception. // If its arity does not match ntypars then throw an exception. // Value will be non-null if we're loading types. /* static */ TypeHandle ClassLoader::LoadGenericInstantiationThrowing(Module *pModule, mdTypeDef typeDef, Instantiation inst, LoadTypesFlag fLoadTypes/*=LoadTypes*/, ClassLoadLevel level/*=CLASS_LOADED*/, const InstantiationContext *pInstContext/*=NULL*/, BOOL fFromNativeImage /*=FALSE*/) { // This can be called in FORBIDGC_LOADER_USE mode by the debugger to find // a particular generic type instance that is already loaded. CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } PRECONDITION(CheckPointer(pModule)); MODE_ANY; PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(CheckPointer(pInstContext, NULL_OK)); POSTCONDITION(CheckPointer(RETVAL, ((fLoadTypes == LoadTypes) ? NULL_NOT_OK : NULL_OK))); SUPPORTS_DAC; } CONTRACT_END // Essentially all checks to determine if a generic instantiation of a type // is well-formed go in this method, i.e. this is the // "choke" point through which all attempts // to create an instantiation flow. There is a similar choke point for generic // methods in genmeth.cpp. if (inst.IsEmpty() || ClassLoader::IsTypicalInstantiation(pModule, typeDef, inst)) { TypeHandle th = LoadTypeDefThrowing(pModule, typeDef, ThrowIfNotFound, PermitUninstDefOrRef, fLoadTypes == DontLoadTypes ? tdAllTypes : tdNoTypes, level, fFromNativeImage ? NULL : &inst); _ASSERTE(th.GetNumGenericArgs() == inst.GetNumArgs()); RETURN th; } if (!fFromNativeImage) { TypeHandle th = ClassLoader::LoadTypeDefThrowing(pModule, typeDef, ThrowIfNotFound, PermitUninstDefOrRef, fLoadTypes == DontLoadTypes ? tdAllTypes : tdNoTypes, level, fFromNativeImage ? NULL : &inst); _ASSERTE(th.GetNumGenericArgs() == inst.GetNumArgs()); } TypeKey key(pModule, typeDef, inst); #ifndef DACCESS_COMPILE // To avoid loading useless shared instantiations, normalize shared instantiations to the canonical form // (e.g. Dictionary -> Dictionary<_Canon,_Canon>) // The denormalized shared instantiations should be needed only during JITing, so it is fine to skip this // for DACCESS_COMPILE. if (TypeHandle::IsCanonicalSubtypeInstantiation(inst) && !IsCanonicalGenericInstantiation(inst)) { RETURN(ClassLoader::LoadCanonicalGenericInstantiation(&key, fLoadTypes, level)); } #endif RETURN(LoadConstructedTypeThrowing(&key, fLoadTypes, level, pInstContext)); } // For non-nested classes, gets the ExportedType name and finds the corresponding // TypeDef. // For nested classes, gets the name of the ExportedType and its encloser. // Recursively gets and keeps the name for each encloser until we have the top // level one. Gets the TypeDef token for that. Then, returns from the // recursion, using the last found TypeDef token in order to find the // next nested level down TypeDef token. Finally, returns the TypeDef // token for the type we care about. /*static*/ HRESULT ClassLoader::FindTypeDefByExportedType(IMDInternalImport *pCTImport, mdExportedType mdCurrent, IMDInternalImport *pTDImport, mdTypeDef *mtd) { CONTRACTL { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; MODE_ANY; SUPPORTS_DAC; } CONTRACTL_END mdToken mdImpl; LPCSTR szcNameSpace; LPCSTR szcName; HRESULT hr; IfFailRet(pCTImport->GetExportedTypeProps( mdCurrent, &szcNameSpace, &szcName, &mdImpl, NULL, //binding NULL)); //flags if ((TypeFromToken(mdImpl) == mdtExportedType) && (mdImpl != mdExportedTypeNil)) { // mdCurrent is a nested ExportedType IfFailRet(FindTypeDefByExportedType(pCTImport, mdImpl, pTDImport, mtd)); // Get TypeDef token for this nested type return pTDImport->FindTypeDef(szcNameSpace, szcName, *mtd, mtd); } // Get TypeDef token for this top-level type return pTDImport->FindTypeDef(szcNameSpace, szcName, mdTokenNil, mtd); } #ifndef DACCESS_COMPILE VOID ClassLoader::CreateCanonicallyCasedKey(LPCUTF8 pszNameSpace, LPCUTF8 pszName, __out LPUTF8 *ppszOutNameSpace, __out LPUTF8 *ppszOutName) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_NOTRIGGER; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; } CONTRACTL_END // We can use the NoThrow versions here because we only call this routine if we're maintaining // a case-insensitive hash table, and the creation of that table initialized the // CasingHelper system. INT32 iNSLength = InternalCasingHelper::InvariantToLowerNoThrow(NULL, 0, pszNameSpace); if (!iNSLength) { COMPlusThrowOM(); } INT32 iNameLength = InternalCasingHelper::InvariantToLowerNoThrow(NULL, 0, pszName); if (!iNameLength) { COMPlusThrowOM(); } { //Calc & allocate path length //Includes terminating null S_SIZE_T allocSize = S_SIZE_T(iNSLength) + S_SIZE_T(iNameLength); if (allocSize.IsOverflow()) { ThrowHR(COR_E_OVERFLOW); } AllocMemHolder pszOutNameSpace (GetAssembly()->GetHighFrequencyHeap()->AllocMem(allocSize)); *ppszOutNameSpace = pszOutNameSpace; if (iNSLength == 1) { **ppszOutNameSpace = '\0'; } else { if (!InternalCasingHelper::InvariantToLowerNoThrow(*ppszOutNameSpace, iNSLength, pszNameSpace)) { COMPlusThrowOM(); } } *ppszOutName = *ppszOutNameSpace + iNSLength; if (!InternalCasingHelper::InvariantToLowerNoThrow(*ppszOutName, iNameLength, pszName)) { COMPlusThrowOM(); } pszOutNameSpace.SuppressRelease(); } } #endif // #ifndef DACCESS_COMPILE // // Return a class that is already loaded // Only for type refs and type defs (not type specs) // /*static*/ TypeHandle ClassLoader::LookupTypeDefOrRefInModule(Module *pModule, mdToken cl, ClassLoadLevel *pLoadLevel) { CONTRACT(TypeHandle) { NOTHROW; GC_NOTRIGGER; FORBID_FAULT; MODE_ANY; PRECONDITION(CheckPointer(pModule)); POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); SUPPORTS_DAC; } CONTRACT_END BAD_FORMAT_NOTHROW_ASSERT((TypeFromToken(cl) == mdtTypeRef || TypeFromToken(cl) == mdtTypeDef || TypeFromToken(cl) == mdtTypeSpec)); TypeHandle typeHandle; if (TypeFromToken(cl) == mdtTypeDef) typeHandle = pModule->LookupTypeDef(cl, pLoadLevel); else if (TypeFromToken(cl) == mdtTypeRef) { typeHandle = pModule->LookupTypeRef(cl); if (pLoadLevel && !typeHandle.IsNull()) { *pLoadLevel = typeHandle.GetLoadLevel(); } } RETURN(typeHandle); } DomainAssembly *ClassLoader::GetDomainAssembly(AppDomain *pDomain/*=NULL*/) { WRAPPER_NO_CONTRACT; return GetAssembly()->GetDomainAssembly(pDomain); } #ifndef DACCESS_COMPILE // // Free all modules associated with this loader // void ClassLoader::FreeModules() { CONTRACTL { INSTANCE_CHECK; NOTHROW; GC_TRIGGERS; MODE_PREEMPTIVE; DISABLED(FORBID_FAULT); //Lots of crud to clean up to make this work } CONTRACTL_END; Module *pManifest = NULL; if (GetAssembly() && (NULL != (pManifest = GetAssembly()->GetManifestModule()))) { // Unload the manifest last, since it contains the module list in its rid map ModuleIterator i = GetAssembly()->IterateModules(); while (i.Next()) { // Have the module free its various tables and some of the EEClass links if (i.GetModule() != pManifest) i.GetModule()->Destruct(); } // Now do the manifest module. pManifest->Destruct(); } } ClassLoader::~ClassLoader() { CONTRACTL { NOTHROW; DESTRUCTOR_CHECK; GC_TRIGGERS; MODE_PREEMPTIVE; DISABLED(FORBID_FAULT); //Lots of crud to clean up to make this work } CONTRACTL_END #ifdef _DEBUG // Do not walk m_pUnresolvedClassHash at destruct time as it is loaderheap allocated memory // and may already have been deallocated via an AllocMemTracker. m_pUnresolvedClassHash = (PendingTypeLoadTable*)(UINT_PTR)0xcccccccc; #endif #ifdef _DEBUG // LOG(( // LF_CLASSLOADER, // INFO3, // "Deleting classloader %x\n" // " >EEClass data: %10d bytes\n" // " >Classname hash: %10d bytes\n" // " >FieldDesc data: %10d bytes\n" // " >MethodDesc data: %10d bytes\n" // " >GCInfo: %10d bytes\n" // " >Interface maps: %10d bytes\n" // " >MethodTables: %10d bytes\n" // " >Vtables: %10d bytes\n" // " >Static fields: %10d bytes\n" // "# methods: %10d\n" // "# field descs: %10d\n" // "# classes: %10d\n" // "# dup intf slots: %10d\n" // "# array classrefs: %10d\n" // "Array class overhead:%10d bytes\n", // this, // m_dwEEClassData, // m_pAvailableClasses->m_dwDebugMemory, // m_dwFieldDescData, // m_dwMethodDescData, // m_dwGCSize, // m_dwInterfaceMapSize, // m_dwMethodTableSize, // m_dwVtableData, // m_dwStaticFieldData, // m_dwDebugMethods, // m_dwDebugFieldDescs, // m_dwDebugClasses, // m_dwDebugDuplicateInterfaceSlots, // )); #endif FreeModules(); m_UnresolvedClassLock.Destroy(); m_AvailableClassLock.Destroy(); m_AvailableTypesLock.Destroy(); } //---------------------------------------------------------------------------- // The constructor should only initialize enough to ensure that the destructor doesn't // crash. It cannot allocate or do anything that might fail as that would leave // the ClassLoader undestructable. Any such tasks should be done in ClassLoader::Init(). //---------------------------------------------------------------------------- ClassLoader::ClassLoader(Assembly *pAssembly) { CONTRACTL { CONSTRUCTOR_CHECK; NOTHROW; GC_NOTRIGGER; MODE_ANY; FORBID_FAULT; } CONTRACTL_END m_pAssembly = pAssembly; m_pUnresolvedClassHash = NULL; m_cUnhashedModules = 0; #ifdef _DEBUG m_dwDebugMethods = 0; m_dwDebugFieldDescs = 0; m_dwDebugClasses = 0; m_dwDebugDuplicateInterfaceSlots = 0; m_dwGCSize = 0; m_dwInterfaceMapSize = 0; m_dwMethodTableSize = 0; m_dwVtableData = 0; m_dwStaticFieldData = 0; m_dwFieldDescData = 0; m_dwMethodDescData = 0; m_dwEEClassData = 0; #endif } //---------------------------------------------------------------------------- // This function completes the initialization of the ClassLoader. It can // assume the constructor is run and that the function is entered with // ClassLoader in a safely destructable state. This function can throw // but whether it throws or succeeds, it must leave the ClassLoader in a safely // destructable state. //---------------------------------------------------------------------------- VOID ClassLoader::Init(AllocMemTracker *pamTracker) { STANDARD_VM_CONTRACT; m_pUnresolvedClassHash = PendingTypeLoadTable::Create(GetAssembly()->GetLowFrequencyHeap(), UNRESOLVED_CLASS_HASH_BUCKETS, pamTracker); m_UnresolvedClassLock.Init(CrstUnresolvedClassLock); // This lock is taken within the classloader whenever we have to enter a // type in one of the modules governed by the loader. // The process of creating these types may be reentrant. The ordering has // not yet been sorted out, and when we sort it out we should also modify the // ordering for m_AvailableTypesLock in BaseDomain. m_AvailableClassLock.Init( CrstAvailableClass, CRST_REENTRANCY); // This lock is taken within the classloader whenever we have to insert a new param. type into the table // This lock also needs to be taken for a read operation in a GC_NOTRIGGER scope, thus the ANYMODE flag. m_AvailableTypesLock.Init( CrstAvailableParamTypes, (CrstFlags)(CRST_UNSAFE_ANYMODE | CRST_DEBUGGER_THREAD)); #ifdef _DEBUG CorTypeInfo::CheckConsistency(); #endif } #endif // #ifndef DACCESS_COMPILE /*static*/ TypeHandle ClassLoader::LoadTypeDefOrRefOrSpecThrowing(Module *pModule, mdToken typeDefOrRefOrSpec, const SigTypeContext *pTypeContext, NotFoundAction fNotFoundAction /* = ThrowIfNotFound */ , PermitUninstantiatedFlag fUninstantiated /* = FailIfUninstDefOrRef */, LoadTypesFlag fLoadTypes/*=LoadTypes*/ , ClassLoadLevel level /* = CLASS_LOADED */, BOOL dropGenericArgumentLevel /* = FALSE */, const Substitution *pSubst) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } PRECONDITION(CheckPointer(pModule)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(FORBIDGC_LOADER_USE_ENABLED() || GetAppDomain()->CheckCanLoadTypes(pModule->GetAssembly())); POSTCONDITION(CheckPointer(RETVAL, (fNotFoundAction == ThrowIfNotFound)? NULL_NOT_OK : NULL_OK)); } CONTRACT_END if (TypeFromToken(typeDefOrRefOrSpec) == mdtTypeSpec) { ULONG cSig; PCCOR_SIGNATURE pSig; IMDInternalImport *pInternalImport = pModule->GetMDImport(); if (FAILED(pInternalImport->GetTypeSpecFromToken(typeDefOrRefOrSpec, &pSig, &cSig))) { #ifndef DACCESS_COMPILE if (fNotFoundAction == ThrowIfNotFound) { pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, typeDefOrRefOrSpec, IDS_CLASSLOAD_BADFORMAT); } #endif //!DACCESS_COMPILE RETURN (TypeHandle()); } SigPointer sigptr(pSig, cSig); TypeHandle typeHnd = sigptr.GetTypeHandleThrowing(pModule, pTypeContext, fLoadTypes, level, dropGenericArgumentLevel, pSubst); #ifndef DACCESS_COMPILE if ((fNotFoundAction == ThrowIfNotFound) && typeHnd.IsNull()) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, typeDefOrRefOrSpec, IDS_CLASSLOAD_GENERAL); #endif RETURN (typeHnd); } else { RETURN (LoadTypeDefOrRefThrowing(pModule, typeDefOrRefOrSpec, fNotFoundAction, fUninstantiated, ((fLoadTypes == LoadTypes) ? tdNoTypes : tdAllTypes), level)); } } // ClassLoader::LoadTypeDefOrRefOrSpecThrowing // Given a token specifying a typeDef, and a module in which to // interpret that token, find or load the corresponding type handle. // // /*static*/ TypeHandle ClassLoader::LoadTypeDefThrowing(Module *pModule, mdToken typeDef, NotFoundAction fNotFoundAction /* = ThrowIfNotFound */ , PermitUninstantiatedFlag fUninstantiated /* = FailIfUninstDefOrRef */, mdToken tokenNotToLoad, ClassLoadLevel level, Instantiation * pTargetInstantiation) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } DAC_LOADS_TYPE(level, !NameHandle::OKToLoad(typeDef, tokenNotToLoad)); PRECONDITION(CheckPointer(pModule)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(FORBIDGC_LOADER_USE_ENABLED() || GetAppDomain()->CheckCanLoadTypes(pModule->GetAssembly())); POSTCONDITION(CheckPointer(RETVAL, NameHandle::OKToLoad(typeDef, tokenNotToLoad) && (fNotFoundAction == ThrowIfNotFound) ? NULL_NOT_OK : NULL_OK)); POSTCONDITION(RETVAL.IsNull() || RETVAL.GetCl() == typeDef); SUPPORTS_DAC; } CONTRACT_END; TypeHandle typeHnd; // First, attempt to find the class if it is already loaded ClassLoadLevel existingLoadLevel = CLASS_LOAD_BEGIN; typeHnd = pModule->LookupTypeDef(typeDef, &existingLoadLevel); if (!typeHnd.IsNull()) { #ifndef DACCESS_COMPILE // If the type is loaded, we can do cheap arity verification if (pTargetInstantiation != NULL && pTargetInstantiation->GetNumArgs() != typeHnd.AsMethodTable()->GetNumGenericArgs()) pModule->GetAssembly()->ThrowTypeLoadException(pModule->GetMDImport(), typeDef, IDS_CLASSLOAD_TYPEWRONGNUMGENERICARGS); #endif if (existingLoadLevel >= level) RETURN(typeHnd); } // We don't want to probe on any threads except for those with a managed thread. This function // can be called from the GC thread etc. so need to control how we probe. INTERIOR_STACK_PROBE_NOTHROW_CHECK_THREAD(goto Exit;); IMDInternalImport *pInternalImport = pModule->GetMDImport(); #ifndef DACCESS_COMPILE if (typeHnd.IsNull() && pTargetInstantiation != NULL) { // If the type is not loaded yet, we have to do heavy weight arity verification based on metadata HENUMInternal hEnumGenericPars; HRESULT hr = pInternalImport->EnumInit(mdtGenericParam, typeDef, &hEnumGenericPars); if (FAILED(hr)) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, typeDef, IDS_CLASSLOAD_BADFORMAT); DWORD nGenericClassParams = pInternalImport->EnumGetCount(&hEnumGenericPars); pInternalImport->EnumClose(&hEnumGenericPars); if (pTargetInstantiation->GetNumArgs() != nGenericClassParams) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, typeDef, IDS_CLASSLOAD_TYPEWRONGNUMGENERICARGS); } #endif if (IsNilToken(typeDef) || TypeFromToken(typeDef) != mdtTypeDef || !pInternalImport->IsValidToken(typeDef) ) { LOG((LF_CLASSLOADER, LL_INFO10, "Bogus class token to load: 0x%08x\n", typeDef)); typeHnd = TypeHandle(); } else { // ***************************************************************************** // // Important invariant: // // The rule here is that we never go to LoadTypeHandleForTypeKey if a Find should succeed. // This is vital, because otherwise a stack crawl will open up opportunities for // GC. Since operations like setting up a GCFrame will trigger a crawl in stress // mode, a GC at that point would be disastrous. We can't assert this, because // of race conditions. (In other words, the type could suddently be find-able // because another thread loaded it while we were in this method. // Not found - try to load it unless we are told not to #ifndef DACCESS_COMPILE if ( !NameHandle::OKToLoad(typeDef, tokenNotToLoad) ) { typeHnd = TypeHandle(); } else { // Anybody who puts himself in a FORBIDGC_LOADER state has promised // to use us only for resolving, not loading. We are now transitioning into // loading. #ifdef _DEBUG_IMPL _ASSERTE(!FORBIDGC_LOADER_USE_ENABLED()); #endif TRIGGERSGC(); if (pModule->IsReflection()) { //if (!(pModule->IsIntrospectionOnly())) { // Don't try to load types that are not in available table, when this // is an in-memory module. Raise the type-resolve event instead. typeHnd = TypeHandle(); // Avoid infinite recursion if (tokenNotToLoad != tdAllAssemblies) { AppDomain* pDomain = SystemDomain::GetCurrentDomain(); LPUTF8 pszFullName; LPCUTF8 className; LPCUTF8 nameSpace; if (FAILED(pInternalImport->GetNameOfTypeDef(typeDef, &className, &nameSpace))) { LOG((LF_CLASSLOADER, LL_INFO10, "Bogus TypeDef record while loading: 0x%08x\n", typeDef)); typeHnd = TypeHandle(); } else { MAKE_FULL_PATH_ON_STACK_UTF8(pszFullName, nameSpace, className); GCX_COOP(); ASSEMBLYREF asmRef = NULL; DomainAssembly *pDomainAssembly = NULL; GCPROTECT_BEGIN(asmRef); pDomainAssembly = pDomain->RaiseTypeResolveEventThrowing( pModule->GetAssembly()->GetDomainAssembly(), pszFullName, &asmRef); if (asmRef != NULL) { _ASSERTE(pDomainAssembly != NULL); if (pDomainAssembly->GetAssembly()->GetLoaderAllocator()->IsCollectible()) { if (!pModule->GetLoaderAllocator()->IsCollectible()) { LOG((LF_CLASSLOADER, LL_INFO10, "Bad result from TypeResolveEvent while loader TypeDef record: 0x%08x\n", typeDef)); COMPlusThrow(kNotSupportedException, W("NotSupported_CollectibleBoundNonCollectible")); } pModule->GetLoaderAllocator()->EnsureReference(pDomainAssembly->GetAssembly()->GetLoaderAllocator()); } } GCPROTECT_END(); if (pDomainAssembly != NULL) { Assembly *pAssembly = pDomainAssembly->GetAssembly(); NameHandle name(nameSpace, className); name.SetTypeToken(pModule, typeDef); name.SetTokenNotToLoad(tdAllAssemblies); typeHnd = pAssembly->GetLoader()->LoadTypeHandleThrowing(&name, level); } } } } } else { TypeKey typeKey(pModule, typeDef); typeHnd = pModule->GetClassLoader()->LoadTypeHandleForTypeKey(&typeKey, typeHnd, level); } } #endif // !DACCESS_COMPILE } // If stack guards are disabled, then this label is unreferenced and produces a compile error. #if defined(FEATURE_STACK_PROBE) && !defined(DACCESS_COMPILE) Exit: #endif #ifndef DACCESS_COMPILE if ((fUninstantiated == FailIfUninstDefOrRef) && !typeHnd.IsNull() && typeHnd.IsGenericTypeDefinition()) { typeHnd = TypeHandle(); } if ((fNotFoundAction == ThrowIfNotFound) && typeHnd.IsNull() && (tokenNotToLoad != tdAllTypes)) { pModule->GetAssembly()->ThrowTypeLoadException(pModule->GetMDImport(), typeDef, IDS_CLASSLOAD_GENERAL); } #endif ; END_INTERIOR_STACK_PROBE; RETURN(typeHnd); } // Given a token specifying a typeDef or typeRef, and a module in // which to interpret that token, find or load the corresponding type // handle. // /*static*/ TypeHandle ClassLoader::LoadTypeDefOrRefThrowing(Module *pModule, mdToken typeDefOrRef, NotFoundAction fNotFoundAction /* = ThrowIfNotFound */ , PermitUninstantiatedFlag fUninstantiated /* = FailIfUninstDefOrRef */, mdToken tokenNotToLoad, ClassLoadLevel level) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } PRECONDITION(CheckPointer(pModule)); PRECONDITION(level > CLASS_LOAD_BEGIN && level <= CLASS_LOADED); PRECONDITION(FORBIDGC_LOADER_USE_ENABLED() || GetAppDomain()->CheckCanLoadTypes(pModule->GetAssembly())); POSTCONDITION(CheckPointer(RETVAL, NameHandle::OKToLoad(typeDefOrRef, tokenNotToLoad) && (fNotFoundAction == ThrowIfNotFound) ? NULL_NOT_OK : NULL_OK)); POSTCONDITION(level <= CLASS_LOAD_UNRESTORED || RETVAL.IsNull() || RETVAL.IsRestored()); SUPPORTS_DAC; } CONTRACT_END; // NotFoundAction could be the bizarre 'ThrowButNullV11McppWorkaround', // which means ThrowIfNotFound EXCEPT if this might be the Everett MCPP // Nil-token ResolutionScope for value type. In that case, it means // ReturnNullIfNotFound. // If we have ThrowButNullV11McppWorkaround, remember that NULL *might* // be OK if there is no resolution scope, but change the value to // ThrowIfNotFound. BOOLEAN bReturnNullOkWhenNoResolutionScope = false; if (fNotFoundAction == ThrowButNullV11McppWorkaround) { bReturnNullOkWhenNoResolutionScope = true; fNotFoundAction = ThrowIfNotFound; } // First, attempt to find the class if it is already loaded ClassLoadLevel existingLoadLevel = CLASS_LOAD_BEGIN; TypeHandle typeHnd = LookupTypeDefOrRefInModule(pModule, typeDefOrRef, &existingLoadLevel); if (!typeHnd.IsNull()) { if (existingLoadLevel < level) { pModule = typeHnd.GetModule(); typeDefOrRef = typeHnd.GetCl(); } } if (!typeHnd.IsNull() && existingLoadLevel >= level) { // perform the check that it's not an uninstantiated TypeDef/TypeRef // being used inappropriately. if (!((fUninstantiated == FailIfUninstDefOrRef) && !typeHnd.IsNull() && typeHnd.IsGenericTypeDefinition())) { RETURN(typeHnd); } } else { // otherwise try to resolve the TypeRef and/or load the corresponding TypeDef IMDInternalImport *pInternalImport = pModule->GetMDImport(); mdToken tokType = TypeFromToken(typeDefOrRef); if (IsNilToken(typeDefOrRef) || ((tokType != mdtTypeDef)&&(tokType != mdtTypeRef)) || !pInternalImport->IsValidToken(typeDefOrRef) ) { #ifdef _DEBUG LOG((LF_CLASSLOADER, LL_INFO10, "Bogus class token to load: 0x%08x\n", typeDefOrRef)); #endif typeHnd = TypeHandle(); } else if (tokType == mdtTypeRef) { BOOL fNoResolutionScope; Module *pFoundModule = Assembly::FindModuleByTypeRef(pModule, typeDefOrRef, tokenNotToLoad==tdAllTypes ? Loader::DontLoad : Loader::Load, &fNoResolutionScope); if (pFoundModule != NULL) { // Not in my module, have to look it up by name. This is the primary path // taken by the TypeRef case, i.e. we've resolve a TypeRef to a TypeDef/Module // pair. LPCUTF8 pszNameSpace; LPCUTF8 pszClassName; if (FAILED(pInternalImport->GetNameOfTypeRef( typeDefOrRef, &pszNameSpace, &pszClassName))) { typeHnd = TypeHandle(); } else { if (fNoResolutionScope) { // Everett C++ compiler can generate a TypeRef with RS=0 // without respective TypeDef for unmanaged valuetypes, // referenced only by pointers to them, // so we can fail to load legally w/ no exception typeHnd = ClassLoader::LoadTypeByNameThrowing(pFoundModule->GetAssembly(), pszNameSpace, pszClassName, ClassLoader::ReturnNullIfNotFound, tokenNotToLoad==tdAllTypes ? ClassLoader::DontLoadTypes : ClassLoader::LoadTypes, level); if(typeHnd.IsNull() && bReturnNullOkWhenNoResolutionScope) { fNotFoundAction = ReturnNullIfNotFound; RETURN(typeHnd); } } else { NameHandle nameHandle(pModule, typeDefOrRef); nameHandle.SetName(pszNameSpace, pszClassName); nameHandle.SetTokenNotToLoad(tokenNotToLoad); typeHnd = pFoundModule->GetClassLoader()-> LoadTypeHandleThrowIfFailed(&nameHandle, level, pFoundModule->IsReflection() ? NULL : pFoundModule); } } #ifndef DACCESS_COMPILE if (!(typeHnd.IsNull())) pModule->StoreTypeRef(typeDefOrRef, typeHnd); #endif } } else { // This is the mdtTypeDef case... typeHnd = LoadTypeDefThrowing(pModule, typeDefOrRef, fNotFoundAction, fUninstantiated, tokenNotToLoad, level); } } TypeHandle thRes = typeHnd; // reject the load if it's an uninstantiated TypeDef/TypeRef // being used inappropriately. if ((fUninstantiated == FailIfUninstDefOrRef) && !typeHnd.IsNull() && typeHnd.IsGenericTypeDefinition()) thRes = TypeHandle(); // perform the check to throw when the thing is not found if ((fNotFoundAction == ThrowIfNotFound) && thRes.IsNull() && (tokenNotToLoad != tdAllTypes)) { #ifndef DACCESS_COMPILE pModule->GetAssembly()->ThrowTypeLoadException(pModule->GetMDImport(), typeDefOrRef, IDS_CLASSLOAD_GENERAL); #else DacNotImpl(); #endif } RETURN(thRes); } /*static*/ BOOL ClassLoader::ResolveTokenToTypeDefThrowing( Module * pTypeRefModule, mdTypeRef typeRefToken, Module ** ppTypeDefModule, mdTypeDef * pTypeDefToken, Loader::LoadFlag loadFlag, BOOL * pfUsesTypeForwarder) // The semantic of this parameter: TRUE if a type forwarder is found. It is never set to FALSE. { CONTRACT(BOOL) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } PRECONDITION(CheckPointer(pTypeRefModule)); SUPPORTS_DAC; } CONTRACT_END; // It's a TypeDef already if (TypeFromToken(typeRefToken) == mdtTypeDef) { if (ppTypeDefModule != NULL) *ppTypeDefModule = pTypeRefModule; if (pTypeDefToken != NULL) *pTypeDefToken = typeRefToken; RETURN TRUE; } TypeHandle typeHnd = pTypeRefModule->LookupTypeRef(typeRefToken); // Type is already (partially) loaded and cached in the module's TypeRef table // Do not return here if we are checking for type forwarders if (!typeHnd.IsNull() && (pfUsesTypeForwarder == NULL)) { if (ppTypeDefModule != NULL) *ppTypeDefModule = typeHnd.GetModule(); if (pTypeDefToken != NULL) *pTypeDefToken = typeHnd.GetCl(); RETURN TRUE; } BOOL fNoResolutionScope; //not used Module * pFoundRefModule = Assembly::FindModuleByTypeRef( pTypeRefModule, typeRefToken, loadFlag, &fNoResolutionScope); if (pFoundRefModule == NULL) { // We didn't find the TypeRef anywhere RETURN FALSE; } // If checking for type forwarders, then we can see if a type forwarder was used based on the output of // pFoundRefModule and typeHnd (if typeHnd is set) if (!typeHnd.IsNull() && (pfUsesTypeForwarder != NULL)) { if (typeHnd.GetModule() != pFoundRefModule) { *pfUsesTypeForwarder = TRUE; } if (ppTypeDefModule != NULL) *ppTypeDefModule = typeHnd.GetModule(); if (pTypeDefToken != NULL) *pTypeDefToken = typeHnd.GetCl(); RETURN TRUE; } // Not in my module, have to look it up by name LPCUTF8 pszNameSpace; LPCUTF8 pszClassName; if (FAILED(pTypeRefModule->GetMDImport()->GetNameOfTypeRef(typeRefToken, &pszNameSpace, &pszClassName))) { RETURN FALSE; } NameHandle nameHandle(pTypeRefModule, typeRefToken); nameHandle.SetName(pszNameSpace, pszClassName); if (loadFlag != Loader::Load) { nameHandle.SetTokenNotToLoad(tdAllTypes); } return ResolveNameToTypeDefThrowing(pFoundRefModule, &nameHandle, ppTypeDefModule, pTypeDefToken, loadFlag, pfUsesTypeForwarder); } /*static*/ BOOL ClassLoader::ResolveNameToTypeDefThrowing( Module * pModule, NameHandle * pName, Module ** ppTypeDefModule, mdTypeDef * pTypeDefToken, Loader::LoadFlag loadFlag, BOOL * pfUsesTypeForwarder) // The semantic of this parameter: TRUE if a type forwarder is found. It is never set to FALSE. { CONTRACT(BOOL) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; MODE_ANY; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } PRECONDITION(CheckPointer(pModule)); PRECONDITION(CheckPointer(pName)); SUPPORTS_DAC; } CONTRACT_END; TypeHandle typeHnd; mdToken foundTypeDef; Module * pFoundModule; mdExportedType foundExportedType; Module * pSourceModule = pModule; Module * pFoundRefModule = pModule; for (UINT32 nTypeForwardingChainSize = 0; nTypeForwardingChainSize < const_cMaxTypeForwardingChainSize; nTypeForwardingChainSize++) { foundTypeDef = mdTokenNil; pFoundModule = NULL; foundExportedType = mdTokenNil; if (!pSourceModule->GetClassLoader()->FindClassModuleThrowing( pName, &typeHnd, &foundTypeDef, &pFoundModule, &foundExportedType, NULL, pSourceModule->IsReflection() ? NULL : pSourceModule, loadFlag)) { RETURN FALSE; } // Type is already loaded and cached in the loader's by-name table if (!typeHnd.IsNull()) { if ((typeHnd.GetModule() != pFoundRefModule) && (pfUsesTypeForwarder != NULL)) { // We followed at least one type forwarder to resolve the type *pfUsesTypeForwarder = TRUE; } if (ppTypeDefModule != NULL) *ppTypeDefModule = typeHnd.GetModule(); if (pTypeDefToken != NULL) *pTypeDefToken = typeHnd.GetCl(); RETURN TRUE; } if (pFoundModule == NULL) { // Module was probably not loaded RETURN FALSE; } if (TypeFromToken(foundExportedType) != mdtExportedType) { // It's not exported type _ASSERTE(foundExportedType == mdTokenNil); if ((pFoundModule != pFoundRefModule) && (pfUsesTypeForwarder != NULL)) { // We followed at least one type forwarder to resolve the type *pfUsesTypeForwarder = TRUE; } if (pTypeDefToken != NULL) *pTypeDefToken = foundTypeDef; if (ppTypeDefModule != NULL) *ppTypeDefModule = pFoundModule; RETURN TRUE; } // It's exported type // Repeat the search for the type in the newly found module pSourceModule = pFoundModule; } // Type forwarding chain is too long RETURN FALSE; } // ClassLoader::ResolveTokenToTypeDefThrowing #ifndef DACCESS_COMPILE //--------------------------------------------------------------------------------------- // //static VOID ClassLoader::GetEnclosingClassThrowing( IMDInternalImport * pInternalImport, Module * pModule, mdTypeDef cl, mdTypeDef * tdEnclosing) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM()); MODE_ANY; } CONTRACTL_END; _ASSERTE(tdEnclosing); *tdEnclosing = mdTypeDefNil; HRESULT hr = pInternalImport->GetNestedClassProps(cl, tdEnclosing); if (FAILED(hr)) { if (hr != CLDB_E_RECORD_NOTFOUND) COMPlusThrowHR(hr); return; } if (TypeFromToken(*tdEnclosing) != mdtTypeDef) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_ENCLOSING); } // ClassLoader::GetEnclosingClassThrowing //--------------------------------------------------------------------------------------- // // Load a parent type or implemented interface type. // // If this is an instantiated type represented by a type spec, then instead of attempting to load the // exact type, load an approximate instantiation in which all reference types are replaced by Object. // The exact instantiated types will be loaded later by LoadInstantiatedInfo. // We do this to avoid cycles early in class loading caused by definitions such as // struct M : ICloneable // load ICloneable // class C : D,int> for any T // load D // //static TypeHandle ClassLoader::LoadApproxTypeThrowing( Module * pModule, mdToken tok, SigPointer * pSigInst, const SigTypeContext * pClassTypeContext) { CONTRACT(TypeHandle) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM()); MODE_ANY; PRECONDITION(CheckPointer(pSigInst, NULL_OK)); PRECONDITION(CheckPointer(pModule)); POSTCONDITION(CheckPointer(RETVAL)); } CONTRACT_END; IMDInternalImport * pInternalImport = pModule->GetMDImport(); if (TypeFromToken(tok) == mdtTypeSpec) { ULONG cSig; PCCOR_SIGNATURE pSig; IfFailThrowBF(pInternalImport->GetTypeSpecFromToken(tok, &pSig, &cSig), BFA_METADATA_CORRUPT, pModule); SigPointer sigptr = SigPointer(pSig, cSig); CorElementType type = ELEMENT_TYPE_END; IfFailThrowBF(sigptr.GetElemType(&type), BFA_BAD_SIGNATURE, pModule); // The only kind of type specs that we recognise are instantiated types if (type != ELEMENT_TYPE_GENERICINST) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, tok, IDS_CLASSLOAD_GENERAL); // Of these, we outlaw instantiated value classes (they can't be interfaces and can't be subclassed) IfFailThrowBF(sigptr.GetElemType(&type), BFA_BAD_SIGNATURE, pModule); if (type != ELEMENT_TYPE_CLASS) pModule->GetAssembly()->ThrowTypeLoadException(pInternalImport, tok, IDS_CLASSLOAD_GENERAL); mdToken genericTok = 0; IfFailThrowBF(sigptr.GetToken(&genericTok), BFA_BAD_SIGNATURE, pModule); IfFailThrowBF(sigptr.GetData(NULL), BFA_BAD_SIGNATURE, pModule); if (pSigInst != NULL) *pSigInst = sigptr; // Try to load the generic type itself THROW_BAD_FORMAT_MAYBE( ((TypeFromToken(genericTok) == mdtTypeRef) || (TypeFromToken(genericTok) == mdtTypeDef)), BFA_UNEXPECTED_GENERIC_TOKENTYPE, pModule); TypeHandle genericTypeTH = LoadTypeDefOrRefThrowing( pModule, genericTok, ClassLoader::ThrowIfNotFound, ClassLoader::PermitUninstDefOrRef, tdNoTypes, CLASS_LOAD_APPROXPARENTS); // We load interfaces at very approximate types - the generic // interface itself. We fix this up in LoadInstantiatedInfo. // This allows us to load recursive interfaces on structs such // as "struct VC : I". The details of the interface // are not currently needed during the first phase // of setting up the method table. if (genericTypeTH.IsInterface()) { RETURN genericTypeTH; } else { // approxTypes, i.e. approximate reference types by Object, i.e. load the canonical type RETURN SigPointer(pSig, cSig).GetTypeHandleThrowing( pModule, pClassTypeContext, ClassLoader::LoadTypes, CLASS_LOAD_APPROXPARENTS, TRUE /*dropGenericArgumentLevel*/); } } else { if (pSigInst != NULL) *pSigInst = SigPointer(); RETURN LoadTypeDefOrRefThrowing( pModule, tok, ClassLoader::ThrowIfNotFound, ClassLoader::FailIfUninstDefOrRef, tdNoTypes, CLASS_LOAD_APPROXPARENTS); } } // ClassLoader::LoadApproxTypeThrowing //--------------------------------------------------------------------------------------- // //static MethodTable * ClassLoader::LoadApproxParentThrowing( Module * pModule, mdToken cl, SigPointer * pParentInst, const SigTypeContext * pClassTypeContext) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM()); MODE_ANY; } CONTRACTL_END; mdTypeRef crExtends; MethodTable * pParentMethodTable = NULL; TypeHandle parentType; DWORD dwAttrClass; Assembly * pAssembly = pModule->GetAssembly(); IMDInternalImport * pInternalImport = pModule->GetMDImport(); // Initialize the return value; *pParentInst = SigPointer(); // Now load all dependencies of this class if (FAILED(pInternalImport->GetTypeDefProps( cl, &dwAttrClass, // AttrClass &crExtends))) { pAssembly->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_BADFORMAT); } if (RidFromToken(crExtends) != mdTokenNil) { // Do an "approximate" load of the parent, replacing reference types in the instantiation by Object // This is to avoid cycles in the loader e.g. on class C : D or class C : D> // We fix up the exact parent later in LoadInstantiatedInfo parentType = LoadApproxTypeThrowing(pModule, crExtends, pParentInst, pClassTypeContext); pParentMethodTable = parentType.GetMethodTable(); if (pParentMethodTable == NULL) pAssembly->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_PARENTNULL); // cannot inherit from an interface if (pParentMethodTable->IsInterface()) pAssembly->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_PARENTINTERFACE); if (IsTdInterface(dwAttrClass)) { // Interfaces must extend from Object if (! pParentMethodTable->IsObjectClass()) pAssembly->ThrowTypeLoadException(pInternalImport, cl, IDS_CLASSLOAD_INTERFACEOBJECT); } } return pParentMethodTable; } // ClassLoader::LoadApproxParentThrowing // Perform a single phase of class loading // It is the caller's responsibility to lock /*static*/ TypeHandle ClassLoader::DoIncrementalLoad(TypeKey *pTypeKey, TypeHandle typeHnd, ClassLoadLevel currentLevel) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pTypeKey)); PRECONDITION(currentLevel >= CLASS_LOAD_BEGIN && currentLevel < CLASS_LOADED); MODE_ANY; } CONTRACTL_END; #ifdef _DEBUG if (LoggingOn(LF_CLASSLOADER, LL_INFO10000)) { SString name; TypeString::AppendTypeKeyDebug(name, pTypeKey); LOG((LF_CLASSLOADER, LL_INFO10000, "PHASEDLOAD: About to do incremental load of type %S (%p) from level %s\n", name.GetUnicode(), typeHnd.AsPtr(), classLoadLevelName[currentLevel])); } #endif // Level is BEGIN if and only if type handle is null CONSISTENCY_CHECK((currentLevel == CLASS_LOAD_BEGIN) == typeHnd.IsNull()); switch (currentLevel) { // Attain at least level CLASS_LOAD_UNRESTORED (if just locating type in ngen image) // or at least level CLASS_LOAD_APPROXPARENTS (if creating type for the first time) case CLASS_LOAD_BEGIN : { IBCLoggerAwareAllocMemTracker amTracker; typeHnd = CreateTypeHandleForTypeKey(pTypeKey, &amTracker); CONSISTENCY_CHECK(!typeHnd.IsNull()); TypeHandle published = PublishType(pTypeKey, typeHnd); if (published == typeHnd) amTracker.SuppressRelease(); typeHnd = published; } break; case CLASS_LOAD_UNRESTOREDTYPEKEY : #ifdef FEATURE_PREJIT typeHnd.DoRestoreTypeKey(); #endif break; // Attain level CLASS_LOAD_APPROXPARENTS, starting with unrestored class case CLASS_LOAD_UNRESTORED : #ifdef FEATURE_PREJIT { CONSISTENCY_CHECK(!typeHnd.IsRestored_NoLogging()); if (typeHnd.IsTypeDesc()) typeHnd.AsTypeDesc()->Restore(); else typeHnd.AsMethodTable()->Restore(); } #endif break; // Attain level CLASS_LOAD_EXACTPARENTS case CLASS_LOAD_APPROXPARENTS : if (!typeHnd.IsTypeDesc()) { LoadExactParents(typeHnd.AsMethodTable()); } break; case CLASS_LOAD_EXACTPARENTS : case CLASS_DEPENDENCIES_LOADED : case CLASS_LOADED : break; } if (typeHnd.GetLoadLevel() >= CLASS_LOAD_EXACTPARENTS) { Notify(typeHnd); } return typeHnd; } /*static*/ // For non-canonical instantiations of generic types, create a fresh type by replicating the canonical instantiation // For canonical instantiations of generic types, create a brand new method table // For other constructed types, create a type desc and template method table if necessary // For all other types, create a method table TypeHandle ClassLoader::CreateTypeHandleForTypeKey(TypeKey* pKey, AllocMemTracker* pamTracker) { CONTRACT(TypeHandle) { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pKey)); POSTCONDITION(RETVAL.CheckMatchesKey(pKey)); MODE_ANY; } CONTRACT_END TypeHandle typeHnd = TypeHandle(); if (!pKey->IsConstructed()) { typeHnd = CreateTypeHandleForTypeDefThrowing(pKey->GetModule(), pKey->GetTypeToken(), pKey->GetInstantiation(), pamTracker); } else if (pKey->HasInstantiation()) { #ifdef FEATURE_FULL_NGEN // Try to find the type in an NGEN'd image. typeHnd = TryFindDynLinkZapType(pKey); if (!typeHnd.IsNull()) { #ifdef _DEBUG if (LoggingOn(LF_CLASSLOADER, LL_INFO10000)) { SString name; TypeString::AppendTypeKeyDebug(name, pKey); LOG((LF_CLASSLOADER, LL_INFO10000, "GENERICS:CreateTypeHandleForTypeKey: found dyn-link ngen type %S with pointer %p in module %S\n", name.GetUnicode(), typeHnd.AsPtr(), typeHnd.GetLoaderModule()->GetDebugName())); } #endif if (typeHnd.GetLoadLevel() == CLASS_LOAD_UNRESTOREDTYPEKEY) { OVERRIDE_TYPE_LOAD_LEVEL_LIMIT(CLASS_LOADED); typeHnd.DoRestoreTypeKey(); } } else #endif // FEATURE_FULL_NGEN { if (IsCanonicalGenericInstantiation(pKey->GetInstantiation())) { typeHnd = CreateTypeHandleForTypeDefThrowing(pKey->GetModule(), pKey->GetTypeToken(), pKey->GetInstantiation(), pamTracker); } else { typeHnd = CreateTypeHandleForNonCanonicalGenericInstantiation(pKey, pamTracker); } #if defined(_DEBUG) && !defined(CROSSGEN_COMPILE) if (Nullable::IsNullableType(typeHnd)) Nullable::CheckFieldOffsets(typeHnd); #endif } } else if (pKey->GetKind() == ELEMENT_TYPE_FNPTR) { Module *pLoaderModule = ComputeLoaderModule(pKey); pLoaderModule->GetLoaderAllocator()->EnsureInstantiation(NULL, Instantiation(pKey->GetRetAndArgTypes(), pKey->GetNumArgs() + 1)); PREFIX_ASSUME(pLoaderModule!=NULL); DWORD numArgs = pKey->GetNumArgs(); BYTE* mem = (BYTE*) pamTracker->Track(pLoaderModule->GetAssembly()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(FnPtrTypeDesc)) + S_SIZE_T(sizeof(TypeHandle)) * S_SIZE_T(numArgs))); typeHnd = TypeHandle(new(mem) FnPtrTypeDesc(pKey->GetCallConv(), numArgs, pKey->GetRetAndArgTypes())); } else { Module *pLoaderModule = ComputeLoaderModule(pKey); PREFIX_ASSUME(pLoaderModule!=NULL); CorElementType kind = pKey->GetKind(); TypeHandle paramType = pKey->GetElementType(); MethodTable *templateMT; // Create a new type descriptor and insert into constructed type table if (CorTypeInfo::IsArray(kind)) { DWORD rank = pKey->GetRank(); THROW_BAD_FORMAT_MAYBE((kind != ELEMENT_TYPE_ARRAY) || rank > 0, BFA_MDARRAY_BADRANK, pLoaderModule); THROW_BAD_FORMAT_MAYBE((kind != ELEMENT_TYPE_SZARRAY) || rank == 1, BFA_SDARRAY_BADRANK, pLoaderModule); // Arrays of BYREFS not allowed if (paramType.GetInternalCorElementType() == ELEMENT_TYPE_BYREF) { ThrowTypeLoadException(pKey, IDS_CLASSLOAD_CANTCREATEARRAYCLASS); } // Arrays of ByRefLike types not allowed MethodTable* pMT = paramType.GetMethodTable(); if (pMT != NULL) { if (pMT->IsByRefLike()) { ThrowTypeLoadException(pKey, IDS_CLASSLOAD_CANTCREATEARRAYCLASS); } } // We really don't need this check anymore. if (rank > MAX_RANK) { ThrowTypeLoadException(pKey, IDS_CLASSLOAD_RANK_TOOLARGE); } templateMT = pLoaderModule->CreateArrayMethodTable(paramType, kind, rank, pamTracker); BYTE* mem = (BYTE*) pamTracker->Track(pLoaderModule->GetAssembly()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(ArrayTypeDesc)))); typeHnd = TypeHandle(new(mem) ArrayTypeDesc(templateMT, paramType)); } else { // no parameterized type allowed on a reference if (paramType.GetInternalCorElementType() == ELEMENT_TYPE_BYREF || paramType.GetInternalCorElementType() == ELEMENT_TYPE_TYPEDBYREF) { ThrowTypeLoadException(pKey, IDS_CLASSLOAD_GENERAL); } // We do allow parametrized types of ByRefLike types. Languages may restrict them to produce safe or verifiable code, // but there is not a good reason for restricting them in the runtime. // let * type have a method table // System.UIntPtr's method table is used for types like int*, void *, string * etc. if (kind == ELEMENT_TYPE_PTR) templateMT = MscorlibBinder::GetElementType(ELEMENT_TYPE_U); else templateMT = NULL; BYTE* mem = (BYTE*) pamTracker->Track(pLoaderModule->GetAssembly()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(ParamTypeDesc)))); typeHnd = TypeHandle(new(mem) ParamTypeDesc(kind, templateMT, paramType)); } } RETURN typeHnd; } // Publish a type (and possibly member information) in the loader's // tables Types are published before they are fully loaded. In // particular, exact parent info (base class and interfaces) is loaded // in a later phase /*static*/ TypeHandle ClassLoader::PublishType(TypeKey *pTypeKey, TypeHandle typeHnd) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(typeHnd)); PRECONDITION(CheckPointer(pTypeKey)); // Key must match that of the handle PRECONDITION(typeHnd.CheckMatchesKey(pTypeKey)); // Don't publish array template method tables; these are accessed only through type descs PRECONDITION(typeHnd.IsTypeDesc() || !typeHnd.AsMethodTable()->IsArray()); } CONTRACTL_END; if (pTypeKey->IsConstructed()) { Module *pLoaderModule = ComputeLoaderModule(pTypeKey); EETypeHashTable *pTable = pLoaderModule->GetAvailableParamTypes(); // m_AvailableTypesLock has to be taken in cooperative mode to avoid deadlocks during GC GCX_COOP(); CrstHolder ch(&pLoaderModule->GetClassLoader()->m_AvailableTypesLock); // The type could have been loaded by a different thread as side-effect of avoiding deadlocks caused by LoadsTypeViolation TypeHandle existing = pTable->GetValue(pTypeKey); if (!existing.IsNull()) return existing; pTable->InsertValue(typeHnd); #ifdef _DEBUG // Checks to help ensure that the CoreLib in the ngen process does not get contaminated with pointers to the compilation domains. if (pLoaderModule->IsSystem() && IsCompilationProcess() && pLoaderModule->HasNativeImage()) { CorElementType kind = pTypeKey->GetKind(); MethodTable *typeHandleMethodTable = typeHnd.GetMethodTable(); if ((typeHandleMethodTable != NULL) && (typeHandleMethodTable->GetLoaderAllocator() != pLoaderModule->GetLoaderAllocator())) { _ASSERTE(!"MethodTable of type loaded into CoreLib during NGen is not from CoreLib!"); } if ((kind != ELEMENT_TYPE_FNPTR) && (kind != ELEMENT_TYPE_VAR) && (kind != ELEMENT_TYPE_MVAR)) { if ((kind == ELEMENT_TYPE_SZARRAY) || (kind == ELEMENT_TYPE_ARRAY) || (kind == ELEMENT_TYPE_BYREF) || (kind == ELEMENT_TYPE_PTR) || (kind == ELEMENT_TYPE_VALUETYPE)) { // Check to ensure param value is also part of CoreLib. if (pTypeKey->GetElementType().GetLoaderAllocator() != pLoaderModule->GetLoaderAllocator()) { _ASSERTE(!"Param value of type key used to load type during NGEN not located within CoreLib yet type is placed into CoreLib"); } } else if (kind == ELEMENT_TYPE_FNPTR) { // Check to ensure the parameter types of fnptr are in CoreLib for (DWORD i = 0; i <= pTypeKey->GetNumArgs(); i++) { if (pTypeKey->GetRetAndArgTypes()[i].GetLoaderAllocator() != pLoaderModule->GetLoaderAllocator()) { _ASSERTE(!"Ret or Arg type of function pointer type key used to load type during NGEN not located within CoreLib yet type is placed into CoreLib"); } } } else if (kind == ELEMENT_TYPE_CLASS) { // Check to ensure that the generic parameters are all within CoreLib for (DWORD i = 0; i < pTypeKey->GetNumGenericArgs(); i++) { if (pTypeKey->GetInstantiation()[i].GetLoaderAllocator() != pLoaderModule->GetLoaderAllocator()) { _ASSERTE(!"Instantiation parameter of generic class type key used to load type during NGEN not located within CoreLib yet type is placed into CoreLib"); } } } else { // Should not be able to get here _ASSERTE(!"Unknown type key type"); } } } #endif // DEBUG } else { Module *pModule = pTypeKey->GetModule(); mdTypeDef typeDef = pTypeKey->GetTypeToken(); // m_AvailableTypesLock has to be taken in cooperative mode to avoid deadlocks during GC GCX_COOP(); CrstHolder ch(&pModule->GetClassLoader()->m_AvailableTypesLock); // ! We cannot fail after this point. CANNOTTHROWCOMPLUSEXCEPTION(); FAULT_FORBID(); // The type could have been loaded by a different thread as side-effect of avoiding deadlocks caused by LoadsTypeViolation TypeHandle existing = pModule->LookupTypeDef(typeDef); if (!existing.IsNull()) return existing; MethodTable *pMT = typeHnd.AsMethodTable(); MethodTable::IntroducedMethodIterator it(pMT); for (; it.IsValid(); it.Next()) { MethodDesc * pMD = it.GetMethodDesc(); CONSISTENCY_CHECK(pMD != NULL && pMD->GetMethodTable() == pMT); if (!pMD->IsUnboxingStub()) { pModule->EnsuredStoreMethodDef(pMD->GetMemberDef(), pMD); } } ApproxFieldDescIterator fdIterator(pMT, ApproxFieldDescIterator::ALL_FIELDS); FieldDesc* pFD; while ((pFD = fdIterator.Next()) != NULL) { if (pFD->GetEnclosingMethodTable() == pMT) { pModule->EnsuredStoreFieldDef(pFD->GetMemberDef(), pFD); } } // Publish the type last - to ensure that nobody can see it until all the method and field RID maps are filled in pModule->EnsuredStoreTypeDef(typeDef, typeHnd); } return typeHnd; } // Notify profiler and debugger that a type load has completed // Also adjust perf counters /*static*/ void ClassLoader::Notify(TypeHandle typeHnd) { CONTRACTL { STANDARD_VM_CHECK; PRECONDITION(CheckPointer(typeHnd)); } CONTRACTL_END; LOG((LF_CLASSLOADER, LL_INFO1000, "Notify: %p %s\n", typeHnd.AsPtr(), typeHnd.IsTypeDesc() ? "typedesc" : typeHnd.AsMethodTable()->GetDebugClassName())); if (typeHnd.IsTypeDesc()) return; MethodTable * pMT = typeHnd.AsMethodTable(); #ifdef PROFILING_SUPPORTED { BEGIN_PIN_PROFILER(CORProfilerTrackClasses()); // We don't tell profilers about typedescs, as per IF above. Also, we don't // tell profilers about: if ( // ...generics with unbound variables (!pMT->ContainsGenericVariables()) && // ...or array method tables // (This check is mainly for NGEN restore, as JITted code won't hit // this code path for array method tables anyway) (!pMT->IsArray())) { LOG((LF_CLASSLOADER, LL_INFO1000, "Notifying profiler of Started1 %p %s\n", pMT, pMT->GetDebugClassName())); // Record successful load of the class for the profiler g_profControlBlock.pProfInterface->ClassLoadStarted(TypeHandleToClassID(typeHnd)); // // Profiler can turn off TrackClasses during the Started() callback. Need to // retest the flag here. // if (CORProfilerTrackClasses()) { LOG((LF_CLASSLOADER, LL_INFO1000, "Notifying profiler of Finished1 %p %s\n", pMT, pMT->GetDebugClassName())); g_profControlBlock.pProfInterface->ClassLoadFinished(TypeHandleToClassID(typeHnd), S_OK); } } END_PIN_PROFILER(); } #endif //PROFILING_SUPPORTED g_IBCLogger.LogMethodTableAccess(pMT); if (pMT->IsTypicalTypeDefinition()) { LOG((LF_CLASSLOADER, LL_INFO100, "Successfully loaded class %s\n", pMT->GetDebugClassName())); #ifdef DEBUGGING_SUPPORTED { Module * pModule = pMT->GetModule(); // Update metadata for dynamic module. pModule->UpdateDynamicMetadataIfNeeded(); } if (CORDebuggerAttached()) { LOG((LF_CORDB, LL_EVERYTHING, "NotifyDebuggerLoad clsload 2239 class %s\n", pMT->GetDebugClassName())); typeHnd.NotifyDebuggerLoad(NULL, FALSE); } #endif // DEBUGGING_SUPPORTED #if defined(ENABLE_PERF_COUNTERS) GetPerfCounters().m_Loading.cClassesLoaded ++; #endif } } //----------------------------------------------------------------------------- // Common helper for LoadTypeHandleForTypeKey and LoadTypeHandleForTypeKeyNoLock. // Makes the root level call to kick off the transitive closure walk for // the final level pushes. //----------------------------------------------------------------------------- static void PushFinalLevels(TypeHandle typeHnd, ClassLoadLevel targetLevel, const InstantiationContext *pInstContext) { CONTRACTL { STANDARD_VM_CHECK; LOADS_TYPE(targetLevel); } CONTRACTL_END // This phase brings the type and all its transitive dependencies to their // final state, sans the IsFullyLoaded bit. if (targetLevel >= CLASS_DEPENDENCIES_LOADED) { BOOL fBailed = FALSE; typeHnd.DoFullyLoad(NULL, CLASS_DEPENDENCIES_LOADED, NULL, &fBailed, pInstContext); } // This phase does access/constraint and other type-safety checks on the type // and on its transitive dependencies. if (targetLevel == CLASS_LOADED) { DFLPendingList pendingList; BOOL fBailed = FALSE; typeHnd.DoFullyLoad(NULL, CLASS_LOADED, &pendingList, &fBailed, pInstContext); // In the case of a circular dependency, one or more types will have // had their promotions deferred. // // If we got to this point, all checks have successfully passed on // the transitive closure (otherwise, DoFullyLoad would have thrown.) // // So we can go ahead and mark everyone as fully loaded. // UINT numTH = pendingList.Count(); TypeHandle *pTHPending = pendingList.Table(); for (UINT i = 0; i < numTH; i++) { // NOTE: It is possible for duplicates to appear in this list so // don't do any operation that isn't idempodent. pTHPending[i].SetIsFullyLoaded(); } } } // TypeHandle ClassLoader::LoadTypeHandleForTypeKey(TypeKey *pTypeKey, TypeHandle typeHnd, ClassLoadLevel targetLevel/*=CLASS_LOADED*/, const InstantiationContext *pInstContext/*=NULL*/) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; LOADS_TYPE(targetLevel); } CONTRACTL_END GCX_PREEMP(); // Type loading can be recursive. Probe for sufficient stack. // // Execution of the FINALLY in LoadTypeHandleForTypeKey_Body can eat // a lot of stack because LoadTypeHandleForTypeKey_Inner can rethrow // any non-SO exceptions that it takes, ensure that we have plenty // of stack before getting into it (>24 pages on AMD64, remember // that num pages probed is 2*N on AMD64). INTERIOR_STACK_PROBE_FOR(GetThread(),20); #ifdef _DEBUG if (LoggingOn(LF_CLASSLOADER, LL_INFO1000)) { SString name; TypeString::AppendTypeKeyDebug(name, pTypeKey); LOG((LF_CLASSLOADER, LL_INFO10000, "PHASEDLOAD: LoadTypeHandleForTypeKey for type %S to level %s\n", name.GetUnicode(), classLoadLevelName[targetLevel])); CrstHolder unresolvedClassLockHolder(&m_UnresolvedClassLock); m_pUnresolvedClassHash->Dump(); } #endif // When using domain neutral assemblies (and not eagerly propagating dependency loads), // it's possible to get here without having injected the module into the current app domain. // GetDomainFile will accomplish that. if (!pTypeKey->IsConstructed()) { pTypeKey->GetModule()->GetDomainFile(); } ClassLoadLevel currentLevel = typeHnd.IsNull() ? CLASS_LOAD_BEGIN : typeHnd.GetLoadLevel(); ClassLoadLevel targetLevelUnderLock = targetLevel < CLASS_DEPENDENCIES_LOADED ? targetLevel : (ClassLoadLevel) (CLASS_DEPENDENCIES_LOADED-1); if (currentLevel < targetLevelUnderLock) { typeHnd = LoadTypeHandleForTypeKey_Body(pTypeKey, typeHnd, targetLevelUnderLock); _ASSERTE(!typeHnd.IsNull()); } _ASSERTE(typeHnd.GetLoadLevel() >= targetLevelUnderLock); PushFinalLevels(typeHnd, targetLevel, pInstContext); END_INTERIOR_STACK_PROBE; return typeHnd; } // TypeHandle ClassLoader::LoadTypeHandleForTypeKeyNoLock(TypeKey *pTypeKey, ClassLoadLevel targetLevel/*=CLASS_LOADED*/, const InstantiationContext *pInstContext/*=NULL*/) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; LOADS_TYPE(targetLevel); PRECONDITION(CheckPointer(pTypeKey)); PRECONDITION(targetLevel >= 0 && targetLevel <= CLASS_LOADED); } CONTRACTL_END GCX_PREEMP(); TypeHandle typeHnd = TypeHandle(); // Type loading can be recursive. Probe for sufficient stack. INTERIOR_STACK_PROBE_FOR(GetThread(),8); ClassLoadLevel currentLevel = CLASS_LOAD_BEGIN; ClassLoadLevel targetLevelUnderLock = targetLevel < CLASS_DEPENDENCIES_LOADED ? targetLevel : (ClassLoadLevel) (CLASS_DEPENDENCIES_LOADED-1); while (currentLevel < targetLevelUnderLock) { typeHnd = DoIncrementalLoad(pTypeKey, typeHnd, currentLevel); CONSISTENCY_CHECK(typeHnd.GetLoadLevel() > currentLevel); currentLevel = typeHnd.GetLoadLevel(); } PushFinalLevels(typeHnd, targetLevel, pInstContext); END_INTERIOR_STACK_PROBE; return typeHnd; } //--------------------------------------------------------------------------------------- // class PendingTypeLoadHolder { Thread * m_pThread; PendingTypeLoadEntry * m_pEntry; PendingTypeLoadHolder * m_pPrevious; public: PendingTypeLoadHolder(PendingTypeLoadEntry * pEntry) { LIMITED_METHOD_CONTRACT; m_pThread = GetThread(); m_pEntry = pEntry; m_pPrevious = m_pThread->GetPendingTypeLoad(); m_pThread->SetPendingTypeLoad(this); } ~PendingTypeLoadHolder() { LIMITED_METHOD_CONTRACT; _ASSERTE(m_pThread->GetPendingTypeLoad() == this); m_pThread->SetPendingTypeLoad(m_pPrevious); } static bool CheckForDeadLockOnCurrentThread(PendingTypeLoadEntry * pEntry) { LIMITED_METHOD_CONTRACT; PendingTypeLoadHolder * pCurrent = GetThread()->GetPendingTypeLoad(); while (pCurrent != NULL) { if (pCurrent->m_pEntry == pEntry) return true; pCurrent = pCurrent->m_pPrevious; } return false; } }; //--------------------------------------------------------------------------------------- // TypeHandle ClassLoader::LoadTypeHandleForTypeKey_Body( TypeKey * pTypeKey, TypeHandle typeHnd, ClassLoadLevel targetLevel) { CONTRACT(TypeHandle) { STANDARD_VM_CHECK; POSTCONDITION(!typeHnd.IsNull() && typeHnd.GetLoadLevel() >= targetLevel); } CONTRACT_END if (!pTypeKey->IsConstructed()) { Module *pModule = pTypeKey->GetModule(); mdTypeDef cl = pTypeKey->GetTypeToken(); STRESS_LOG2(LF_CLASSLOADER, LL_INFO100000, "LoadTypeHandle: Loading Class from Module %p token %x\n", pModule, cl); #ifdef _DEBUG IMDInternalImport* pInternalImport = pModule->GetMDImport(); LPCUTF8 className; LPCUTF8 nameSpace; if (FAILED(pInternalImport->GetNameOfTypeDef(cl, &className, &nameSpace))) { className = nameSpace = "Invalid TypeDef record"; } if (g_pConfig->ShouldBreakOnClassLoad(className)) CONSISTENCY_CHECK_MSGF(false, ("BreakOnClassLoad: typename '%s' ", className)); #endif } ReleaseHolder pLoadingEntry; CrstHolderWithState unresolvedClassLockHolder(&m_UnresolvedClassLock, false); retry: unresolvedClassLockHolder.Acquire(); // Is it in the hash of classes currently being loaded? pLoadingEntry = m_pUnresolvedClassHash->GetValue(pTypeKey); if (pLoadingEntry) { pLoadingEntry->AddRef(); // It is in the hash, which means that another thread is waiting for it (or that we are // already loading this class on this thread, which should never happen, since that implies // a recursive dependency). unresolvedClassLockHolder.Release(); // // Check one last time before waiting that the type handle is not sufficiently loaded to // prevent deadlocks // { if (typeHnd.IsNull()) { typeHnd = LookupTypeHandleForTypeKey(pTypeKey); } if (!typeHnd.IsNull()) { if (typeHnd.GetLoadLevel() >= targetLevel) RETURN typeHnd; } } if (PendingTypeLoadHolder::CheckForDeadLockOnCurrentThread(pLoadingEntry)) { // Attempting recursive load ClassLoader::ThrowTypeLoadException(pTypeKey, IDS_CLASSLOAD_GENERAL); } // // Violation of type loadlevel ordering rules depends on type load failing in case of cyclic dependency that would // otherwise lead to deadlock. We will speculatively proceed with the type load to make it fail in the right spot, // in backward compatible way. In case the type load succeeds, we will only let one type win in PublishType. // if (typeHnd.IsNull() && GetThread()->HasThreadStateNC(Thread::TSNC_LoadsTypeViolation)) { PendingTypeLoadHolder ptlh(pLoadingEntry); typeHnd = DoIncrementalLoad(pTypeKey, TypeHandle(), CLASS_LOAD_BEGIN); goto retry; } { // Wait for class to be loaded by another thread. This is where we start tracking the // entry, so there is an implicit Acquire in our use of Assign here. CrstHolder loadingEntryLockHolder(&pLoadingEntry->m_Crst); _ASSERTE(pLoadingEntry->HasLock()); } // Result of other thread loading the class HRESULT hr = pLoadingEntry->m_hrResult; if (FAILED(hr)) { // // Redo the lookup one more time and return a valid type if possible. The other thread could // have hit error while loading the type to higher level than we need. // { if (typeHnd.IsNull()) { typeHnd = LookupTypeHandleForTypeKey(pTypeKey); } if (!typeHnd.IsNull()) { if (typeHnd.GetLoadLevel() >= targetLevel) RETURN typeHnd; } } if (hr == E_ABORT) { LOG((LF_CLASSLOADER, LL_INFO10, "need to retry LoadTypeHandle: %x\n", hr)); goto retry; } LOG((LF_CLASSLOADER, LL_INFO10, "Failed to load in other entry: %x\n", hr)); if (hr == E_OUTOFMEMORY) { COMPlusThrowOM(); } pLoadingEntry->ThrowException(); } // Get a pointer to the EEClass being loaded typeHnd = pLoadingEntry->m_typeHandle; if (!typeHnd.IsNull()) { // If the type load on the other thread loaded the type to the needed level, return it here. if (typeHnd.GetLoadLevel() >= targetLevel) RETURN typeHnd; } // The type load on the other thread did not load the type "enough". Begin the type load // process again to cause us to load to the needed level. goto retry; } if (typeHnd.IsNull()) { // The class was not being loaded. However, it may have already been loaded after our // first LoadTypeHandleThrowIfFailed() and before taking the lock. typeHnd = LookupTypeHandleForTypeKey(pTypeKey); } ClassLoadLevel currentLevel = CLASS_LOAD_BEGIN; if (!typeHnd.IsNull()) { currentLevel = typeHnd.GetLoadLevel(); if (currentLevel >= targetLevel) RETURN typeHnd; } // It was not loaded, and it is not being loaded, so we must load it. Create a new LoadingEntry // and acquire it immediately so that other threads will block. pLoadingEntry = new PendingTypeLoadEntry(*pTypeKey, typeHnd); // this atomically creates a crst and acquires it if (!(m_pUnresolvedClassHash->InsertValue(pLoadingEntry))) { COMPlusThrowOM(); } // Leave the global lock, so that other threads may now start waiting on our class's lock unresolvedClassLockHolder.Release(); EX_TRY { PendingTypeLoadHolder ptlh(pLoadingEntry); TRIGGERS_TYPELOAD(); while (currentLevel < targetLevel) { typeHnd = DoIncrementalLoad(pTypeKey, typeHnd, currentLevel); CONSISTENCY_CHECK(typeHnd.GetLoadLevel() > currentLevel); currentLevel = typeHnd.GetLoadLevel(); // If other threads are waiting for this load, unblock them as soon as possible to prevent deadlocks. if (pLoadingEntry->HasWaiters()) break; } _ASSERTE(!typeHnd.IsNull()); pLoadingEntry->SetResult(typeHnd); } EX_HOOK { LOG((LF_CLASSLOADER, LL_INFO10, "Caught an exception loading: %x, %0x (Module)\n", pTypeKey->GetTypeToken(), pTypeKey->GetModule())); if (!GetThread()->HasThreadStateNC(Thread::TSNC_LoadsTypeViolation)) { // Fix up the loading entry. Exception *pException = GET_EXCEPTION(); pLoadingEntry->SetException(pException); } // Unlink this class from the unresolved class list. unresolvedClassLockHolder.Acquire(); m_pUnresolvedClassHash->DeleteValue(pTypeKey); // Release the lock before proceeding. The unhandled exception filters take number of locks that // have ordering violations with this lock. unresolvedClassLockHolder.Release(); // Unblock any thread waiting to load same type as in TypeLoadEntry pLoadingEntry->UnblockWaiters(); } EX_END_HOOK; // Unlink this class from the unresolved class list. unresolvedClassLockHolder.Acquire(); m_pUnresolvedClassHash->DeleteValue(pTypeKey); unresolvedClassLockHolder.Release(); // Unblock any thread waiting to load same type as in TypeLoadEntry. This should be done // after pLoadingEntry is removed from m_pUnresolvedClassHash. Otherwise the other thread // (which was waiting) will keep spinning for a while after waking up, till the current thread removes // pLoadingEntry from m_pUnresolvedClassHash. This can cause hang in situation when the current // thread is a background thread and so will get very less processor cycle to perform subsequent // operations to remove the entry from hash later. pLoadingEntry->UnblockWaiters(); if (currentLevel < targetLevel) goto retry; RETURN typeHnd; } // ClassLoader::LoadTypeHandleForTypeKey_Body #endif //!DACCESS_COMPILE //--------------------------------------------------------------------------------------- // //static TypeHandle ClassLoader::LoadArrayTypeThrowing( TypeHandle elemType, CorElementType arrayKind, unsigned rank, //=0 LoadTypesFlag fLoadTypes, //=LoadTypes ClassLoadLevel level) { CONTRACT(TypeHandle) { if (FORBIDGC_LOADER_USE_ENABLED()) NOTHROW; else THROWS; if (FORBIDGC_LOADER_USE_ENABLED()) GC_NOTRIGGER; else GC_TRIGGERS; if (FORBIDGC_LOADER_USE_ENABLED()) FORBID_FAULT; else { INJECT_FAULT(COMPlusThrowOM()); } if (FORBIDGC_LOADER_USE_ENABLED() || fLoadTypes != LoadTypes) { LOADS_TYPE(CLASS_LOAD_BEGIN); } else { LOADS_TYPE(level); } if (fLoadTypes == DontLoadTypes) SO_TOLERANT; else SO_INTOLERANT; MODE_ANY; SUPPORTS_DAC; POSTCONDITION(CheckPointer(RETVAL, ((fLoadTypes == LoadTypes) ? NULL_NOT_OK : NULL_OK))); } CONTRACT_END CorElementType predefinedElementType = ELEMENT_TYPE_END; // Try finding it in our cache of primitive SD arrays if (arrayKind == ELEMENT_TYPE_SZARRAY) { predefinedElementType = elemType.GetSignatureCorElementType(); if (predefinedElementType <= ELEMENT_TYPE_R8) { ArrayTypeDesc* typeDesc = g_pPredefinedArrayTypes[predefinedElementType]; if (typeDesc != 0) RETURN(TypeHandle(typeDesc)); } // This call to AsPtr is somewhat bogus and only used // as an optimization. If the TypeHandle is really a TypeDesc // then the equality checks for the optimizations below will // fail. Thus ArrayMT should not be used elsewhere in this function else if (elemType.AsPtr() == PTR_VOID(g_pObjectClass)) { // Code duplicated because Object[]'s SigCorElementType is E_T_CLASS, not OBJECT ArrayTypeDesc* typeDesc = g_pPredefinedArrayTypes[ELEMENT_TYPE_OBJECT]; if (typeDesc != 0) RETURN(TypeHandle(typeDesc)); predefinedElementType = ELEMENT_TYPE_OBJECT; } else if (elemType.AsPtr() == PTR_VOID(g_pStringClass)) { // Code duplicated because String[]'s SigCorElementType is E_T_CLASS, not STRING ArrayTypeDesc* typeDesc = g_pPredefinedArrayTypes[ELEMENT_TYPE_STRING]; if (typeDesc != 0) RETURN(TypeHandle(typeDesc)); predefinedElementType = ELEMENT_TYPE_STRING; } else { predefinedElementType = ELEMENT_TYPE_END; } rank = 1; } #ifndef DACCESS_COMPILE // To avoid loading useless shared instantiations, normalize shared instantiations to the canonical form // (e.g. List<_Canon>[] -> _Canon[]) // The denormalized shared instantiations should be needed only during JITing, so it is fine to skip this // for DACCESS_COMPILE. if (elemType.IsCanonicalSubtype()) { elemType = ClassLoader::CanonicalizeGenericArg(elemType); } #endif TypeKey key(arrayKind, elemType, FALSE, rank); TypeHandle th = LoadConstructedTypeThrowing(&key, fLoadTypes, level); if (predefinedElementType != ELEMENT_TYPE_END && !th.IsNull() && th.IsFullyLoaded()) { g_pPredefinedArrayTypes[predefinedElementType] = th.AsArray(); } RETURN(th); } // ClassLoader::LoadArrayTypeThrowing #ifndef DACCESS_COMPILE VOID ClassLoader::AddAvailableClassDontHaveLock(Module *pModule, mdTypeDef classdef, AllocMemTracker *pamTracker) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END #ifdef FEATURE_COMINTEROP _ASSERTE(!pModule->GetAssembly()->IsWinMD()); // WinMD files should never get into this path, otherwise provide szWinRtNamespacePrefix #endif CrstHolder ch(&m_AvailableClassLock); AddAvailableClassHaveLock( pModule, classdef, pamTracker, NULL, // szWinRtNamespacePrefix 0); // cchWinRtNamespacePrefix } // This routine must be single threaded! The reason is that there are situations which allow // the same class name to have two different mdTypeDef tokens (for example, we load two different DLLs // simultaneously, and they have some common class files, or we convert the same class file // simultaneously on two threads). The problem is that we do not want to overwrite the old // -> pModule mapping with the new one, because this may cause identity problems. // // This routine assumes you already have the lock. Use AddAvailableClassDontHaveLock() if you // don't have it. // // Also validates that TypeDef namespace begins with szWinRTNamespacePrefix (if it is not NULL). // The prefix should be NULL for normal non-WinRT .NET assemblies. // VOID ClassLoader::AddAvailableClassHaveLock( Module * pModule, mdTypeDef classdef, AllocMemTracker * pamTracker, LPCSTR szWinRtNamespacePrefix, DWORD cchWinRtNamespacePrefix) // Optimization for faster prefix comparison implementation { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END EEClassHashTable *pClassHash = pModule->GetAvailableClassHash(); EEClassHashTable *pClassCaseInsHash = pModule->GetAvailableClassCaseInsHash(); LPCUTF8 pszName; LPCUTF8 pszNameSpace; HashDatum ThrowawayData; IMDInternalImport *pMDImport = pModule->GetMDImport(); if (FAILED(pMDImport->GetNameOfTypeDef(classdef, &pszName, &pszNameSpace))) { pszName = pszNameSpace = "Invalid TypeDef token"; pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } EEClassHashEntry_t *pBucket; mdTypeDef enclosing; if (SUCCEEDED(pMDImport->GetNestedClassProps(classdef, &enclosing))) { // nested type LPCUTF8 pszEnclosingName; LPCUTF8 pszEnclosingNameSpace; mdTypeDef enclEnclosing; // Find this type's encloser's entry in the available table. // We'll save a pointer to it in the new hash entry for this type. BOOL fNestedEncl = SUCCEEDED(pMDImport->GetNestedClassProps(enclosing, &enclEnclosing)); EEClassHashTable::LookupContext sContext; if (FAILED(pMDImport->GetNameOfTypeDef(enclosing, &pszEnclosingName, &pszEnclosingNameSpace))) { pszName = pszNameSpace = "Invalid TypeDef token"; pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } if ((pBucket = pClassHash->GetValue(pszEnclosingNameSpace, pszEnclosingName, &ThrowawayData, fNestedEncl, &sContext)) != NULL) { if (fNestedEncl) { // Find entry for enclosing class - NOTE, this assumes that the // enclosing class's TypeDef or ExportedType was inserted previously, // which assumes that, when enuming TD's, we get the enclosing class first while ((!CompareNestedEntryWithTypeDef(pMDImport, enclEnclosing, pClassHash, pBucket->GetEncloser())) && (pBucket = pClassHash->FindNextNestedClass(pszEnclosingNameSpace, pszEnclosingName, &ThrowawayData, &sContext)) != NULL); } if (!pBucket) // Enclosing type not found in hash table pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_ENCLOSING_TYPE_NOT_FOUND); // In this hash table, if the lower bit is set, it means a Module, otherwise it means EEClass* ThrowawayData = EEClassHashTable::CompressClassDef(classdef); InsertValue(pClassHash, pClassCaseInsHash, pszNameSpace, pszName, ThrowawayData, pBucket, pamTracker); } } else { // Don't add duplicate top-level classes. Top-level classes are // added to the beginning of the bucket, while nested classes are // added to the end. So, a duplicate top-level class could hide // the previous type's EEClass* entry in the hash table. EEClassHashEntry_t *pCaseInsEntry = NULL; LPUTF8 pszLowerCaseNS = NULL; LPUTF8 pszLowerCaseName = NULL; if (pClassCaseInsHash) { CreateCanonicallyCasedKey(pszNameSpace, pszName, &pszLowerCaseNS, &pszLowerCaseName); pCaseInsEntry = pClassCaseInsHash->AllocNewEntry(pamTracker); } EEClassHashEntry_t *pEntry = pClassHash->FindItem(pszNameSpace, pszName, FALSE, NULL); if (pEntry) { HashDatum Data = pEntry->GetData(); if (((size_t)Data & EECLASSHASH_TYPEHANDLE_DISCR) && ((size_t)Data & EECLASSHASH_MDEXPORT_DISCR)) { // it's an ExportedType - check the 'already seen' bit and if on, report a class loading exception // otherwise, set it if ((size_t)Data & EECLASSHASH_ALREADYSEEN) pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_MULT_TYPE_SAME_NAME); else { Data = (HashDatum)((size_t)Data | EECLASSHASH_ALREADYSEEN); pEntry->SetData(Data); } } else { // We want to throw an exception for a duplicate typedef. // However, this used to be allowed in 1.0/1.1, and some third-party DLLs have // been obfuscated so that they have duplicate private typedefs. // We must allow this for old assemblies for app compat reasons pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_MULT_TYPE_SAME_NAME); } } else { pEntry = pClassHash->AllocNewEntry(pamTracker); CANNOTTHROWCOMPLUSEXCEPTION(); FAULT_FORBID(); pClassHash->InsertValueUsingPreallocatedEntry(pEntry, pszNameSpace, pszName, EEClassHashTable::CompressClassDef(classdef), NULL); if (pClassCaseInsHash) pClassCaseInsHash->InsertValueUsingPreallocatedEntry(pCaseInsEntry, pszLowerCaseNS, pszLowerCaseName, pEntry, pEntry->GetEncloser()); } #ifdef FEATURE_COMINTEROP // Check WinRT namespace prefix if required if (szWinRtNamespacePrefix != NULL) { DWORD dwAttr; if (FAILED(pMDImport->GetTypeDefProps(classdef, &dwAttr, NULL))) { pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } // Check only public WinRT types that are not nested (i.e. only types available for binding, excluding NoPIA) if (IsTdPublic(dwAttr) && IsTdWindowsRuntime(dwAttr)) { // Guaranteed by the caller - code:ClassLoader::PopulateAvailableClassHashTable _ASSERTE(cchWinRtNamespacePrefix == strlen(szWinRtNamespacePrefix)); // Now make sure namespace is, or begins with the namespace-prefix (note: 'MyN' should not match namespace 'MyName') // Note: Case insensitive comparison function has to be in sync with Win8 implementation // (ExtractExactCaseNamespaceSegmentFromMetadataFile in com\WinRT\WinTypes\TypeResolution\NamespaceResolution.cpp) BOOL fIsNamespaceSubstring = (pszNameSpace != NULL) && ((strncmp(pszNameSpace, szWinRtNamespacePrefix, cchWinRtNamespacePrefix) == 0) || (_strnicmp(pszNameSpace, szWinRtNamespacePrefix, cchWinRtNamespacePrefix) == 0)); BOOL fIsSubNamespace = fIsNamespaceSubstring && ((pszNameSpace[cchWinRtNamespacePrefix] == '\0') || (pszNameSpace[cchWinRtNamespacePrefix] == '.')); if (!fIsSubNamespace) { pModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_WINRT_INVALID_NAMESPACE_FOR_TYPE); } } } #endif // FEATURE_COMINTEROP } } VOID ClassLoader::AddExportedTypeDontHaveLock(Module *pManifestModule, mdExportedType cl, AllocMemTracker *pamTracker) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END CrstHolder ch(&m_AvailableClassLock); AddExportedTypeHaveLock( pManifestModule, cl, pamTracker); } VOID ClassLoader::AddExportedTypeHaveLock(Module *pManifestModule, mdExportedType cl, AllocMemTracker *pamTracker) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END mdToken mdImpl; LPCSTR pszName; LPCSTR pszNameSpace; IMDInternalImport* pAsmImport = pManifestModule->GetMDImport(); if (FAILED(pAsmImport->GetExportedTypeProps( cl, &pszNameSpace, &pszName, &mdImpl, NULL, // type def NULL))) // flags { pManifestModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } HashDatum ThrowawayData; if (TypeFromToken(mdImpl) == mdtExportedType) { // nested class LPCUTF8 pszEnclosingNameSpace; LPCUTF8 pszEnclosingName; mdToken nextImpl; if (FAILED(pAsmImport->GetExportedTypeProps( mdImpl, &pszEnclosingNameSpace, &pszEnclosingName, &nextImpl, NULL, // type def NULL))) // flags { pManifestModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } // Find entry for enclosing class - NOTE, this assumes that the // enclosing class's ExportedType was inserted previously, which assumes that, // when enuming ExportedTypes, we get the enclosing class first EEClassHashEntry_t *pBucket; EEClassHashTable::LookupContext sContext; if ((pBucket = pManifestModule->GetAvailableClassHash()->GetValue(pszEnclosingNameSpace, pszEnclosingName, &ThrowawayData, TypeFromToken(nextImpl) == mdtExportedType, &sContext)) != NULL) { do { // check to see if this is the correct class if (EEClassHashTable::UncompressModuleAndClassDef(ThrowawayData) == mdImpl) { ThrowawayData = EEClassHashTable::CompressClassDef(cl); // we explicitly don't check for the case insensitive hash table because we know it can't have been created yet pManifestModule->GetAvailableClassHash()->InsertValue(pszNameSpace, pszName, ThrowawayData, pBucket, pamTracker); } pBucket = pManifestModule->GetAvailableClassHash()->FindNextNestedClass(pszEnclosingNameSpace, pszEnclosingName, &ThrowawayData, &sContext); } while (pBucket); } // If the encloser is not in the hash table, this nested class // was defined in the manifest module, so it doesn't need to be added return; } else { // Defined in the manifest module - add to the hash table by TypeDef instead if (mdImpl == mdFileNil) return; // Don't add duplicate top-level classes // In this hash table, if the lower bit is set, it means a Module, otherwise it means EEClass* ThrowawayData = EEClassHashTable::CompressClassDef(cl); // ThrowawayData is an IN OUT param. Going in its the pointer to the new value if the entry needs // to be inserted. The OUT param points to the value stored in the hash table. BOOL bFound; pManifestModule->GetAvailableClassHash()->InsertValueIfNotFound(pszNameSpace, pszName, &ThrowawayData, NULL, FALSE, &bFound, pamTracker); if (bFound) { // Check for duplicate ExportedTypes // Let it slide if it's pointing to the same type mdToken foundTypeImpl; if ((size_t)ThrowawayData & EECLASSHASH_MDEXPORT_DISCR) { mdExportedType foundExportedType = EEClassHashTable::UncompressModuleAndClassDef(ThrowawayData); if (FAILED(pAsmImport->GetExportedTypeProps( foundExportedType, NULL, // namespace NULL, // name &foundTypeImpl, NULL, // TypeDef NULL))) // flags { pManifestModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_INVALID_TOKEN); } } else { foundTypeImpl = mdFileNil; } if (mdImpl != foundTypeImpl) { pManifestModule->GetAssembly()->ThrowBadImageException(pszNameSpace, pszName, BFA_MULT_TYPE_SAME_NAME); } } } } static MethodTable* GetEnclosingMethodTable(MethodTable *pMT) { CONTRACT(MethodTable*) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; PRECONDITION(CheckPointer(pMT)); POSTCONDITION(RETVAL == NULL || RETVAL->IsTypicalTypeDefinition()); } CONTRACT_END; MethodTable *pmtEnclosing = NULL; // In the common case, the method table will be either shared or in the AppDomain we're currently // running in. If this is true, we can just access its enclosing method table directly. // // However, if the current method table is actually in another AppDomain (for instance, we're reflecting // across AppDomains), then we cannot get its enclsoing type in our AppDomain since doing that may involve // loading the enclosing type. Instead, we need to transition back to the original domain (which we // should already be running in higher up on the stack) and get the method table we're looking for. if (pMT->GetDomain()->IsSharedDomain() || pMT->GetDomain()->AsAppDomain() == GetAppDomain()) { pmtEnclosing = pMT->LoadEnclosingMethodTable(); } else { GCX_COOP(); ENTER_DOMAIN_PTR(pMT->GetDomain()->AsAppDomain(), ADV_RUNNINGIN); pmtEnclosing = pMT->LoadEnclosingMethodTable(); END_DOMAIN_TRANSITION; } RETURN pmtEnclosing; } StaticAccessCheckContext::StaticAccessCheckContext(MethodDesc* pCallerMethod) { CONTRACTL { LIMITED_METHOD_CONTRACT; PRECONDITION(CheckPointer(pCallerMethod)); } CONTRACTL_END; m_pCallerMethod = pCallerMethod; m_pCallerMT = m_pCallerMethod->GetMethodTable(); m_pCallerAssembly = m_pCallerMT->GetAssembly(); } StaticAccessCheckContext::StaticAccessCheckContext(MethodDesc* pCallerMethod, MethodTable* pCallerType) { CONTRACTL { LIMITED_METHOD_CONTRACT; PRECONDITION(CheckPointer(pCallerMethod, NULL_OK)); PRECONDITION(CheckPointer(pCallerType)); } CONTRACTL_END; m_pCallerMethod = pCallerMethod; m_pCallerMT = pCallerType; m_pCallerAssembly = pCallerType->GetAssembly(); } //****************************************************************************** // static AccessCheckOptions* AccessCheckOptions::s_pNormalAccessChecks; //****************************************************************************** void AccessCheckOptions::Startup() { STANDARD_VM_CONTRACT; s_pNormalAccessChecks = new AccessCheckOptions( AccessCheckOptions::kNormalAccessibilityChecks, NULL, FALSE, (MethodTable *)NULL); } //****************************************************************************** AccessCheckOptions::AccessCheckOptions( const AccessCheckOptions & templateOptions, BOOL throwIfTargetIsInaccessible) : m_pAccessContext(templateOptions.m_pAccessContext) { WRAPPER_NO_CONTRACT; Initialize( templateOptions.m_accessCheckType, throwIfTargetIsInaccessible, templateOptions.m_pTargetMT, templateOptions.m_pTargetMethod, templateOptions.m_pTargetField); } //****************************************************************************** // This function should only be called when normal accessibility is not possible. // It returns TRUE if the target can be accessed. // Otherwise, it either returns FALSE or throws an exception, depending on the value of throwIfTargetIsInaccessible. BOOL AccessCheckOptions::DemandMemberAccess(AccessCheckContext *pContext, MethodTable * pTargetMT, BOOL visibilityCheck) const { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(m_accessCheckType != kNormalAccessibilityChecks); PRECONDITION(CheckPointer(pContext)); } CONTRACTL_END; _ASSERTE(m_accessCheckType != kNormalAccessibilityChecks); if (NingenEnabled()) { // NinGen should always perform normal accessibility checks _ASSERTE(false); if (m_fThrowIfTargetIsInaccessible) { ThrowAccessException(pContext, pTargetMT, NULL); } return FALSE; } if (pTargetMT && pTargetMT->GetAssembly()->IsDisabledPrivateReflection()) { if (m_fThrowIfTargetIsInaccessible) { ThrowAccessException(pContext, pTargetMT, NULL); } return FALSE; } BOOL canAccessTarget = FALSE; #ifndef CROSSGEN_COMPILE // In CoreCLR kRestrictedMemberAccess means that one can access private/internal // classes/members in app code. if (m_accessCheckType != kMemberAccess && pTargetMT) { // We allow all transparency checks to succeed in LCG methods and reflection invocation. if (m_accessCheckType == kNormalAccessNoTransparency || m_accessCheckType == kRestrictedMemberAccessNoTransparency) return TRUE; } // Always allow interop (NULL) callers full access. if (pContext->IsCalledFromInterop()) return TRUE; // No Access if (m_fThrowIfTargetIsInaccessible) { ThrowAccessException(pContext, pTargetMT, NULL); } #endif // CROSSGEN_COMPILE return canAccessTarget; } //****************************************************************************** // pFailureMT - the MethodTable that we were trying to access. It can be null // if the failure is not because of a specific type. This will be a // a component of the instantiation of m_pTargetMT/m_pTargetMethod/m_pTargetField. void AccessCheckOptions::ThrowAccessException( AccessCheckContext* pContext, MethodTable* pFailureMT, /* = NULL */ Exception* pInnerException /* = NULL */) const { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pContext)); PRECONDITION(CheckPointer(pInnerException, NULL_OK)); PRECONDITION(m_fThrowIfTargetIsInaccessible); } CONTRACTL_END; GCX_COOP(); MethodDesc* pCallerMD = pContext->GetCallerMethod(); if (m_pTargetMT != NULL) { // If we know the specific type that caused the failure, display it. // Else display the whole type that we are trying to access. MethodTable * pMT = (pFailureMT != NULL) ? pFailureMT : m_pTargetMT; ThrowTypeAccessException(pContext, pMT, 0, pInnerException); } else if (m_pTargetMethod != NULL) { // If the caller and target method are non-null and the same, then this means that we're checking to see // if the method has access to itself in order to validate that it has access to its parameter types, // containing type, and return type. In this case, throw a more informative TypeAccessException to // describe the error that occurred (for instance, "this method doesn't have access to one of its // parameter types", rather than "this method doesn't have access to itself"). // We only want to do this if we know the exact type that caused the problem, otherwise fall back to // throwing the standard MethodAccessException. if (pCallerMD != NULL && m_pTargetMethod == pCallerMD && pFailureMT != NULL) { ThrowTypeAccessException(pContext, pFailureMT, 0, pInnerException); } else { ThrowMethodAccessException(pContext, m_pTargetMethod, 0, pInnerException); } } else { _ASSERTE(m_pTargetField != NULL); ThrowFieldAccessException(pContext, m_pTargetField, 0, pInnerException); } } //****************************************************************************** // This will do a security demand if appropriate. // If access is not possible, this will either throw an exception or return FALSE BOOL AccessCheckOptions::DemandMemberAccessOrFail(AccessCheckContext *pContext, MethodTable * pTargetMT, BOOL visibilityCheck) const { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; if (DoNormalAccessibilityChecks()) { if (pContext->GetCallerAssembly()->IgnoresAccessChecksTo(pTargetMT->GetAssembly())) { return TRUE; } if (m_fThrowIfTargetIsInaccessible) { ThrowAccessException(pContext, pTargetMT); } return FALSE; } return DemandMemberAccess(pContext, pTargetMT, visibilityCheck); } //****************************************************************************** // This should be called if access to the target is not possible. // This will either throw an exception or return FALSE. BOOL AccessCheckOptions::FailOrThrow(AccessCheckContext *pContext) const { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pContext)); } CONTRACTL_END; if (m_fThrowIfTargetIsInaccessible) { ThrowAccessException(pContext); } return FALSE; } void DECLSPEC_NORETURN ThrowFieldAccessException(AccessCheckContext* pContext, FieldDesc *pFD, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pContext)); PRECONDITION(CheckPointer(pFD)); } CONTRACTL_END; MethodDesc* pCallerMD = pContext->GetCallerMethod(); ThrowFieldAccessException(pCallerMD, pFD, messageID, pInnerException); } void DECLSPEC_NORETURN ThrowFieldAccessException(MethodDesc* pCallerMD, FieldDesc *pFD, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pCallerMD, NULL_OK)); PRECONDITION(CheckPointer(pFD)); } CONTRACTL_END; if (pCallerMD != NULL) { if (messageID == 0) { messageID = IDS_E_FIELDACCESS; } EX_THROW_WITH_INNER(EEFieldException, (pFD, pCallerMD, SString::Empty(), messageID), pInnerException); } else { EX_THROW_WITH_INNER(EEFieldException, (pFD), pInnerException); } } void DECLSPEC_NORETURN ThrowMethodAccessException(AccessCheckContext* pContext, MethodDesc *pCalleeMD, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pContext)); PRECONDITION(CheckPointer(pCalleeMD)); } CONTRACTL_END; MethodDesc* pCallerMD = pContext->GetCallerMethod(); ThrowMethodAccessException(pCallerMD, pCalleeMD, messageID, pInnerException); } void DECLSPEC_NORETURN ThrowMethodAccessException(MethodDesc* pCallerMD, MethodDesc *pCalleeMD, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pCallerMD, NULL_OK)); PRECONDITION(CheckPointer(pCalleeMD)); } CONTRACTL_END; if (pCallerMD != NULL) { if (messageID == 0) { messageID = IDS_E_METHODACCESS; } EX_THROW_WITH_INNER(EEMethodException, (pCalleeMD, pCallerMD, SString::Empty(), messageID), pInnerException); } else { EX_THROW_WITH_INNER(EEMethodException, (pCalleeMD), pInnerException); } } void DECLSPEC_NORETURN ThrowTypeAccessException(AccessCheckContext* pContext, MethodTable *pMT, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pContext)); PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; MethodDesc* pCallerMD = pContext->GetCallerMethod(); ThrowTypeAccessException(pCallerMD, pMT, messageID, pInnerException); } void DECLSPEC_NORETURN ThrowTypeAccessException(MethodDesc* pCallerMD, MethodTable *pMT, UINT messageID /* = 0 */, Exception *pInnerException /* = NULL */) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pCallerMD, NULL_OK)); PRECONDITION(CheckPointer(pMT)); } CONTRACTL_END; if (pCallerMD != NULL) { if (messageID == 0) { messageID = IDS_E_TYPEACCESS; } EX_THROW_WITH_INNER(EETypeAccessException, (pMT, pCallerMD, SString::Empty(), messageID), pInnerException); } else { EX_THROW_WITH_INNER(EETypeAccessException, (pMT), pInnerException); } } //--------------------------------------------------------------------------------------- // // Checks to see if access to a member with assembly visiblity is allowed. // // Arguments: // pAccessingAssembly - The assembly requesting access to the internal member // pTargetAssembly - The assembly which contains the target member // pOptionalTargetField - Internal field being accessed OR // pOptionalTargetMethod - Internal type being accessed OR // pOptionalTargetType - Internal type being accessed // // Return Value: // TRUE if pTargetAssembly is pAccessingAssembly, or if pTargetAssembly allows // pAccessingAssembly friend access to the target. FALSE otherwise. // static BOOL AssemblyOrFriendAccessAllowed(Assembly *pAccessingAssembly, Assembly *pTargetAssembly, FieldDesc *pOptionalTargetField, MethodDesc *pOptionalTargetMethod, MethodTable *pOptionalTargetType) { CONTRACTL { THROWS; GC_TRIGGERS; PRECONDITION(CheckPointer(pAccessingAssembly)); PRECONDITION(CheckPointer(pTargetAssembly)); PRECONDITION(pOptionalTargetField != NULL || pOptionalTargetMethod != NULL || pOptionalTargetType != NULL); PRECONDITION(pOptionalTargetField == NULL || pOptionalTargetMethod == NULL); } CONTRACTL_END; if (pAccessingAssembly == pTargetAssembly) { return TRUE; } if (pAccessingAssembly->IgnoresAccessChecksTo(pTargetAssembly)) { return TRUE; } else if (pOptionalTargetField != NULL) { return pTargetAssembly->GrantsFriendAccessTo(pAccessingAssembly, pOptionalTargetField); } else if (pOptionalTargetMethod != NULL) { return pTargetAssembly->GrantsFriendAccessTo(pAccessingAssembly, pOptionalTargetMethod); } else { return pTargetAssembly->GrantsFriendAccessTo(pAccessingAssembly, pOptionalTargetType); } } //****************************************************************************** // This function determines whether a target class is accessible from // some given class. /* static */ BOOL ClassLoader::CanAccessMethodInstantiation( // True if access is legal, false otherwise. AccessCheckContext* pContext, MethodDesc* pOptionalTargetMethod, // The desired method; if NULL, return TRUE (or) const AccessCheckOptions & accessCheckOptions) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; PRECONDITION(CheckPointer(pContext)); } CONTRACTL_END // If there is no target method just allow access. // NB: the caller may just be checking access to a field or class, so we allow for NULL. if (!pOptionalTargetMethod) return TRUE; // Is the desired target an instantiated generic method? if (pOptionalTargetMethod->HasMethodInstantiation()) { // check that the current class has access // to all of the instantiating classes. Instantiation inst = pOptionalTargetMethod->GetMethodInstantiation(); for (DWORD i = 0; i < inst.GetNumArgs(); i++) { TypeHandle th = inst[i]; MethodTable* pMT = th.GetMethodTableOfElementType(); // Either a TypeVarTypeDesc or a FnPtrTypeDesc. No access check needed. if (pMT == NULL) continue; if (!CanAccessClass( pContext, pMT, th.GetAssembly(), accessCheckOptions)) { return FALSE; } } // If we are here, the current class has access to all of the target's instantiating args, } return TRUE; } //****************************************************************************** // This function determines whether a target class is accessible from // some given class. // CanAccessClass does the following checks: // 1. Transparency check on the target class // 2. Recursively calls CanAccessClass on the generic arguments of the target class if it is generic. // 3. Visibility check on the target class, if the target class is nested, this will be translated // to a member access check on the enclosing type (calling CanAccess with appropriate dwProtection. // /* static */ BOOL ClassLoader::CanAccessClass( // True if access is legal, false otherwise. AccessCheckContext* pContext, // The caller context MethodTable* pTargetClass, // The desired target class. Assembly* pTargetAssembly, // Assembly containing the target class. const AccessCheckOptions & accessCheckOptions)// = TRUE { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; PRECONDITION(CheckPointer(pContext)); PRECONDITION(CheckPointer(pTargetClass)); } CONTRACTL_END // If there is no target class, allow access. // @todo: what does that mean? //if (!pTargetClass) // return TRUE; // Step 2: Recursively call CanAccessClass on the generic type arguments // Is the desired target a generic instantiation? if (pTargetClass->HasInstantiation()) { // Yes, so before going any further, check that the current class has access // to all of the instantiating classes. Instantiation inst = pTargetClass->GetInstantiation(); for (DWORD i = 0; i < inst.GetNumArgs(); i++) { TypeHandle th = inst[i]; MethodTable* pMT = th.GetMethodTableOfElementType(); // Either a TypeVarTypeDesc or a FnPtrTypeDesc. No access check needed. if (pMT == NULL) continue; if (!CanAccessClass( pContext, pMT, th.GetAssembly(), accessCheckOptions)) { // no need to call accessCheckOptions.DemandMemberAccessOrFail here because the base case in // CanAccessClass does that already return FALSE; } } // If we are here, the current class has access to all of the desired target's instantiating args. // Now, check whether the current class has access to the desired target itself. } // Step 3: Visibility Check if (!pTargetClass->GetClass()->IsNested()) { // a non-nested class can be either all public or accessible only from its own assembly (and friends). if (IsTdPublic(pTargetClass->GetClass()->GetProtection())) { return TRUE; } else { // Always allow interop callers full access. if (pContext->IsCalledFromInterop()) return TRUE; Assembly* pCurrentAssembly = pContext->GetCallerAssembly(); _ASSERTE(pCurrentAssembly != NULL); if (AssemblyOrFriendAccessAllowed(pCurrentAssembly, pTargetAssembly, NULL, NULL, pTargetClass)) { return TRUE; } else { return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetClass, TRUE /*visibilityCheck*/); } } } // If we are here, the desired target class is nested. Translate the type flags // to corresponding method access flags. We need to make a note if friend access was allowed to the // type being checked since we're not passing it directly to the recurisve call to CanAccess, and // instead are just passing in the dwProtectionFlags. DWORD dwProtection = pTargetClass->GetClass()->GetProtection(); switch(dwProtection) { case tdNestedPublic: dwProtection = mdPublic; break; case tdNestedFamily: dwProtection = mdFamily; break; case tdNestedPrivate: dwProtection = mdPrivate; break; case tdNestedFamORAssem: // If we can access the class because we have assembly or friend access, we have satisfied the // FamORAssem accessibility, so we we can simplify it down to public. Otherwise we require that // family access be allowed to grant access. case tdNestedFamANDAssem: // If we don't grant assembly or friend access to the target class, then there is no way we // could satisfy the FamANDAssem requirement. Otherwise, since we have satsified the Assm // portion, we only need to check for the Fam portion. case tdNestedAssembly: // If we don't grant assembly or friend access to the target class, and that class has assembly // protection, we can fail the request now. Otherwise we can check to make sure a public member // of the outer class is allowed, since we have satisfied the target's accessibility rules. // Always allow interop callers full access. if (pContext->IsCalledFromInterop()) return TRUE; if (AssemblyOrFriendAccessAllowed(pContext->GetCallerAssembly(), pTargetAssembly, NULL, NULL, pTargetClass)) dwProtection = (dwProtection == tdNestedFamANDAssem) ? mdFamily : mdPublic; else if (dwProtection == tdNestedFamORAssem) dwProtection = mdFamily; else return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetClass, TRUE /*visibilityCheck*/); break; default: THROW_BAD_FORMAT_MAYBE(!"Unexpected class visibility flag value", BFA_BAD_VISIBILITY, pTargetClass); } // The desired target class is nested, so translate the class access request into // a member access request. That is, if the current class is trying to access A::B, // check if it can access things in A with the visibility of B. // So, pass A as the desired target class and visibility of B within A as the member access // We've already done transparency check above. No need to do it again. return ClassLoader::CanAccess( pContext, GetEnclosingMethodTable(pTargetClass), pTargetAssembly, dwProtection, NULL, NULL, accessCheckOptions); } // BOOL ClassLoader::CanAccessClass() //****************************************************************************** // This is a front-end to CheckAccessMember that handles the nested class scope. If can't access // from the current point and are a nested class, then try from the enclosing class. // In addition to CanAccessMember, if the caller class doesn't have access to the caller, see if the enclosing class does. // /* static */ BOOL ClassLoader::CanAccess( // TRUE if access is allowed, FALSE otherwise. AccessCheckContext* pContext, // The caller context MethodTable* pTargetMT, // The class containing the desired target member. Assembly* pTargetAssembly, // Assembly containing that class. DWORD dwMemberAccess, // Member access flags of the desired target member (as method bits). MethodDesc* pOptionalTargetMethod, // The target method; NULL if the target is a not a method or // there is no need to check the method's instantiation. FieldDesc* pOptionalTargetField, // or The desired field; if NULL, return TRUE const AccessCheckOptions & accessCheckOptions) // = s_NormalAccessChecks { CONTRACT(BOOL) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(CheckPointer(pContext)); MODE_ANY; } CONTRACT_END; // Recursive: CanAccess->CheckAccessMember->CanAccessClass->CanAccess INTERIOR_STACK_PROBE(GetThread()); AccessCheckOptions accessCheckOptionsNoThrow(accessCheckOptions, FALSE); if (!CheckAccessMember(pContext, pTargetMT, pTargetAssembly, dwMemberAccess, pOptionalTargetMethod, pOptionalTargetField, // Suppress exceptions for nested classes since this is not a hard-failure, // and we can do additional checks accessCheckOptionsNoThrow)) { // If we're here, CheckAccessMember didn't allow access. BOOL canAccess = FALSE; // If the current class is nested, there may be an enclosing class that might have access // to the target. And if the pCurrentMT == NULL, the current class is global, and so there // is no enclosing class. MethodTable* pCurrentMT = pContext->GetCallerMT(); // if this is called from interop, the CheckAccessMember call above should have already succeeded. _ASSERTE(!pContext->IsCalledFromInterop()); BOOL isNestedClass = (pCurrentMT && pCurrentMT->GetClass()->IsNested()); if (isNestedClass) { // A nested class also has access to anything that the enclosing class does, so // recursively check whether the enclosing class can access the desired target member. MethodTable * pEnclosingMT = GetEnclosingMethodTable(pCurrentMT); StaticAccessCheckContext accessContext(pContext->GetCallerMethod(), pEnclosingMT, pContext->GetCallerAssembly()); // On failure, do not throw from inside this call since that will cause the exception message // to refer to the enclosing type. canAccess = ClassLoader::CanAccess( &accessContext, pTargetMT, pTargetAssembly, dwMemberAccess, pOptionalTargetMethod, pOptionalTargetField, accessCheckOptionsNoThrow); } if (!canAccess) { BOOL fail = accessCheckOptions.FailOrThrow(pContext); RETURN_FROM_INTERIOR_PROBE(fail); } } RETURN_FROM_INTERIOR_PROBE(TRUE); END_INTERIOR_STACK_PROBE; } // BOOL ClassLoader::CanAccess() //****************************************************************************** // This is the helper function for the corresponding CanAccess() // It does the following checks: // 1. CanAccessClass on pTargetMT // 2. CanAccessMethodInstantiation if the pOptionalTargetMethod is provided and is generic. // 3. Transparency check on pTargetMT, pOptionalTargetMethod and pOptionalTargetField. // 4. Visibility check on dwMemberAccess (on pTargetMT) /* static */ BOOL ClassLoader::CheckAccessMember( // TRUE if access is allowed, false otherwise. AccessCheckContext* pContext, MethodTable* pTargetMT, // The class containing the desired target member. Assembly* pTargetAssembly, // Assembly containing that class. DWORD dwMemberAccess, // Member access flags of the desired target member (as method bits). MethodDesc* pOptionalTargetMethod, // The target method; NULL if the target is a not a method or // there is no need to check the method's instantiation. FieldDesc* pOptionalTargetField, // target field, NULL if there is no Target field const AccessCheckOptions & accessCheckOptions ) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); PRECONDITION(CheckPointer(pContext)); MODE_ANY; } CONTRACTL_END // we're trying to access a member that is contained in the class pTargetClass, so need to // check if have access to pTargetClass itself from the current point before worry about // having access to the member within the class if (!CanAccessClass(pContext, pTargetMT, pTargetAssembly, accessCheckOptions)) { return FALSE; } // If we are trying to access a generic method, we have to ensure its instantiation is accessible. // Note that we need to perform transparency checks on the instantiation even if we have if (!CanAccessMethodInstantiation( pContext, pOptionalTargetMethod, accessCheckOptions)) { return FALSE; } // pOptionalTargetMethod and pOptionalTargetField can never be NULL at the same time. _ASSERTE(pOptionalTargetMethod == NULL || pOptionalTargetField == NULL); // Perform transparency checks // We don't need to do transparency check against pTargetMT here because // it was already done in CanAccessClass above. if (IsMdPublic(dwMemberAccess)) { return TRUE; } // Always allow interop callers full access. if (pContext->IsCalledFromInterop()) return TRUE; MethodTable* pCurrentMT = pContext->GetCallerMT(); if (IsMdPrivateScope(dwMemberAccess)) { if (pCurrentMT != NULL && pCurrentMT->GetModule() == pTargetMT->GetModule()) { return TRUE; } else { return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetMT, TRUE /*visibilityCheck*/); } } #ifdef _DEBUG if (pTargetMT == NULL && (IsMdFamORAssem(dwMemberAccess) || IsMdFamANDAssem(dwMemberAccess) || IsMdFamily(dwMemberAccess))) { THROW_BAD_FORMAT_MAYBE(!"Family flag is not allowed on global functions", BFA_FAMILY_ON_GLOBAL, pTargetMT); } #endif if (pTargetMT == NULL || IsMdAssem(dwMemberAccess) || IsMdFamORAssem(dwMemberAccess) || IsMdFamANDAssem(dwMemberAccess)) { // If the member has Assembly accessibility, grant access if the current // class is in the same assembly as the desired target member, or if the // desired target member's assembly grants friend access to the current // assembly. // @todo: What does it mean for the target class to be NULL? Assembly* pCurrentAssembly = pContext->GetCallerAssembly(); // pCurrentAssembly should never be NULL, unless we are called from interop, // in which case we should have already returned TRUE. _ASSERTE(pCurrentAssembly != NULL); const BOOL fAssemblyOrFriendAccessAllowed = AssemblyOrFriendAccessAllowed(pCurrentAssembly, pTargetAssembly, pOptionalTargetField, pOptionalTargetMethod, pTargetMT); if ((pTargetMT == NULL || IsMdAssem(dwMemberAccess) || IsMdFamORAssem(dwMemberAccess)) && fAssemblyOrFriendAccessAllowed) { return TRUE; } else if (IsMdFamANDAssem(dwMemberAccess) && !fAssemblyOrFriendAccessAllowed) { return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetMT, TRUE /*visibilityCheck*/); } } // Nested classes can access all members of the parent class. while(pCurrentMT != NULL) { //@GENERICSVER: if (pTargetMT->HasSameTypeDefAs(pCurrentMT)) return TRUE; if (IsMdPrivate(dwMemberAccess)) { if (!pCurrentMT->GetClass()->IsNested()) { return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetMT, TRUE /*visibilityCheck*/); } } else if (IsMdFamORAssem(dwMemberAccess) || IsMdFamily(dwMemberAccess) || IsMdFamANDAssem(dwMemberAccess)) { if (CanAccessFamily(pCurrentMT, pTargetMT)) { return TRUE; } } pCurrentMT = GetEnclosingMethodTable(pCurrentMT); } return accessCheckOptions.DemandMemberAccessOrFail(pContext, pTargetMT, TRUE /*visibilityCheck*/); } // The family check is actually in two parts (Partition I, 8.5.3.2). The first part: // // ...accessible to referents that support the same type // (i.e., an exact type and all of the types that inherit // from it). // // Translation: pCurrentClass must be the same type as pTargetClass or a derived class. (i.e. Derived // can access Base.protected but Unrelated cannot access Base.protected). // // The second part: // // For verifiable code (see Section 8.8), there is an additional // requirement that can require a runtime check: the reference // shall be made through an item whose exact type supports // the exact type of the referent. That is, the item whose // member is being accessed shall inherit from the type // performing the access. // // Translation: The C++ protected rule. For those unfamiliar, it means that: // if you have: // GrandChild : Child // and // Child : Parent // and // Parent { // protected: // int protectedField; // } // // Child::function(GrandChild * o) { // o->protectedField; //This access is legal. // } // // GrandChild:function2(Child * o) { // o->protectedField; //This access is illegal. // } // // The reason for this rule is that if you had: // Sibling : Parent // // Child::function3( Sibling * o ) { // o->protectedField; //This access is illegal // } // // This is intuitively correct. However, you need to prevent: // Child::function4( Sibling * o ) { // ((Parent*)o)->protectedField; // } // // Which means that you must access protected fields through a type that is yourself or one of your // derived types. //This checks the first part of the rule above. /* static */ BOOL ClassLoader::CanAccessFamily( MethodTable *pCurrentClass, MethodTable *pTargetClass) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; PRECONDITION(CheckPointer(pTargetClass)); } CONTRACTL_END _ASSERTE(pCurrentClass); _ASSERTE(pTargetClass); //Look to see if Current is a child of the Target. while (pCurrentClass) { MethodTable *pCurInstance = pCurrentClass; while (pCurInstance) { //This is correct. csc is incredibly lax about generics. Essentially if you are a subclass of //any type of generic it lets you access it. Since the standard is totally unclear, mirror that //behavior here. if (pCurInstance->HasSameTypeDefAs(pTargetClass)) { return TRUE; } pCurInstance = pCurInstance->GetParentMethodTable(); } ///Looking at 8.5.3, it looks like a protected member of a nested class in a parent type is also //accessible. pCurrentClass = GetEnclosingMethodTable(pCurrentClass); } return FALSE; } //If instance is an inner class, this also succeeds if the outer class conforms to 8.5.3.2. A nested class //is enclosed inside of the enclosing class' open type. So we need to ignore generic variables. That also //helps us with: /* class Base { protected int m_family; } class Derived : Base { class Inner { public int function(Derived d) { return d.m_family; } } } */ //Since the inner T is not the same T as the enclosing T (since accessing generic variables is a CLS rule, //not a CLI rule), we see that as a comparison between Derived and Derived. CanCastTo rejects that. //Instead we just check against the typedef of the two types. This ignores all generic parameters (formal //or not). BOOL CanAccessFamilyVerificationEnclosingHelper(MethodTable * pMTCurrentEnclosingClass, TypeHandle thInstanceClass) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END _ASSERTE(pMTCurrentEnclosingClass); if (thInstanceClass.IsGenericVariable()) { //In this case it is a TypeVarTypeDesc (i.e. T). If this access would be legal due to a //constraint: // /* public class My { public class Inner where U : My { public int foo(U u) { return u.field; } } protected int field; } */ //We need to find the generic class constraint. (The above is legal because U must be a My which makes this //legal by 8.5.3.2) // There may only be 1 class constraint on a generic parameter // Get the constraints on this generic variable // At most 1 of them is a class constraint. // That class constraint methodtable can go through the normal search for matching typedef logic below TypeVarTypeDesc *tyvar = thInstanceClass.AsGenericVariable(); DWORD numConstraints; TypeHandle *constraints = tyvar->GetConstraints(&numConstraints, CLASS_DEPENDENCIES_LOADED); if (constraints == NULL) { // If we did not find a class constraint, we cannot generate a methodtable to search for return FALSE; } else { for (DWORD i = 0; i < numConstraints; i++) { if (!constraints[i].IsInterface()) { // We have found the class constraint on this TypeVarTypeDesc // Recurse on the found class constraint. It is possible that this constraint may also be a TypeVarTypeDesc //class Outer4 //{ // protected int field; // // public class Inner where V:U where U : Outer4 // { // public int Method(V param) { return (++param.field); } // } //} return CanAccessFamilyVerificationEnclosingHelper(pMTCurrentEnclosingClass, constraints[i]); } } // If we did not find a class constraint, we cannot generate a methodtable to search for return FALSE; } } do { MethodTable * pAccessor = pMTCurrentEnclosingClass; //If thInstanceClass is a MethodTable, we should only be doing the TypeDef comparison (see //above). if (!thInstanceClass.IsTypeDesc()) { MethodTable *pInstanceMT = thInstanceClass.AsMethodTable(); // This is a CanCastTo implementation for classes, assuming we should ignore generic instantiation parameters. do { if (pAccessor->HasSameTypeDefAs(pInstanceMT)) return TRUE; pInstanceMT = pInstanceMT->GetParentMethodTable(); }while(pInstanceMT); } else { // Leave this logic in place for now, as I'm not fully confident it can't happen, and we are very close to RTM // This logic was originally written to handle TypeVarTypeDescs, but those are now handled above. _ASSERTE(FALSE); if (thInstanceClass.CanCastTo(TypeHandle(pAccessor))) return TRUE; } pMTCurrentEnclosingClass = GetEnclosingMethodTable(pMTCurrentEnclosingClass); }while(pMTCurrentEnclosingClass); return FALSE; } //This checks the verification only part of the rule above. //From the example above: // GrandChild::function2(Child * o) { // o->protectedField; //This access is illegal. // } // pCurrentClass is GrandChild and pTargetClass is Child. This check is completely unnecessary for statics, // but by legacy convention you can use GrandChild for pTargetClass in that case. BOOL ClassLoader::CanAccessFamilyVerification(TypeHandle thCurrentClass, TypeHandle thInstanceClass) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); MODE_ANY; PRECONDITION(!thCurrentClass.IsNull()); PRECONDITION(!thCurrentClass.IsTypeDesc()); } CONTRACTL_END //Check to see if Instance is equal to or derived from pCurrentClass. // //In some cases the type we have for the instance type is actually a TypeVarTypeDesc. In those cases we //need to check against the constraints (You're accessing a member through a 'T' with a type constraint //that makes this legal). For those cases, CanCastTo does what I want. MethodTable * pAccessor = thCurrentClass.GetMethodTable(); if (thInstanceClass.CanCastTo(TypeHandle(pAccessor))) return TRUE; //ArrayTypeDescs are the only typedescs that have methods, and their methods don't have IL. All other //TypeDescs don't need to be here. So only run this on MethodTables. if (!thInstanceClass.IsNull()) { return CanAccessFamilyVerificationEnclosingHelper(pAccessor, thInstanceClass); } return FALSE; } #endif // #ifndef DACCESS_COMPILE #ifdef DACCESS_COMPILE void ClassLoader::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; DAC_ENUM_DTHIS(); EMEM_OUT(("MEM: %p ClassLoader\n", dac_cast(this))); if (m_pAssembly.IsValid()) { ModuleIterator modIter = GetAssembly()->IterateModules(); while (modIter.Next()) { modIter.GetModule()->EnumMemoryRegions(flags, true); } } } #endif // #ifdef DACCESS_COMPILE