// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. /*============================================================ ** ** Header: AssemblySpec.cpp ** ** Purpose: Implements Assembly binding class ** ** ** ===========================================================*/ #include "common.h" #include #include "assemblyspec.hpp" #include "eeconfig.h" #include "strongnameinternal.h" #include "strongnameholders.h" #include "eventtrace.h" #include "../binder/inc/bindertracing.h" #ifdef _DEBUG // This debug-only wrapper for LookupAssembly is solely for the use of postconditions and // assertions. The problem is that the real LookupAssembly can throw an OOM // simply because it can't allocate scratch space. For the sake of asserting, // we can treat those as successful lookups. BOOL UnsafeVerifyLookupAssembly(AssemblySpecBindingCache *pCache, AssemblySpec *pSpec, DomainAssembly *pComparator) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_TRIGGERS; STATIC_CONTRACT_FORBID_FAULT; BOOL result = FALSE; EX_TRY { SCAN_IGNORE_FAULT; // Won't go away: This wrapper exists precisely to turn an OOM here into something our postconditions can deal with. result = (pComparator == pCache->LookupAssembly(pSpec)); } EX_CATCH { Exception *ex = GET_EXCEPTION(); result = ex->IsTransient(); } EX_END_CATCH(SwallowAllExceptions) return result; } #endif #ifdef _DEBUG // This debug-only wrapper for LookupFile is solely for the use of postconditions and // assertions. The problem is that the real LookupFile can throw an OOM // simply because it can't allocate scratch space. For the sake of asserting, // we can treat those as successful lookups. BOOL UnsafeVerifyLookupFile(AssemblySpecBindingCache *pCache, AssemblySpec *pSpec, PEAssembly *pComparator) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_TRIGGERS; STATIC_CONTRACT_FORBID_FAULT; BOOL result = FALSE; EX_TRY { SCAN_IGNORE_FAULT; // Won't go away: This wrapper exists precisely to turn an OOM here into something our postconditions can deal with. result = pCache->LookupFile(pSpec)->Equals(pComparator); } EX_CATCH { Exception *ex = GET_EXCEPTION(); result = ex->IsTransient(); } EX_END_CATCH(SwallowAllExceptions) return result; } #endif #ifdef _DEBUG // This debug-only wrapper for Contains is solely for the use of postconditions and // assertions. The problem is that the real Contains can throw an OOM // simply because it can't allocate scratch space. For the sake of asserting, // we can treat those as successful lookups. BOOL UnsafeContains(AssemblySpecBindingCache *pCache, AssemblySpec *pSpec) { STATIC_CONTRACT_NOTHROW; STATIC_CONTRACT_GC_TRIGGERS; STATIC_CONTRACT_FORBID_FAULT; BOOL result = FALSE; EX_TRY { SCAN_IGNORE_FAULT; // Won't go away: This wrapper exists precisely to turn an OOM here into something our postconditions can deal with. result = pCache->Contains(pSpec); } EX_CATCH { Exception *ex = GET_EXCEPTION(); result = ex->IsTransient(); } EX_END_CATCH(SwallowAllExceptions) return result; } #endif AssemblySpecHash::~AssemblySpecHash() { CONTRACTL { DESTRUCTOR_CHECK; NOTHROW; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; PtrHashMap::PtrIterator i = m_map.begin(); while (!i.end()) { AssemblySpec *s = (AssemblySpec*) i.GetValue(); if (m_pHeap != NULL) s->~AssemblySpec(); else delete s; ++i; } } // Check assembly name for invalid characters // Return value: // TRUE: If no invalid characters were found, or if the assembly name isn't set // FALSE: If invalid characters were found // This is needed to prevent security loopholes with ':', '/' and '\' in the assembly name BOOL AssemblySpec::IsValidAssemblyName() { CONTRACTL { THROWS; GC_TRIGGERS; } CONTRACTL_END; if (GetName()) { SString ssAssemblyName(SString::Utf8, GetName()); for (SString::Iterator i = ssAssemblyName.Begin(); i[0] != W('\0'); i++) { switch (i[0]) { case W(':'): case W('\\'): case W('/'): return FALSE; default: break; } } } return TRUE; } HRESULT AssemblySpec::InitializeSpecInternal(mdToken kAssemblyToken, IMDInternalImport *pImport, DomainAssembly *pStaticParent, BOOL fAllowAllocation) { CONTRACTL { INSTANCE_CHECK; if (fAllowAllocation) {GC_TRIGGERS;} else {GC_NOTRIGGER;}; if (fAllowAllocation) {INJECT_FAULT(COMPlusThrowOM());} else {FORBID_FAULT;}; NOTHROW; MODE_ANY; PRECONDITION(pImport->IsValidToken(kAssemblyToken)); PRECONDITION(TypeFromToken(kAssemblyToken) == mdtAssembly || TypeFromToken(kAssemblyToken) == mdtAssemblyRef); } CONTRACTL_END; HRESULT hr = S_OK; EX_TRY { IfFailThrow(BaseAssemblySpec::Init(kAssemblyToken,pImport)); // For static binds, we cannot reference a weakly named assembly from a strong named one. // (Note that this constraint doesn't apply to dynamic binds which is why this check is // not farther down the stack.) if (pStaticParent != NULL) { // We dont validate this for CoreCLR as there is no good use-case for this scenario. SetParentAssembly(pStaticParent); } } EX_CATCH_HRESULT(hr); return hr; } // AssemblySpec::InitializeSpecInternal void AssemblySpec::InitializeSpec(PEAssembly * pFile) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pFile)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END; ReleaseHolder pImport(pFile->GetMDImportWithRef()); mdAssembly a; IfFailThrow(pImport->GetAssemblyFromScope(&a)); InitializeSpec(a, pImport, NULL); // Set the binding context for the AssemblySpec ICLRPrivBinder* pCurrentBinder = GetBindingContext(); ICLRPrivBinder* pExpectedBinder = pFile->GetBindingContext(); if (pCurrentBinder == NULL) { // We should aways having the binding context in the PEAssembly. The only exception to this are the following: // // 1) when we are here during EEStartup and loading CoreLib. // 2) We are dealing with dynamic assemblies _ASSERTE((pExpectedBinder != NULL) || pFile->IsSystem() || pFile->IsDynamic()); SetBindingContext(pExpectedBinder); } } #ifndef CROSSGEN_COMPILE // This uses thread storage to allocate space. Please use Checkpoint and release it. HRESULT AssemblySpec::InitializeSpec(StackingAllocator* alloc, ASSEMBLYNAMEREF* pName, BOOL fParse /*=TRUE*/) { CONTRACTL { INSTANCE_CHECK; THROWS; MODE_COOPERATIVE; GC_TRIGGERS; PRECONDITION(CheckPointer(alloc)); PRECONDITION(CheckPointer(pName)); PRECONDITION(IsProtectedByGCFrame(pName)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END; // Simple name if ((*pName)->GetSimpleName() != NULL) { WCHAR* pString; int iString; ((STRINGREF) (*pName)->GetSimpleName())->RefInterpretGetStringValuesDangerousForGC(&pString, &iString); DWORD lgth = WszWideCharToMultiByte(CP_UTF8, 0, pString, iString, NULL, 0, NULL, NULL); if (lgth + 1 < lgth) ThrowHR(E_INVALIDARG); LPSTR lpName = (LPSTR) alloc->Alloc(S_UINT32(lgth) + S_UINT32(1)); WszWideCharToMultiByte(CP_UTF8, 0, pString, iString, lpName, lgth+1, NULL, NULL); lpName[lgth] = '\0'; // Calling Init here will trash the cached lpName in AssemblySpec, but lpName is still needed by ParseName // call below. SetName(lpName); } else { // Ensure we always have an assembly simple name. LPSTR lpName = (LPSTR) alloc->Alloc(S_UINT32(1)); lpName[0] = '\0'; SetName(lpName); } if (fParse) { HRESULT hr = ParseName(); // Sometimes Fusion flags invalid characters in the name, sometimes it doesn't // depending on where the invalid characters are // We want to Raise the assembly resolve event on all invalid characters // but calling ParseName before checking for invalid characters gives Fusion a chance to // parse the rest of the name (to get a public key token, etc.) if ((hr == FUSION_E_INVALID_NAME) || (!IsValidAssemblyName())) { // This is the only case where we do not throw on an error // We don't want to throw so as to give the caller a chance to call RaiseAssemblyResolveEvent // The only caller that cares is System.Reflection.Assembly.InternalLoad which calls us through // AssemblyNameNative::Init return FUSION_E_INVALID_NAME; } else IfFailThrow(hr); } else { AssemblyMetaDataInternal asmInfo; // Flags DWORD dwFlags = (*pName)->GetFlags(); // Version VERSIONREF version = (VERSIONREF) (*pName)->GetVersion(); if(version == NULL) { asmInfo.usMajorVersion = (USHORT)-1; asmInfo.usMinorVersion = (USHORT)-1; asmInfo.usBuildNumber = (USHORT)-1; asmInfo.usRevisionNumber = (USHORT)-1; } else { asmInfo.usMajorVersion = (USHORT)version->GetMajor(); asmInfo.usMinorVersion = (USHORT)version->GetMinor(); asmInfo.usBuildNumber = (USHORT)version->GetBuild(); asmInfo.usRevisionNumber = (USHORT)version->GetRevision(); } asmInfo.szLocale = 0; if ((*pName)->GetCultureInfo() != NULL) { struct _gc { OBJECTREF cultureinfo; STRINGREF pString; } gc; gc.cultureinfo = (*pName)->GetCultureInfo(); gc.pString = NULL; GCPROTECT_BEGIN(gc); MethodDescCallSite getName(METHOD__CULTURE_INFO__GET_NAME, &gc.cultureinfo); ARG_SLOT args[] = { ObjToArgSlot(gc.cultureinfo) }; gc.pString = getName.Call_RetSTRINGREF(args); if (gc.pString != NULL) { WCHAR* pString; int iString; gc.pString->RefInterpretGetStringValuesDangerousForGC(&pString, &iString); DWORD lgth = WszWideCharToMultiByte(CP_UTF8, 0, pString, iString, NULL, 0, NULL, NULL); S_UINT32 lengthWillNull = S_UINT32(lgth) + S_UINT32(1); LPSTR lpLocale = (LPSTR) alloc->Alloc(lengthWillNull); if (lengthWillNull.IsOverflow()) { COMPlusThrowHR(COR_E_OVERFLOW); } WszWideCharToMultiByte(CP_UTF8, 0, pString, iString, lpLocale, lengthWillNull.Value(), NULL, NULL); lpLocale[lgth] = '\0'; asmInfo.szLocale = lpLocale; } GCPROTECT_END(); } // Strong name DWORD cbPublicKeyOrToken=0; BYTE* pbPublicKeyOrToken=NULL; // Note that we prefer to take a public key token if present, // even if flags indicate a full public key if ((*pName)->GetPublicKeyToken() != NULL) { dwFlags &= ~afPublicKey; PBYTE pArray = NULL; pArray = (*pName)->GetPublicKeyToken()->GetDirectPointerToNonObjectElements(); cbPublicKeyOrToken = (*pName)->GetPublicKeyToken()->GetNumComponents(); pbPublicKeyOrToken = pArray; } else if ((*pName)->GetPublicKey() != NULL) { dwFlags |= afPublicKey; PBYTE pArray = NULL; pArray = (*pName)->GetPublicKey()->GetDirectPointerToNonObjectElements(); cbPublicKeyOrToken = (*pName)->GetPublicKey()->GetNumComponents(); pbPublicKeyOrToken = pArray; } BaseAssemblySpec::Init(GetName(),&asmInfo,pbPublicKeyOrToken,cbPublicKeyOrToken,dwFlags); } CloneFieldsToStackingAllocator(alloc); return S_OK; } void AssemblySpec::AssemblyNameInit(ASSEMBLYNAMEREF* pAsmName, PEImage* pImageInfo) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_TRIGGERS; PRECONDITION(IsProtectedByGCFrame (pAsmName)); } CONTRACTL_END; struct _gc { OBJECTREF CultureInfo; STRINGREF Locale; OBJECTREF Version; U1ARRAYREF PublicKeyOrToken; STRINGREF Name; STRINGREF CodeBase; } gc; ZeroMemory(&gc, sizeof(gc)); GCPROTECT_BEGIN(gc); if ((m_context.usMajorVersion != (USHORT) -1) && (m_context.usMinorVersion != (USHORT) -1)) { MethodTable* pVersion = CoreLibBinder::GetClass(CLASS__VERSION); // version gc.Version = AllocateObject(pVersion); // BaseAssemblySpec and AssemblyName properties store uint16 components for the version. Version and AssemblyVersion // store int32 or uint32. When the former are initialized from the latter, the components are truncated to uint16 size. // When the latter are initialized from the former, they are zero-extended to int32 size. For uint16 components, the max // value is used to indicate an unspecified component. For int32 components, -1 is used. Since we're initializing a // Version from an assembly version, map the uint16 unspecified value to the int32 size. int componentCount = 2; if (m_context.usBuildNumber != (USHORT)-1) { ++componentCount; if (m_context.usRevisionNumber != (USHORT)-1) { ++componentCount; } } switch (componentCount) { case 2: { // Call Version(int, int) because Version(int, int, int, int) does not allow passing the unspecified value -1 MethodDescCallSite ctorMethod(METHOD__VERSION__CTOR_Ix2); ARG_SLOT VersionArgs[] = { ObjToArgSlot(gc.Version), (ARG_SLOT) m_context.usMajorVersion, (ARG_SLOT) m_context.usMinorVersion }; ctorMethod.Call(VersionArgs); break; } case 3: { // Call Version(int, int, int) because Version(int, int, int, int) does not allow passing the unspecified value -1 MethodDescCallSite ctorMethod(METHOD__VERSION__CTOR_Ix3); ARG_SLOT VersionArgs[] = { ObjToArgSlot(gc.Version), (ARG_SLOT) m_context.usMajorVersion, (ARG_SLOT) m_context.usMinorVersion, (ARG_SLOT) m_context.usBuildNumber }; ctorMethod.Call(VersionArgs); break; } default: { // Call Version(int, int, int, int) _ASSERTE(componentCount == 4); MethodDescCallSite ctorMethod(METHOD__VERSION__CTOR_Ix4); ARG_SLOT VersionArgs[] = { ObjToArgSlot(gc.Version), (ARG_SLOT) m_context.usMajorVersion, (ARG_SLOT) m_context.usMinorVersion, (ARG_SLOT) m_context.usBuildNumber, (ARG_SLOT) m_context.usRevisionNumber }; ctorMethod.Call(VersionArgs); break; } } } // cultureinfo if (m_context.szLocale) { MethodTable* pCI = CoreLibBinder::GetClass(CLASS__CULTURE_INFO); gc.CultureInfo = AllocateObject(pCI); gc.Locale = StringObject::NewString(m_context.szLocale); MethodDescCallSite strCtor(METHOD__CULTURE_INFO__STR_CTOR); ARG_SLOT args[2] = { ObjToArgSlot(gc.CultureInfo), ObjToArgSlot(gc.Locale) }; strCtor.Call(args); } // public key or token byte array if (m_pbPublicKeyOrToken) { gc.PublicKeyOrToken = (U1ARRAYREF)AllocatePrimitiveArray(ELEMENT_TYPE_U1, m_cbPublicKeyOrToken); memcpyNoGCRefs(gc.PublicKeyOrToken->m_Array, m_pbPublicKeyOrToken, m_cbPublicKeyOrToken); } // simple name if(GetName()) gc.Name = StringObject::NewString(GetName()); if (GetCodeBase()) gc.CodeBase = StringObject::NewString(GetCodeBase()); BOOL fPublicKey = m_dwFlags & afPublicKey; ULONG hashAlgId=0; if (pImageInfo != NULL) { if(!pImageInfo->GetMDImport()->IsValidToken(TokenFromRid(1, mdtAssembly))) { ThrowHR(COR_E_BADIMAGEFORMAT); } IfFailThrow(pImageInfo->GetMDImport()->GetAssemblyProps(TokenFromRid(1, mdtAssembly), NULL, NULL, &hashAlgId, NULL, NULL, NULL)); } MethodDescCallSite init(METHOD__ASSEMBLY_NAME__CTOR); ARG_SLOT MethodArgs[] = { ObjToArgSlot(*pAsmName), ObjToArgSlot(gc.Name), fPublicKey ? ObjToArgSlot(gc.PublicKeyOrToken) : (ARG_SLOT) NULL, // public key fPublicKey ? (ARG_SLOT) NULL : ObjToArgSlot(gc.PublicKeyOrToken), // public key token ObjToArgSlot(gc.Version), ObjToArgSlot(gc.CultureInfo), (ARG_SLOT) hashAlgId, (ARG_SLOT) 1, // AssemblyVersionCompatibility.SameMachine ObjToArgSlot(gc.CodeBase), (ARG_SLOT) m_dwFlags, }; init.Call(MethodArgs); // Only set the processor architecture if we're looking at a newer binary that has // that information in the PE, and we're not looking at a reference assembly. if(pImageInfo && !pImageInfo->HasV1Metadata() && !pImageInfo->IsReferenceAssembly()) { DWORD dwMachine, dwKind; pImageInfo->GetPEKindAndMachine(&dwMachine,&dwKind); MethodDescCallSite setPA(METHOD__ASSEMBLY_NAME__SET_PROC_ARCH_INDEX); ARG_SLOT PAMethodArgs[] = { ObjToArgSlot(*pAsmName), (ARG_SLOT)dwMachine, (ARG_SLOT)dwKind }; setPA.Call(PAMethodArgs); } GCPROTECT_END(); } /* static */ void AssemblySpec::InitializeAssemblyNameRef(_In_ BINDER_SPACE::AssemblyName* assemblyName, _Out_ ASSEMBLYNAMEREF* assemblyNameRef) { CONTRACTL { THROWS; MODE_COOPERATIVE; GC_TRIGGERS; PRECONDITION(assemblyName != NULL); PRECONDITION(IsProtectedByGCFrame(assemblyNameRef)); } CONTRACTL_END; AssemblySpec spec; spec.InitializeWithAssemblyIdentity(assemblyName); StackScratchBuffer nameBuffer; spec.SetName(assemblyName->GetSimpleName().GetUTF8(nameBuffer)); StackScratchBuffer cultureBuffer; if (assemblyName->Have(BINDER_SPACE::AssemblyIdentity::IDENTITY_FLAG_CULTURE)) { LPCSTR culture = assemblyName->IsNeutralCulture() ? "" : assemblyName->GetCulture().GetUTF8(cultureBuffer); spec.SetCulture(culture); } spec.AssemblyNameInit(assemblyNameRef, NULL); } #endif // CROSSGEN_COMPILE // Check if the supplied assembly's public key matches up with the one in the Spec, if any // Throws an appropriate exception in case of a mismatch void AssemblySpec::MatchPublicKeys(Assembly *pAssembly) { CONTRACTL { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; // Check that the public keys are the same as in the AR. if (!IsStrongNamed()) return; const void *pbPublicKey; DWORD cbPublicKey; pbPublicKey = pAssembly->GetPublicKey(&cbPublicKey); if (cbPublicKey == 0) ThrowHR(FUSION_E_PRIVATE_ASM_DISALLOWED); if (IsAfPublicKey(m_dwFlags)) { if ((m_cbPublicKeyOrToken != cbPublicKey) || memcmp(m_pbPublicKeyOrToken, pbPublicKey, m_cbPublicKeyOrToken)) { ThrowHR(FUSION_E_REF_DEF_MISMATCH); } } else { // Ref has a token StrongNameBufferHolder pbStrongNameToken; DWORD cbStrongNameToken; IfFailThrow(StrongNameTokenFromPublicKey((BYTE*)pbPublicKey, cbPublicKey, &pbStrongNameToken, &cbStrongNameToken)); if ((m_cbPublicKeyOrToken != cbStrongNameToken) || memcmp(m_pbPublicKeyOrToken, pbStrongNameToken, cbStrongNameToken)) { ThrowHR(FUSION_E_REF_DEF_MISMATCH); } } } Assembly *AssemblySpec::LoadAssembly(FileLoadLevel targetLevel, BOOL fThrowOnFileNotFound) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; DomainAssembly * pDomainAssembly = LoadDomainAssembly(targetLevel, fThrowOnFileNotFound); if (pDomainAssembly == NULL) { _ASSERTE(!fThrowOnFileNotFound); return NULL; } return pDomainAssembly->GetAssembly(); } // Returns a BOOL indicating if the two Binder references point to the same // binder instance. BOOL AreSameBinderInstance(ICLRPrivBinder *pBinderA, ICLRPrivBinder *pBinderB) { LIMITED_METHOD_CONTRACT; BOOL fIsSameInstance = (pBinderA == pBinderB); if (!fIsSameInstance && (pBinderA != NULL) && (pBinderB != NULL)) { // Get the ID for the first binder UINT_PTR binderIDA = 0, binderIDB = 0; HRESULT hr = pBinderA->GetBinderID(&binderIDA); if (SUCCEEDED(hr)) { // Get the ID for the second binder hr = pBinderB->GetBinderID(&binderIDB); if (SUCCEEDED(hr)) { fIsSameInstance = (binderIDA == binderIDB); } } } return fIsSameInstance; } ICLRPrivBinder* AssemblySpec::GetBindingContextFromParentAssembly(AppDomain *pDomain) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(pDomain != NULL); } CONTRACTL_END; ICLRPrivBinder *pParentAssemblyBinder = NULL; DomainAssembly *pParentDomainAssembly = GetParentAssembly(); if(pParentDomainAssembly != NULL) { // Get the PEAssembly associated with the parent's domain assembly PEAssembly *pParentPEAssembly = pParentDomainAssembly->GetFile(); // ICLRPrivAssembly implements ICLRPrivBinder and thus, "is a" binder in a manner of semantics. pParentAssemblyBinder = pParentPEAssembly->GetBindingContext(); } if (GetPreferFallbackLoadContextBinder()) { // If we have been asked to use the fallback load context binder (currently only supported for AssemblyLoadContext.LoadFromAssemblyName), // then pretend we do not have any binder yet available. _ASSERTE(GetFallbackLoadContextBinderForRequestingAssembly() != NULL); pParentAssemblyBinder = NULL; } if (pParentAssemblyBinder == NULL) { // If the parent assembly binder is not available, then we maybe dealing with one of the following // assembly scenarios: // // 1) Domain Neutral assembly // 2) Entrypoint assembly // 3) AssemblyLoadContext.LoadFromAssemblyName // // For (1) and (2), we will need to bind against the DefaultContext binder (aka TPA Binder). This happens // below if we do not find the parent assembly binder. // // For (3), fetch the fallback load context binder reference. pParentAssemblyBinder = GetFallbackLoadContextBinderForRequestingAssembly(); } if (pParentAssemblyBinder != NULL) { CLRPrivBinderCoreCLR *pTPABinder = pDomain->GetTPABinderContext(); if (AreSameBinderInstance(pTPABinder, pParentAssemblyBinder)) { // If the parent assembly is a platform (TPA) assembly, then its binding context will always be the TPABinder context. In // such case, we will return the default context for binding to allow the bind to go // via the custom binder context, if it was overridden. If it was not overridden, then we will get the expected // TPABinder context anyways. // // Get the reference to the default binding context (this could be the TPABinder context or custom AssemblyLoadContext) pParentAssemblyBinder = static_cast(pDomain->GetTPABinderContext()); } } if (!pParentAssemblyBinder) { // We can be here when loading assemblies via the host (e.g. ICLRRuntimeHost2::ExecuteAssembly) or dealing with assemblies // whose parent is a domain neutral assembly (see comment above for details). // // In such a case, the parent assembly (semantically) is CoreLibrary and thus, the default binding context should be // used as the parent assembly binder. pParentAssemblyBinder = static_cast(pDomain->GetTPABinderContext()); } return pParentAssemblyBinder; } DomainAssembly *AssemblySpec::LoadDomainAssembly(FileLoadLevel targetLevel, BOOL fThrowOnFileNotFound) { CONTRACT(DomainAssembly *) { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; POSTCONDITION((!fThrowOnFileNotFound && CheckPointer(RETVAL, NULL_OK)) || CheckPointer(RETVAL)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; ETWOnStartup (LoaderCatchCall_V1, LoaderCatchCallEnd_V1); AppDomain* pDomain = GetAppDomain(); DomainAssembly* pAssembly = pDomain->FindCachedAssembly(this); if (pAssembly) { BinderTracing::AssemblyBindOperation bindOperation(this); bindOperation.SetResult(pAssembly->GetFile(), true /*cached*/); pDomain->LoadDomainFile(pAssembly, targetLevel); RETURN pAssembly; } PEAssemblyHolder pFile(pDomain->BindAssemblySpec(this, fThrowOnFileNotFound)); if (pFile == NULL) RETURN NULL; pAssembly = pDomain->LoadDomainAssembly(this, pFile, targetLevel); RETURN pAssembly; } /* static */ Assembly *AssemblySpec::LoadAssembly(LPCSTR pSimpleName, AssemblyMetaDataInternal* pContext, const BYTE * pbPublicKeyOrToken, DWORD cbPublicKeyOrToken, DWORD dwFlags) { CONTRACT(Assembly *) { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pSimpleName)); POSTCONDITION(CheckPointer(RETVAL)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; AssemblySpec spec; IfFailThrow(spec.Init(pSimpleName, pContext, pbPublicKeyOrToken, cbPublicKeyOrToken, dwFlags)); RETURN spec.LoadAssembly(FILE_LOADED); } /* static */ Assembly *AssemblySpec::LoadAssembly(LPCWSTR pFilePath) { CONTRACT(Assembly *) { THROWS; GC_TRIGGERS; MODE_ANY; PRECONDITION(CheckPointer(pFilePath)); POSTCONDITION(CheckPointer(RETVAL)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; AssemblySpec spec; spec.SetCodeBase(pFilePath); RETURN spec.LoadAssembly(FILE_LOADED); } HRESULT AssemblySpec::CheckFriendAssemblyName() { WRAPPER_NO_CONTRACT; // Version, Culture, Architecture, and publickeytoken are not permitted if ((m_context.usMajorVersion != (USHORT) -1) || (m_context.szLocale != NULL) || (IsAfPA_Specified(m_dwFlags)) || (IsStrongNamed() && !HasPublicKey())) { return META_E_CA_BAD_FRIENDS_ARGS; } else { return S_OK; } } HRESULT AssemblySpec::EmitToken( IMetaDataAssemblyEmit *pEmit, mdAssemblyRef *pToken) { CONTRACTL { INSTANCE_CHECK; MODE_ANY; NOTHROW; GC_NOTRIGGER; INJECT_FAULT(return E_OUTOFMEMORY;); } CONTRACTL_END; HRESULT hr = S_OK; EX_TRY { SmallStackSString ssName; GetName(ssName); ASSEMBLYMETADATA AMD; AMD.usMajorVersion = m_context.usMajorVersion; AMD.usMinorVersion = m_context.usMinorVersion; AMD.usBuildNumber = m_context.usBuildNumber; AMD.usRevisionNumber = m_context.usRevisionNumber; if (m_context.szLocale) { AMD.cbLocale = MultiByteToWideChar(CP_UTF8, 0, m_context.szLocale, -1, NULL, 0); if(AMD.cbLocale==0) IfFailGo(HRESULT_FROM_GetLastError()); AMD.szLocale = (LPWSTR) alloca(AMD.cbLocale * sizeof(WCHAR) ); if(MultiByteToWideChar(CP_UTF8, 0, m_context.szLocale, -1, AMD.szLocale, AMD.cbLocale)==0) IfFailGo(HRESULT_FROM_GetLastError()); } else { AMD.cbLocale = 0; AMD.szLocale = NULL; } // If we've been asked to emit a public key token in the reference but we've // been given a public key then we need to generate the token now. if (m_cbPublicKeyOrToken && IsAfPublicKey(m_dwFlags)) { StrongNameBufferHolder pbPublicKeyToken; DWORD cbPublicKeyToken; IfFailThrow(StrongNameTokenFromPublicKey(m_pbPublicKeyOrToken, m_cbPublicKeyOrToken, &pbPublicKeyToken, &cbPublicKeyToken)); hr = pEmit->DefineAssemblyRef(pbPublicKeyToken, cbPublicKeyToken, ssName.GetUnicode(), &AMD, NULL, 0, m_dwFlags & ~afPublicKey, pToken); } else { hr = pEmit->DefineAssemblyRef(m_pbPublicKeyOrToken, m_cbPublicKeyOrToken, ssName.GetUnicode(), &AMD, NULL, 0, m_dwFlags, pToken); } hr = S_OK; ErrExit: ; } EX_CATCH_HRESULT(hr); return hr; } AssemblySpecBindingCache::AssemblySpecBindingCache() { LIMITED_METHOD_CONTRACT; } AssemblySpecBindingCache::~AssemblySpecBindingCache() { CONTRACTL { DESTRUCTOR_CHECK; NOTHROW; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; Clear(); } void AssemblySpecBindingCache::Clear() { CONTRACTL { DESTRUCTOR_CHECK; NOTHROW; GC_TRIGGERS; MODE_ANY; } CONTRACTL_END; PtrHashMap::PtrIterator i = m_map.begin(); while (!i.end()) { AssemblyBinding *b = (AssemblyBinding*) i.GetValue(); if (m_pHeap == NULL) delete b; else b->~AssemblyBinding(); ++i; } m_map.Clear(); } void AssemblySpecBindingCache::Init(CrstBase *pCrst, LoaderHeap *pHeap) { WRAPPER_NO_CONTRACT; LockOwner lock = {pCrst, IsOwnerOfCrst}; m_map.Init(INITIAL_ASM_SPEC_HASH_SIZE, CompareSpecs, TRUE, &lock); m_pHeap = pHeap; } AssemblySpecBindingCache::AssemblyBinding* AssemblySpecBindingCache::LookupInternal(AssemblySpec* pSpec, BOOL fThrow) { CONTRACTL { if (fThrow) { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } else { GC_NOTRIGGER; NOTHROW; FORBID_FAULT; } MODE_ANY; PRECONDITION(pSpec != NULL); } CONTRACTL_END; UPTR key = (UPTR)pSpec->Hash(); UPTR lookupKey = key; // On CoreCLR, we will use the BinderID as the key ICLRPrivBinder *pBinderContextForLookup = NULL; AppDomain *pSpecDomain = pSpec->GetAppDomain(); bool fGetBindingContextFromParent = true; // Check if the AssemblySpec already has specified its binding context. This will be set for assemblies that are // attempted to be explicitly bound using AssemblyLoadContext LoadFrom* methods. if(!pSpec->IsAssemblySpecForCoreLib()) pBinderContextForLookup = pSpec->GetBindingContext(); else { // For System.Private.Corelib Binding context is either not set or if set then it should be TPA _ASSERTE(pSpec->GetBindingContext() == NULL || pSpec->GetBindingContext() == pSpecDomain->GetTPABinderContext()); } if (pBinderContextForLookup != NULL) { // We are working with the actual binding context in which the assembly was expected to be loaded. // Thus, we don't need to get it from the parent assembly. fGetBindingContextFromParent = false; } if (fGetBindingContextFromParent) { // CoreLib does not have a binding context associated with it and its lookup will only be done // using its AssemblySpec hash. if (!pSpec->IsAssemblySpecForCoreLib()) { pBinderContextForLookup = pSpec->GetBindingContextFromParentAssembly(pSpecDomain); pSpec->SetBindingContext(pBinderContextForLookup); } } if (pBinderContextForLookup) { UINT_PTR binderID = 0; HRESULT hr = pBinderContextForLookup->GetBinderID(&binderID); _ASSERTE(SUCCEEDED(hr)); lookupKey = key^binderID; } AssemblyBinding* pEntry = (AssemblyBinding *)m_map.LookupValue(lookupKey, pSpec); // Reset the binding context if one was originally never present in the AssemblySpec and we didnt find any entry // in the cache. if (fGetBindingContextFromParent) { if (pEntry == (AssemblyBinding *) INVALIDENTRY) { pSpec->SetBindingContext(NULL); } } return pEntry; } BOOL AssemblySpecBindingCache::Contains(AssemblySpec *pSpec) { WRAPPER_NO_CONTRACT; return (LookupInternal(pSpec, TRUE) != (AssemblyBinding *) INVALIDENTRY); } DomainAssembly *AssemblySpecBindingCache::LookupAssembly(AssemblySpec *pSpec, BOOL fThrow /*=TRUE*/) { CONTRACT(DomainAssembly *) { INSTANCE_CHECK; if (fThrow) { GC_TRIGGERS; THROWS; INJECT_FAULT(COMPlusThrowOM();); } else { GC_NOTRIGGER; NOTHROW; FORBID_FAULT; } MODE_ANY; POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); } CONTRACT_END; AssemblyBinding *entry = (AssemblyBinding *) INVALIDENTRY; entry = LookupInternal(pSpec, fThrow); if (entry == (AssemblyBinding *) INVALIDENTRY) RETURN NULL; else { if ((entry->GetAssembly() == NULL) && fThrow) { // May be either unloaded, or an exception occurred. entry->ThrowIfError(); } RETURN entry->GetAssembly(); } } PEAssembly *AssemblySpecBindingCache::LookupFile(AssemblySpec *pSpec, BOOL fThrow /*=TRUE*/) { CONTRACT(PEAssembly *) { INSTANCE_CHECK; if (fThrow) { GC_TRIGGERS; THROWS; INJECT_FAULT(COMPlusThrowOM();); } else { GC_NOTRIGGER; NOTHROW; FORBID_FAULT; } MODE_ANY; POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); } CONTRACT_END; AssemblyBinding *entry = (AssemblyBinding *) INVALIDENTRY; entry = LookupInternal(pSpec, fThrow); if (entry == (AssemblyBinding *) INVALIDENTRY) RETURN NULL; else { if (fThrow && (entry->GetFile() == NULL)) { CONSISTENCY_CHECK(entry->IsError()); entry->ThrowIfError(); } RETURN entry->GetFile(); } } class AssemblyBindingHolder { public: AssemblyBindingHolder() { LIMITED_METHOD_CONTRACT; m_entry = NULL; m_pHeap = NULL; } AssemblySpecBindingCache::AssemblyBinding *CreateAssemblyBinding(LoaderHeap *pHeap) { CONTRACTL { THROWS; GC_TRIGGERS; INJECT_FAULT(COMPlusThrowOM();); } CONTRACTL_END m_pHeap = pHeap; if (pHeap) { m_entry = new (m_amTracker.Track(pHeap->AllocMem(S_SIZE_T(sizeof(AssemblySpecBindingCache::AssemblyBinding))))) AssemblySpecBindingCache::AssemblyBinding; } else { m_entry = new AssemblySpecBindingCache::AssemblyBinding; } return m_entry; } ~AssemblyBindingHolder() { CONTRACTL { NOTHROW; GC_TRIGGERS; FORBID_FAULT; } CONTRACTL_END if (m_entry) { if (m_pHeap) { // just call destructor - m_amTracker will delete the memory for m_entry itself. m_entry->~AssemblyBinding(); } else { delete m_entry; } } } void SuppressRelease() { LIMITED_METHOD_CONTRACT; m_entry = NULL; m_pHeap = NULL; m_amTracker.SuppressRelease(); } AllocMemTracker *GetPamTracker() { LIMITED_METHOD_CONTRACT; return &m_amTracker; } private: AssemblySpecBindingCache::AssemblyBinding *m_entry; LoaderHeap *m_pHeap; AllocMemTracker m_amTracker; }; // NOTE ABOUT STATE OF CACHE ENTRIES: // // A cache entry can be in one of 4 states: // 1. Empty (no entry) // 2. File (a PEAssembly has been bound, but not yet an Assembly) // 3. Assembly (Both a PEAssembly & Assembly are available.) // 4. Error (an error has occurred) // // The legal state transitions are: // 1 -> any // 2 -> 3 // 2 -> 4 BOOL AssemblySpecBindingCache::StoreAssembly(AssemblySpec *pSpec, DomainAssembly *pAssembly) { CONTRACT(BOOL) { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; POSTCONDITION(UnsafeContains(this, pSpec)); POSTCONDITION(UnsafeVerifyLookupAssembly(this, pSpec, pAssembly)); INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; UPTR key = (UPTR)pSpec->Hash(); // On CoreCLR, we will use the BinderID as the key ICLRPrivBinder* pBinderContextForLookup = pAssembly->GetFile()->GetBindingContext(); _ASSERTE(pBinderContextForLookup || pAssembly->GetFile()->IsSystem()); if (pBinderContextForLookup) { UINT_PTR binderID = 0; HRESULT hr = pBinderContextForLookup->GetBinderID(&binderID); _ASSERTE(SUCCEEDED(hr)); key = key^binderID; if (!pSpec->GetBindingContext()) { pSpec->SetBindingContext(pBinderContextForLookup); } } AssemblyBinding *entry = (AssemblyBinding *) m_map.LookupValue(key, pSpec); if (entry == (AssemblyBinding *) INVALIDENTRY) { AssemblyBindingHolder abHolder; LoaderHeap* pHeap = m_pHeap; if (pAssembly->IsCollectible()) { pHeap = pAssembly->GetLoaderAllocator()->GetHighFrequencyHeap(); } entry = abHolder.CreateAssemblyBinding(pHeap); entry->Init(pSpec,pAssembly->GetFile(),pAssembly,NULL,pHeap, abHolder.GetPamTracker()); m_map.InsertValue(key, entry); abHolder.SuppressRelease(); STRESS_LOG2(LF_CLASSLOADER,LL_INFO10,"StoreFile (StoreAssembly): Add cached entry (%p) with PEFile %p",entry,pAssembly->GetFile()); RETURN TRUE; } else { if (!entry->IsError()) { if (entry->GetAssembly() != NULL) { // OK if this is a duplicate if (entry->GetAssembly() == pAssembly) RETURN TRUE; } else { // OK if we have have a matching PEAssembly if (entry->GetFile() != NULL && pAssembly->GetFile()->Equals(entry->GetFile())) { entry->SetAssembly(pAssembly); RETURN TRUE; } } } // Invalid cache transition (see above note about state transitions) RETURN FALSE; } } // Note that this routine may be called outside a lock, so may be racing with another thread. // Returns TRUE if add was successful - if FALSE is returned, caller should honor current // cached value to ensure consistency. BOOL AssemblySpecBindingCache::StoreFile(AssemblySpec *pSpec, PEAssembly *pFile) { CONTRACT(BOOL) { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; POSTCONDITION((!RETVAL) || (UnsafeContains(this, pSpec) && UnsafeVerifyLookupFile(this, pSpec, pFile))); INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; UPTR key = (UPTR)pSpec->Hash(); // On CoreCLR, we will use the BinderID as the key ICLRPrivBinder* pBinderContextForLookup = pFile->GetBindingContext(); _ASSERTE(pBinderContextForLookup || pFile->IsSystem()); if (pBinderContextForLookup) { UINT_PTR binderID = 0; HRESULT hr = pBinderContextForLookup->GetBinderID(&binderID); _ASSERTE(SUCCEEDED(hr)); key = key^binderID; if (!pSpec->GetBindingContext()) { pSpec->SetBindingContext(pBinderContextForLookup); } } AssemblyBinding *entry = (AssemblyBinding *) m_map.LookupValue(key, pSpec); if (entry == (AssemblyBinding *) INVALIDENTRY) { AssemblyBindingHolder abHolder; LoaderHeap* pHeap = m_pHeap; #ifndef CROSSGEN_COMPILE if (pBinderContextForLookup != NULL) { LoaderAllocator* pLoaderAllocator = NULL; // Assemblies loaded with AssemblyLoadContext need to use a different heap if // marked as collectible if (SUCCEEDED(pBinderContextForLookup->GetLoaderAllocator((LPVOID*)&pLoaderAllocator))) { _ASSERTE(pLoaderAllocator != NULL); pHeap = pLoaderAllocator->GetHighFrequencyHeap(); } } #endif // !CROSSGEN_COMPILE entry = abHolder.CreateAssemblyBinding(pHeap); entry->Init(pSpec,pFile,NULL,NULL,pHeap, abHolder.GetPamTracker()); m_map.InsertValue(key, entry); abHolder.SuppressRelease(); STRESS_LOG2(LF_CLASSLOADER,LL_INFO10,"StoreFile: Add cached entry (%p) with PEFile %p\n", entry, pFile); RETURN TRUE; } else { if (!entry->IsError()) { // OK if this is a duplicate if (entry->GetFile() != NULL && pFile->Equals(entry->GetFile())) RETURN TRUE; } else if (entry->IsPostBindError()) { // Another thread has reported what's going to happen later. entry->ThrowIfError(); } STRESS_LOG2(LF_CLASSLOADER,LL_INFO10,"Incompatible cached entry found (%p) when adding PEFile %p\n", entry, pFile); // Invalid cache transition (see above note about state transitions) RETURN FALSE; } } BOOL AssemblySpecBindingCache::StoreException(AssemblySpec *pSpec, Exception* pEx) { CONTRACT(BOOL) { INSTANCE_CHECK; THROWS; GC_TRIGGERS; MODE_ANY; DISABLED(POSTCONDITION(UnsafeContains(this, pSpec))); //@todo: Getting violations here - StoreExceptions could happen anywhere so this is possibly too aggressive. INJECT_FAULT(COMPlusThrowOM();); } CONTRACT_END; UPTR key = (UPTR)pSpec->Hash(); AssemblyBinding *entry = LookupInternal(pSpec, TRUE); if (entry == (AssemblyBinding *) INVALIDENTRY) { // TODO: Merge this with the failure lookup in the binder // // Since no entry was found for this assembly in any binding context, save the failure // in the TPABinder context ICLRPrivBinder* pBinderToSaveException = NULL; pBinderToSaveException = pSpec->GetBindingContext(); if (pBinderToSaveException == NULL) { if (!pSpec->IsAssemblySpecForCoreLib()) { pBinderToSaveException = pSpec->GetBindingContextFromParentAssembly(pSpec->GetAppDomain()); UINT_PTR binderID = 0; HRESULT hr = pBinderToSaveException->GetBinderID(&binderID); _ASSERTE(SUCCEEDED(hr)); key = key^binderID; } } } if (entry == (AssemblyBinding *) INVALIDENTRY) { AssemblyBindingHolder abHolder; entry = abHolder.CreateAssemblyBinding(m_pHeap); entry->Init(pSpec,NULL,NULL,pEx,m_pHeap, abHolder.GetPamTracker()); m_map.InsertValue(key, entry); abHolder.SuppressRelease(); STRESS_LOG2(LF_CLASSLOADER,LL_INFO10,"StoreFile (StoreException): Add cached entry (%p) with exception %p",entry,pEx); RETURN TRUE; } else { // OK if this is a duplicate if (entry->IsError()) { if (entry->GetHR() == pEx->GetHR()) RETURN TRUE; } else { // OK to transition to error if we don't have an Assembly yet if (entry->GetAssembly() == NULL) { entry->InitException(pEx); RETURN TRUE; } } // Invalid cache transition (see above note about state transitions) RETURN FALSE; } } BOOL AssemblySpecBindingCache::RemoveAssembly(DomainAssembly* pAssembly) { CONTRACT(BOOL) { INSTANCE_CHECK; NOTHROW; GC_TRIGGERS; MODE_ANY; PRECONDITION(pAssembly != NULL); } CONTRACT_END; BOOL result = FALSE; PtrHashMap::PtrIterator i = m_map.begin(); while (!i.end()) { AssemblyBinding* entry = (AssemblyBinding*)i.GetValue(); if (entry->GetAssembly() == pAssembly) { UPTR key = i.GetKey(); m_map.DeleteValue(key, entry); if (m_pHeap == NULL) delete entry; else entry->~AssemblyBinding(); result = TRUE; } ++i; } RETURN result; } /* static */ BOOL AssemblySpecHash::CompareSpecs(UPTR u1, UPTR u2) { // the same... WRAPPER_NO_CONTRACT; return AssemblySpecBindingCache::CompareSpecs(u1,u2); } /* static */ BOOL AssemblySpecBindingCache::CompareSpecs(UPTR u1, UPTR u2) { WRAPPER_NO_CONTRACT; AssemblySpec *a1 = (AssemblySpec *) (u1 << 1); AssemblySpec *a2 = (AssemblySpec *) u2; return a1->CompareEx(a2); } DomainAssembly * AssemblySpec::GetParentAssembly() { LIMITED_METHOD_CONTRACT; return m_pParentAssembly; }