From 1dd60327548a0d906ba1435e21d5e03ea12ee182 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Fri, 3 Oct 2025 20:23:45 -0700 Subject: [PATCH 1/4] Fix race on assembly initialize - delay assembly creation until after FileLoadLock creation --- src/coreclr/vm/appdomain.cpp | 130 +++++++++++++++++++------------- src/coreclr/vm/appdomain.hpp | 12 ++- src/coreclr/vm/assemblyspec.hpp | 3 +- 3 files changed, 89 insertions(+), 56 deletions(-) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 7b247b2b8bfd02..fd87146d0103fd 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1871,7 +1871,7 @@ DispIDCache* AppDomain::SetupRefDispIDCache() #endif // FEATURE_COMINTEROP -FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEAssembly * pPEAssembly, Assembly *pAssembly) +FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEAssembly * pPEAssembly) { CONTRACTL { @@ -1884,7 +1884,7 @@ FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEAssembly * pPEAssemb } CONTRACTL_END; - NewHolder result(new FileLoadLock(pLock, pPEAssembly, pAssembly)); + NewHolder result(new FileLoadLock(pLock, pPEAssembly)); pLock->AddElement(result); result->AddRef(); // Add one ref on behalf of the ListLock's reference. The corresponding Release() happens in FileLoadLock::CompleteLoadLevel. @@ -1910,6 +1910,13 @@ Assembly *FileLoadLock::GetAssembly() return m_pAssembly; } +PEAssembly *FileLoadLock::GetPEAssembly() +{ + LIMITED_METHOD_CONTRACT; + // Underlying PEAssembly pointer is stored in base ListLockEntry::m_data. + return (PEAssembly*)m_data; +} + FileLoadLevel FileLoadLock::GetLoadLevel() { LIMITED_METHOD_CONTRACT; @@ -1958,6 +1965,7 @@ BOOL FileLoadLock::CanAcquire(FileLoadLevel targetLevel) static const char *fileLoadLevelName[] = { "CREATE", // FILE_LOAD_CREATE + "ALLOCATE", // FILE_LOAD_ALLOCATE "BEGIN", // FILE_LOAD_BEGIN "BEFORE_TYPE_LOAD", // FILE_LOAD_BEFORE_TYPE_LOAD "EAGER_FIXUPS", // FILE_LOAD_EAGER_FIXUPS @@ -1983,7 +1991,9 @@ BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success) if (level > m_level) { // Must complete each level in turn, unless we have an error - CONSISTENCY_CHECK(m_pAssembly->IsError() || (level == (m_level+1))); + CONSISTENCY_CHECK((level == (m_level+1)) || (m_pAssembly != nullptr && m_pAssembly->IsError())); + CONSISTENCY_CHECK(m_pAssembly != nullptr || level < FILE_LOAD_ALLOCATE); + // Remove the lock from the list if the load is completed if (level >= FILE_ACTIVE) { @@ -2019,7 +2029,7 @@ BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success) { m_level = (FileLoadLevel)level; - if (success) + if (success && level >= FILE_LOAD_ALLOCATE) m_pAssembly->SetLoadLevel(level); } @@ -2042,6 +2052,18 @@ BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success) return FALSE; } +void FileLoadLock::SetAssembly(Assembly *pAssembly) +{ + LIMITED_METHOD_CONTRACT; + + _ASSERTE(HasLock()); + _ASSERTE(m_level == FILE_LOAD_CREATE); // Only valid to set during CREATE -> ALLOCATE + _ASSERTE(m_pAssembly == nullptr); + _ASSERTE(pAssembly != nullptr && pAssembly->GetPEAssembly() == (PEAssembly *)m_data); + + m_pAssembly = pAssembly; +} + void FileLoadLock::SetError(Exception *ex) { CONTRACTL @@ -2088,10 +2110,10 @@ UINT32 FileLoadLock::Release() return count; } -FileLoadLock::FileLoadLock(PEFileListLock *pLock, PEAssembly * pPEAssembly, Assembly *pAssembly) +FileLoadLock::FileLoadLock(PEFileListLock *pLock, PEAssembly * pPEAssembly) : ListLockEntry(pLock, pPEAssembly, "File load lock"), m_level((FileLoadLevel) (FILE_LOAD_CREATE)), - m_pAssembly(pAssembly), + m_pAssembly(nullptr), m_cachedHR(S_OK) { WRAPPER_NO_CONTRACT; @@ -2420,23 +2442,6 @@ Assembly *AppDomain::LoadAssemblyInternal(AssemblySpec* pIdentity, if (result == NULL) { - LoaderAllocator *pLoaderAllocator = NULL; - - AssemblyBinder *pAssemblyBinder = pPEAssembly->GetAssemblyBinder(); - // Assemblies loaded with CustomAssemblyBinder need to use a different LoaderAllocator if - // marked as collectible - pLoaderAllocator = pAssemblyBinder->GetLoaderAllocator(); - if (pLoaderAllocator == NULL) - { - pLoaderAllocator = this->GetLoaderAllocator(); - } - - // Allocate the DomainAssembly a bit early to avoid GC mode problems. We could potentially avoid - // a rare redundant allocation by moving this closer to FileLoadLock::Create, but it's not worth it. - AllocMemTracker amTracker; - AllocMemTracker *pamTracker = &amTracker; - NewHolder pDomainAssembly = new DomainAssembly(pPEAssembly, pLoaderAllocator, pamTracker); - LoadLockHolder lock(this); // Find the list lock entry @@ -2448,20 +2453,9 @@ Assembly *AppDomain::LoadAssemblyInternal(AssemblySpec* pIdentity, result = FindAssembly(pPEAssembly, FindAssemblyOptions_IncludeFailedToLoad); if (result == NULL) { - // We are the first one in - create the DomainAssembly + // We are the first one in - create the FileLoadLock. Creation of the Assembly will happen at FILE_LOAD_ALLOCATE stage registerNewAssembly = true; - fileLock = FileLoadLock::Create(lock, pPEAssembly, pDomainAssembly->GetAssembly()); - pDomainAssembly.SuppressRelease(); - pamTracker->SuppressRelease(); - - // Set the assembly module to be tenured now that we know it won't be deleted - pDomainAssembly->GetAssembly()->SetIsTenured(); - if (pDomainAssembly->GetAssembly()->IsCollectible()) - { - // We add the assembly to the LoaderAllocator only when we are sure that it can be added - // and won't be deleted in case of a concurrent load from the same ALC - ((AssemblyLoaderAllocator *)pLoaderAllocator)->AddDomainAssembly(pDomainAssembly); - } + fileLock = FileLoadLock::Create(lock, pPEAssembly); } } else @@ -2479,6 +2473,8 @@ Assembly *AppDomain::LoadAssemblyInternal(AssemblySpec* pIdentity, // so it will not be removed until app domain unload. So there is no need // to release our ref count. result = LoadAssembly(fileLock, targetLevel); + // By now FILE_LOAD_ALLOCATE should have run and the Assembly should exist + _ASSERTE(result != NULL); } else { @@ -2487,7 +2483,7 @@ Assembly *AppDomain::LoadAssemblyInternal(AssemblySpec* pIdentity, if (registerNewAssembly) { - pPEAssembly->GetAssemblyBinder()->AddLoadedAssembly(pDomainAssembly->GetAssembly()); + pPEAssembly->GetAssemblyBinder()->AddLoadedAssembly(result); } } else @@ -2517,6 +2513,7 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel STANDARD_VM_CHECK; PRECONDITION(CheckPointer(pLock)); PRECONDITION(AppDomain::GetCurrentDomain() == this); + PRECONDITION(targetLevel >= FILE_LOAD_ALLOCATE); POSTCONDITION(RETVAL->GetLoadLevel() >= GetCurrentFileLoadLevel() || RETVAL->GetLoadLevel() >= targetLevel); POSTCONDITION(RETVAL->CheckNoError(targetLevel)); @@ -2531,6 +2528,7 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel // Do a quick out check for the already loaded case. if (pLock->GetLoadLevel() >= targetLevel) { + _ASSERTE(pAssembly != nullptr); pAssembly->ThrowIfError(targetLevel); RETURN pAssembly; @@ -2553,8 +2551,9 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel if (immediateTargetLevel > limit.GetLoadLevel()) immediateTargetLevel = limit.GetLoadLevel(); + const char *simpleName = pLock->GetPEAssembly()->GetSimpleName(); LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t>>>Load initiated, %s/%s\n", - pAssembly->GetSimpleName(), + simpleName, fileLoadLevelName[immediateTargetLevel], fileLoadLevelName[targetLevel])); // Now loop and do the load incrementally to the target level. @@ -2577,30 +2576,32 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel LOG((LF_LOADER, (workLevel == FILE_LOAD_BEGIN - || workLevel == FILE_LOADED - || workLevel == FILE_ACTIVE) - ? LL_INFO10 : LL_INFO1000, - "LOADER: %p:***%s*\t loading at level %s\n", - this, pAssembly->GetSimpleName(), fileLoadLevelName[workLevel])); + || workLevel == FILE_LOADED + || workLevel == FILE_ACTIVE) + ? LL_INFO10 : LL_INFO1000, + "LOADER: %p:***%s*\t loading at level %s\n", + this, simpleName, fileLoadLevelName[workLevel])); - TryIncrementalLoad(pAssembly, workLevel, fileLock); + TryIncrementalLoad(workLevel, fileLock); } } if (pLock->GetLoadLevel() == immediateTargetLevel-1) { LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t<<GetSimpleName(), + simpleName, fileLoadLevelName[immediateTargetLevel-1])); } } LOG((LF_LOADER, LL_INFO100, "LOADER: ***%s*\t<<GetSimpleName(), + simpleName, fileLoadLevelName[pLock->GetLoadLevel()])); - } + pAssembly = pLock->GetAssembly(); + _ASSERTE(pAssembly != nullptr); // We should always be loading to at least FILE_LOAD_ALLOCATE, so the assembly should be created + // There may have been an error stored on the domain file by another thread, or from a previous load pAssembly->ThrowIfError(targetLevel); @@ -2624,7 +2625,7 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel RETURN pAssembly; } -void AppDomain::TryIncrementalLoad(Assembly *pAssembly, FileLoadLevel workLevel, FileLoadLockHolder &lockHolder) +void AppDomain::TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder &lockHolder) { STANDARD_VM_CONTRACT; @@ -2632,11 +2633,38 @@ void AppDomain::TryIncrementalLoad(Assembly *pAssembly, FileLoadLevel workLevel, BOOL released = FALSE; FileLoadLock* pLoadLock = lockHolder.GetValue(); + Assembly* pAssembly = pLoadLock->GetAssembly(); EX_TRY { - // Do the work - BOOL success = pAssembly->DoIncrementalLoad(workLevel); + BOOL success; + if (workLevel == FILE_LOAD_ALLOCATE) + { + // Allocate DomainAssembly & Assembly + PEAssembly *pPEAssembly = pLoadLock->GetPEAssembly(); + AssemblyBinder *pAssemblyBinder = pPEAssembly->GetAssemblyBinder(); + LoaderAllocator *pLoaderAllocator = pAssemblyBinder->GetLoaderAllocator(); + if (pLoaderAllocator == NULL) + pLoaderAllocator = this->GetLoaderAllocator(); + + AllocMemTracker amTracker; + AllocMemTracker *pamTracker = &amTracker; + NewHolder pDomainAssembly = new DomainAssembly(pPEAssembly, pLoaderAllocator, pamTracker); + pLoadLock->SetAssembly(pDomainAssembly->GetAssembly()); + pDomainAssembly->GetAssembly()->SetIsTenured(); + if (pDomainAssembly->GetAssembly()->IsCollectible()) + { + ((AssemblyLoaderAllocator *)pLoaderAllocator)->AddDomainAssembly(pDomainAssembly); + } + pDomainAssembly.SuppressRelease(); + pamTracker->SuppressRelease(); + pAssembly = pLoadLock->GetAssembly(); + success = TRUE; + } + else + { + success = pAssembly->DoIncrementalLoad(workLevel); + } // Complete the level. if (pLoadLock->CompleteLoadLevel(workLevel, success) && @@ -2653,7 +2681,7 @@ void AppDomain::TryIncrementalLoad(Assembly *pAssembly, FileLoadLevel workLevel, //We will cache this error and wire this load to forever fail, // unless the exception is transient or the file is loaded OK but just cannot execute - if (!pEx->IsTransient() && !pAssembly->IsLoaded()) + if (pAssembly != nullptr && !pEx->IsTransient() && !pAssembly->IsLoaded()) { if (released) { diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 8a42c4d7cd213f..c95abd5a6733c2 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -293,14 +293,15 @@ class FileLoadLock : public ListLockEntry { private: FileLoadLevel m_level; - Assembly* m_pAssembly; + Assembly* m_pAssembly; // Will be null until FILE_LOAD_ALLOCATE is completed successfully HRESULT m_cachedHR; public: - static FileLoadLock *Create(PEFileListLock *pLock, PEAssembly *pPEAssembly, Assembly *pAssembly); + static FileLoadLock *Create(PEFileListLock *pLock, PEAssembly *pPEAssembly); ~FileLoadLock(); Assembly *GetAssembly(); + PEAssembly *GetPEAssembly(); FileLoadLevel GetLoadLevel(); // CanAcquire will return FALSE if Acquire will definitely not take the lock due @@ -320,6 +321,9 @@ class FileLoadLock : public ListLockEntry // returns TRUE if it updated load level, FALSE if the level was set already BOOL CompleteLoadLevel(FileLoadLevel level, BOOL success); + // Associate an Assembly with this lock + void SetAssembly(Assembly *pAssembly); + void SetError(Exception *ex); void AddRef(); @@ -327,7 +331,7 @@ class FileLoadLock : public ListLockEntry private: - FileLoadLock(PEFileListLock *pLock, PEAssembly *pPEAssembly, Assembly *pAssembly); + FileLoadLock(PEFileListLock *pLock, PEAssembly *pPEAssembly); static void HolderLeave(FileLoadLock *pThis); @@ -1098,7 +1102,7 @@ class AppDomain final Assembly *LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel); - void TryIncrementalLoad(Assembly *pFile, FileLoadLevel workLevel, FileLoadLockHolder &lockHolder); + void TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder &lockHolder); #ifndef DACCESS_COMPILE // needs AssemblySpec public: diff --git a/src/coreclr/vm/assemblyspec.hpp b/src/coreclr/vm/assemblyspec.hpp index ea94c25dbbfc24..49446800fe7be2 100644 --- a/src/coreclr/vm/assemblyspec.hpp +++ b/src/coreclr/vm/assemblyspec.hpp @@ -27,7 +27,8 @@ enum FileLoadLevel // Note that semantics here are description is the LAST step done, not what is // currently being done. - FILE_LOAD_CREATE, + FILE_LOAD_CREATE, // List entry + FileLoadLock created, no Assembly/DomainAssembly yet + FILE_LOAD_ALLOCATE, // DomainAssembly & Assembly object allocated and associated with the lock FILE_LOAD_BEGIN, FILE_LOAD_BEFORE_TYPE_LOAD, FILE_LOAD_EAGER_FIXUPS, From e21670e9b8a10ec5b75eb469eab10f5d06a73f26 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Mon, 6 Oct 2025 15:14:45 -0700 Subject: [PATCH 2/4] Update moduleload profiler test --- src/tests/profiler/unittest/moduleload.cs | 16 +++++++++++++++- src/tests/profiler/unittest/moduleload.csproj | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tests/profiler/unittest/moduleload.cs b/src/tests/profiler/unittest/moduleload.cs index 17e1312d8b096f..4539597aea52e3 100644 --- a/src/tests/profiler/unittest/moduleload.cs +++ b/src/tests/profiler/unittest/moduleload.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.Loader; namespace Profiler.Tests { @@ -21,13 +23,25 @@ public static int RunTest(string[] args) .DefineDynamicModule("TestModule") .DefineType("TestClass", TypeAttributes.Public) .CreateType(); - + var obj = Activator.CreateInstance(type); if (obj == null) { throw new NullReferenceException(); } + // Trigger module load in multiple threads + int threadCount = 20; + List threads = new(threadCount); + for (int i = 0; i < threadCount; i++) + threads.Add(new Thread(() => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("unloadlibrary")))); + + foreach (var thread in threads) + thread.Start(); + + foreach (var thread in threads) + thread.Join(); + return 100; } diff --git a/src/tests/profiler/unittest/moduleload.csproj b/src/tests/profiler/unittest/moduleload.csproj index e94b734bb5a977..83770db0d9b025 100644 --- a/src/tests/profiler/unittest/moduleload.csproj +++ b/src/tests/profiler/unittest/moduleload.csproj @@ -15,6 +15,7 @@ + From 942598ac1f48c8b7c66a95791192917f58fe2d1e Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 7 Oct 2025 10:58:16 -0700 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Aaron Robinson --- src/coreclr/vm/appdomain.cpp | 12 ++++++------ src/coreclr/vm/appdomain.hpp | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index fd87146d0103fd..1166d840a7a13c 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1871,7 +1871,7 @@ DispIDCache* AppDomain::SetupRefDispIDCache() #endif // FEATURE_COMINTEROP -FileLoadLock *FileLoadLock::Create(PEFileListLock *pLock, PEAssembly * pPEAssembly) +FileLoadLock* FileLoadLock::Create(PEFileListLock* pLock, PEAssembly* pPEAssembly) { CONTRACTL { @@ -1910,7 +1910,7 @@ Assembly *FileLoadLock::GetAssembly() return m_pAssembly; } -PEAssembly *FileLoadLock::GetPEAssembly() +PEAssembly* FileLoadLock::GetPEAssembly() { LIMITED_METHOD_CONTRACT; // Underlying PEAssembly pointer is stored in base ListLockEntry::m_data. @@ -2052,7 +2052,7 @@ BOOL FileLoadLock::CompleteLoadLevel(FileLoadLevel level, BOOL success) return FALSE; } -void FileLoadLock::SetAssembly(Assembly *pAssembly) +void FileLoadLock::SetAssembly(Assembly* pAssembly) { LIMITED_METHOD_CONTRACT; @@ -2110,7 +2110,7 @@ UINT32 FileLoadLock::Release() return count; } -FileLoadLock::FileLoadLock(PEFileListLock *pLock, PEAssembly * pPEAssembly) +FileLoadLock::FileLoadLock(PEFileListLock* pLock, PEAssembly* pPEAssembly) : ListLockEntry(pLock, pPEAssembly, "File load lock"), m_level((FileLoadLevel) (FILE_LOAD_CREATE)), m_pAssembly(nullptr), @@ -2625,7 +2625,7 @@ Assembly *AppDomain::LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel RETURN pAssembly; } -void AppDomain::TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder &lockHolder) +void AppDomain::TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder& lockHolder) { STANDARD_VM_CONTRACT; @@ -2679,7 +2679,7 @@ void AppDomain::TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder & { Exception *pEx = GET_EXCEPTION(); - //We will cache this error and wire this load to forever fail, + // We will cache this error and wire this load to forever fail, // unless the exception is transient or the file is loaded OK but just cannot execute if (pAssembly != nullptr && !pEx->IsTransient() && !pAssembly->IsLoaded()) { diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index c95abd5a6733c2..a58a2314ee0225 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -297,11 +297,11 @@ class FileLoadLock : public ListLockEntry HRESULT m_cachedHR; public: - static FileLoadLock *Create(PEFileListLock *pLock, PEAssembly *pPEAssembly); + static FileLoadLock* Create(PEFileListLock* pLock, PEAssembly* pPEAssembly); ~FileLoadLock(); Assembly *GetAssembly(); - PEAssembly *GetPEAssembly(); + PEAssembly* GetPEAssembly(); FileLoadLevel GetLoadLevel(); // CanAcquire will return FALSE if Acquire will definitely not take the lock due @@ -322,7 +322,7 @@ class FileLoadLock : public ListLockEntry BOOL CompleteLoadLevel(FileLoadLevel level, BOOL success); // Associate an Assembly with this lock - void SetAssembly(Assembly *pAssembly); + void SetAssembly(Assembly* pAssembly); void SetError(Exception *ex); @@ -331,7 +331,7 @@ class FileLoadLock : public ListLockEntry private: - FileLoadLock(PEFileListLock *pLock, PEAssembly *pPEAssembly); + FileLoadLock(PEFileListLock* pLock, PEAssembly* pPEAssembly); static void HolderLeave(FileLoadLock *pThis); @@ -1102,7 +1102,7 @@ class AppDomain final Assembly *LoadAssembly(FileLoadLock *pLock, FileLoadLevel targetLevel); - void TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder &lockHolder); + void TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder& lockHolder); #ifndef DACCESS_COMPILE // needs AssemblySpec public: From 128fdea554702049a21bb508169abedc4cc42055 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 7 Oct 2025 11:20:43 -0700 Subject: [PATCH 4/4] PR feedback - asserts --- src/coreclr/vm/appdomain.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 1166d840a7a13c..f7d01fd809a59f 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -1913,7 +1913,8 @@ Assembly *FileLoadLock::GetAssembly() PEAssembly* FileLoadLock::GetPEAssembly() { LIMITED_METHOD_CONTRACT; - // Underlying PEAssembly pointer is stored in base ListLockEntry::m_data. + // Underlying PEAssembly pointer is stored in the constructor in base ListLockEntry::m_data. + _ASSERTE(m_data != NULL); return (PEAssembly*)m_data; } @@ -2640,6 +2641,9 @@ void AppDomain::TryIncrementalLoad(FileLoadLevel workLevel, FileLoadLockHolder& BOOL success; if (workLevel == FILE_LOAD_ALLOCATE) { + // FileLoadLock should not have an assembly yet + _ASSERTE(pAssembly == NULL); + // Allocate DomainAssembly & Assembly PEAssembly *pPEAssembly = pLoadLock->GetPEAssembly(); AssemblyBinder *pAssemblyBinder = pPEAssembly->GetAssemblyBinder();