Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
7e896cd
Improve FileNotFoundException diagnostics in assembly loading
elinor-fung Apr 10, 2026
6503066
Wire diagnostic info through full bind chain to exception throw sites
elinor-fung Apr 10, 2026
1be58e3
Revert TryOpenFile ERROR_OPEN_FAILED change
elinor-fung Apr 10, 2026
cc0c5be
Switch diagnostic strings to Printf format and append mode
elinor-fung Apr 10, 2026
ec7a60c
Include HRESULT message in diagnostic info and update cached prefix
elinor-fung Apr 10, 2026
c6dc939
Add tests for TPA assembly load failures
elinor-fung Apr 10, 2026
7976c56
Change FusionLog back to get-only auto-property
elinor-fung Apr 11, 2026
96228a0
Simplify EEFileLoadException: delegating ctor and throw site
elinor-fung Apr 11, 2026
f4ce4b9
Add newline separators between diagnostic messages
elinor-fung Apr 11, 2026
bdc3496
Move Init failure diagnostic next to the Init call
elinor-fung Apr 11, 2026
42d636d
Fix test comments to match actual build mechanism
elinor-fung Apr 11, 2026
db4fdb2
Fix include ordering and use delegating constructor
elinor-fung Apr 15, 2026
a298c54
Address PR review feedback for tests
elinor-fung Apr 15, 2026
275d271
Fix test failures: ensure DLLs removed before test payload packaging
elinor-fung Apr 16, 2026
f8981fa
Move diagnostic info into BindResult struct
elinor-fung Apr 22, 2026
ca68101
Merge remote-tracking branch 'upstream/main' into copilot/improve-fnf…
elinor-fung Apr 24, 2026
c6eecd3
Take diagnostic info by const SString& in failure cache Add
elinor-fung Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,17 @@ internal enum FileLoadExceptionKind
}

[UnmanagedCallersOnly]
internal static unsafe void Create(FileLoadExceptionKind kind, char* pFileName, int hresult, object* pThrowable, Exception* pException)
internal static unsafe void Create(FileLoadExceptionKind kind, char* pFileName, int hresult, char* pDiagnosticInfo, object* pThrowable, Exception* pException)
{
Comment thread
elinor-fung marked this conversation as resolved.
try
{
string? fileName = pFileName is not null ? new string(pFileName) : null;
string? diagnosticInfo = pDiagnosticInfo is not null ? new string(pDiagnosticInfo) : null;
Debug.Assert(Enum.IsDefined(kind));
*pThrowable = kind switch
{
FileLoadExceptionKind.BadImageFormat => new BadImageFormatException(fileName, hresult),
FileLoadExceptionKind.FileNotFound => new FileNotFoundException(fileName, hresult),
FileLoadExceptionKind.FileNotFound => new FileNotFoundException(fileName, hresult, diagnosticInfo),
FileLoadExceptionKind.OutOfMemory => new OutOfMemoryException(),
_ /* FileLoadExceptionKind.FileLoad */ => new FileLoadException(fileName, hresult),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ internal FileNotFoundException(string? fileName, int hResult)
FileName = fileName;
SetMessageField();
}

internal FileNotFoundException(string? fileName, int hResult, string? diagnosticInfo)
: this(fileName, hResult)
{
FusionLog = diagnosticInfo;
Comment thread
elinor-fung marked this conversation as resolved.
}
}
}
44 changes: 35 additions & 9 deletions src/coreclr/binder/assemblybindercommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ extern HRESULT RuntimeInvokeHostAssemblyResolver(INT_PTR pAssemblyLoadContextToB

STDAPI BinderAcquirePEImage(LPCTSTR szAssemblyPath,
PEImage** ppPEImage,
ProbeExtensionResult probeExtensionResult);
ProbeExtensionResult probeExtensionResult,
SString *pDiagnosticInfo = NULL);

namespace BINDER_SPACE
{
Expand Down Expand Up @@ -193,7 +194,8 @@ namespace BINDER_SPACE
/* in */ AssemblyName *pAssemblyName,
/* in */ bool excludeAppPaths,
/* out */ Assembly **ppAssembly,
/* [out, optional] */ Assembly **ppExistingAssemblyOnFailure)
/* [out, optional] */ Assembly **ppExistingAssemblyOnFailure,
/* out */ SString *pDiagnosticInfo)
{
HRESULT hr = S_OK;
LONG kContextVersion = 0;
Expand Down Expand Up @@ -225,6 +227,11 @@ namespace BINDER_SPACE
Exit:
tracer.TraceBindResult(bindResult);

if (pDiagnosticInfo != NULL && !bindResult.GetDiagnosticInfo().IsEmpty())
{
pDiagnosticInfo->Append(bindResult.GetDiagnosticInfo());
}

if (bindResult.HaveResult())
{
BindResult hostBindResult;
Expand Down Expand Up @@ -405,7 +412,7 @@ namespace BINDER_SPACE
pAssemblyName->GetDisplayName(assemblyDisplayName,
AssemblyName::INCLUDE_VERSION);

hr = pApplicationContext->GetFailureCache()->Lookup(assemblyDisplayName);
hr = pApplicationContext->GetFailureCache()->Lookup(assemblyDisplayName, &pBindResult->GetDiagnosticInfo());
if (FAILED(hr))
{
if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) && skipFailureCaching)
Expand Down Expand Up @@ -459,7 +466,7 @@ namespace BINDER_SPACE
}
}

hr = pApplicationContext->AddToFailureCache(assemblyDisplayName, hr);
hr = pApplicationContext->AddToFailureCache(assemblyDisplayName, hr, pBindResult->GetDiagnosticInfo());
}

LogExit:
Expand Down Expand Up @@ -869,7 +876,8 @@ namespace BINDER_SPACE
hr = GetAssembly(assemblyFilePath,
TRUE, // fIsInTPA
&pTPAAssembly,
probeExtensionResult);
probeExtensionResult,
&pBindResult->GetDiagnosticInfo());

BinderTracing::PathProbed(assemblyFilePath, BinderTracing::PathSource::Bundle, hr);

Expand Down Expand Up @@ -899,7 +907,9 @@ namespace BINDER_SPACE

hr = GetAssembly(fileName,
TRUE, // fIsInTPA
&pTPAAssembly);
&pTPAAssembly,
ProbeExtensionResult::Invalid(),
&pBindResult->GetDiagnosticInfo());
BinderTracing::PathProbed(fileName, BinderTracing::PathSource::ApplicationAssemblies, hr);

pBindResult->SetAttemptResult(hr, pTPAAssembly);
Expand Down Expand Up @@ -985,7 +995,8 @@ namespace BINDER_SPACE
HRESULT AssemblyBinderCommon::GetAssembly(SString &assemblyPath,
BOOL fIsInTPA,
Assembly **ppAssembly,
ProbeExtensionResult probeExtensionResult)
ProbeExtensionResult probeExtensionResult,
SString *pDiagnosticInfo)
{
HRESULT hr = S_OK;

Expand All @@ -1001,12 +1012,27 @@ namespace BINDER_SPACE
{
LPCTSTR szAssemblyPath = const_cast<LPCTSTR>(assemblyPath.GetUnicode());

hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, probeExtensionResult);
hr = BinderAcquirePEImage(szAssemblyPath, &pPEImage, probeExtensionResult, pDiagnosticInfo);
IF_FAIL_GO(hr);
}

// Initialize assembly object
IF_FAIL_GO(pAssembly->Init(pPEImage, fIsInTPA));
hr = pAssembly->Init(pPEImage, fIsInTPA);
if (FAILED(hr))
{
if (pDiagnosticInfo != NULL)
{
if (!pDiagnosticInfo->IsEmpty())
pDiagnosticInfo->AppendUTF8("\n");

StackSString format;
format.LoadResource(IDS_BINDING_FAILED_TO_INIT_ASSEMBLY);
StackSString hrMsg;
GetHRMsg(hr, hrMsg);
pDiagnosticInfo->AppendPrintf(format.GetUTF8(), assemblyPath.GetUTF8(), hrMsg.GetUTF8());
}
goto Exit;
}

// We're done
*ppAssembly = pAssembly.Extract();
Expand Down
12 changes: 8 additions & 4 deletions src/coreclr/binder/customassemblybinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ using namespace BINDER_SPACE;
// CustomAssemblyBinder implementation
// ============================================================================
HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName,
BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly)
BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly,
SString *pDiagnosticInfo)
{
VALIDATE_ARG_RET(pAssemblyName != nullptr && ppCoreCLRFoundAssembly != nullptr);
HRESULT hr = S_OK;
Expand All @@ -28,7 +29,9 @@ HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNam
hr = AssemblyBinderCommon::BindAssembly(this,
pAssemblyName,
false, //excludeAppPaths,
ppCoreCLRFoundAssembly);
ppCoreCLRFoundAssembly,
nullptr /* ppExistingAssemblyOnFailure */,
pDiagnosticInfo);
if (!FAILED(hr))
{
_ASSERTE(*ppCoreCLRFoundAssembly != NULL);
Expand All @@ -39,7 +42,8 @@ HRESULT CustomAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNam
}

HRESULT CustomAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName,
BINDER_SPACE::Assembly** ppAssembly)
BINDER_SPACE::Assembly** ppAssembly,
SString* pDiagnosticInfo)
{
// When LoadContext needs to resolve an assembly reference, it will go through the following lookup order:
//
Expand All @@ -58,7 +62,7 @@ HRESULT CustomAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName*

{
// Step 1 - Try to find the assembly within the LoadContext.
hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly);
hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, pDiagnosticInfo);
if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) ||
(hr == FUSION_E_APP_DOMAIN_LOCKED) || (hr == FUSION_E_REF_DEF_MISMATCH))
{
Expand Down
11 changes: 7 additions & 4 deletions src/coreclr/binder/defaultassemblybinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ using namespace BINDER_SPACE;
HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName,
BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly,
bool excludeAppPaths,
BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure)
BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure,
SString *pDiagnosticInfo)
{
VALIDATE_ARG_RET(pAssemblyName != nullptr && ppCoreCLRFoundAssembly != nullptr);
HRESULT hr = S_OK;
Expand All @@ -28,7 +29,8 @@ HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNa
pAssemblyName,
excludeAppPaths,
ppCoreCLRFoundAssembly,
ppExistingAssemblyOnFailure);
ppExistingAssemblyOnFailure,
pDiagnosticInfo);
if (!FAILED(hr))
{
(*ppCoreCLRFoundAssembly)->SetBinder(this);
Expand All @@ -46,7 +48,8 @@ HRESULT DefaultAssemblyBinder::BindAssemblyByNameWorker(BINDER_SPACE::AssemblyNa
// DefaultAssemblyBinder implementation
// ============================================================================
HRESULT DefaultAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName *pAssemblyName,
BINDER_SPACE::Assembly **ppAssembly)
BINDER_SPACE::Assembly **ppAssembly,
SString *pDiagnosticInfo)
{
HRESULT hr = S_OK;
VALIDATE_ARG_RET(pAssemblyName != nullptr && ppAssembly != nullptr);
Expand All @@ -55,7 +58,7 @@ HRESULT DefaultAssemblyBinder::BindUsingAssemblyName(BINDER_SPACE::AssemblyName

ReleaseHolder<BINDER_SPACE::Assembly> pCoreCLRFoundAssembly;

hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, false /* excludeAppPaths */);
hr = BindAssemblyByNameWorker(pAssemblyName, &pCoreCLRFoundAssembly, false /* excludeAppPaths */, nullptr /* ppExistingAssemblyOnFailure */, pDiagnosticInfo);

#if !defined(DACCESS_COMPILE)
if ((hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) ||
Expand Down
20 changes: 18 additions & 2 deletions src/coreclr/binder/failurecache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//
// ============================================================

#include "common.h"
#include "failurecache.hpp"

namespace BINDER_SPACE
Expand All @@ -32,7 +33,8 @@ namespace BINDER_SPACE
}

HRESULT FailureCache::Add(SString &assemblyNameorPath,
HRESULT hrBindingResult)
HRESULT hrBindingResult,
const SString &diagnosticInfo)
{
HRESULT hr = S_OK;

Expand All @@ -44,6 +46,10 @@ namespace BINDER_SPACE

pFailureCacheEntry->GetAssemblyNameOrPath().Set(assemblyNameorPath);
pFailureCacheEntry->SetBindingResult(hrBindingResult);
if (!diagnosticInfo.IsEmpty())
{
pFailureCacheEntry->SetDiagnosticInfo(diagnosticInfo);
}

Hash::Add(pFailureCacheEntry);
pFailureCacheEntry.SuppressRelease();
Expand All @@ -52,14 +58,24 @@ namespace BINDER_SPACE
return hr;
}

HRESULT FailureCache::Lookup(SString &assemblyNameorPath)
HRESULT FailureCache::Lookup(SString &assemblyNameorPath,
SString *pDiagnosticInfo)
{
HRESULT hr = S_OK;
FailureCacheEntry *pFailureCachEntry = Hash::Lookup(assemblyNameorPath);

if (pFailureCachEntry != NULL)
{
hr = pFailureCachEntry->GetBindingResult();
if (pDiagnosticInfo != NULL && !pFailureCachEntry->GetDiagnosticInfo().IsEmpty())
{
if (!pDiagnosticInfo->IsEmpty())
pDiagnosticInfo->AppendUTF8("\n");

StackSString format;
format.LoadResource(IDS_BINDING_CACHED_FAILURE_PREFIX);
pDiagnosticInfo->AppendPrintf(format.GetUTF8(), pFailureCachEntry->GetDiagnosticInfo().GetUTF8());
}
Comment thread
elinor-fung marked this conversation as resolved.
}

return hr;
Expand Down
3 changes: 2 additions & 1 deletion src/coreclr/binder/inc/applicationcontext.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ namespace BINDER_SPACE
inline ExecutionContext *GetExecutionContext();
inline FailureCache *GetFailureCache();
inline HRESULT AddToFailureCache(SString &assemblyNameOrPath,
HRESULT hrBindResult);
HRESULT hrBindResult,
const SString &diagnosticInfo);
inline StringArrayList *GetAppPaths();
inline SimpleNameToFileNameMap *GetTpaList();
inline StringArrayList *GetPlatformResourceRoots();
Expand Down
5 changes: 3 additions & 2 deletions src/coreclr/binder/inc/applicationcontext.inl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ FailureCache *ApplicationContext::GetFailureCache()
}

HRESULT ApplicationContext::AddToFailureCache(SString &assemblyNameOrPath,
HRESULT hrBindResult)
HRESULT hrBindResult,
const SString &diagnosticInfo)
{
HRESULT hr = GetFailureCache()->Add(assemblyNameOrPath, hrBindResult);
HRESULT hr = GetFailureCache()->Add(assemblyNameOrPath, hrBindResult, diagnosticInfo);
IncrementVersion();
return hr;
}
Expand Down
6 changes: 4 additions & 2 deletions src/coreclr/binder/inc/assemblybindercommon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ namespace BINDER_SPACE
/* in */ AssemblyName *pAssemblyName,
/* in */ bool excludeAppPaths,
/* out */ Assembly **ppAssembly,
/* [out, optional] */ Assembly **ppExistingAssemblyOnFailure = nullptr);
/* [out, optional] */ Assembly **ppExistingAssemblyOnFailure = nullptr,
/* out */ SString *pDiagnosticInfo = NULL);

static HRESULT BindToSystem(/* in */ SString &systemDirectory,
/* out */ Assembly **ppSystemAssembly);
Expand All @@ -45,7 +46,8 @@ namespace BINDER_SPACE
static HRESULT GetAssembly(/* in */ SString &assemblyPath,
/* in */ BOOL fIsInTPA,
/* out */ Assembly **ppAssembly,
/* in */ ProbeExtensionResult probeExtensionResult = ProbeExtensionResult::Invalid());
/* in */ ProbeExtensionResult probeExtensionResult = ProbeExtensionResult::Invalid(),
/* out */ SString *pDiagnosticInfo = NULL);

#if !defined(DACCESS_COMPILE)
static HRESULT BindUsingHostAssemblyResolver (/* in */ INT_PTR pAssemblyLoadContextToBindWithin,
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/binder/inc/bindresult.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ namespace BINDER_SPACE

const AttemptResult* GetAttempt(bool foundInContext) const;

SString& GetDiagnosticInfo() { return m_diagnosticInfo; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid this abstract? I'd imagine a method to return a LPCWSTR and then one to set the current SString. Sharing out a private SString field is a long standing bad thing.


protected:
bool m_isContextBound;
ReleaseHolder<Assembly> m_pAssembly;

AttemptResult m_inContextAttempt;
AttemptResult m_applicationAssembliesAttempt;

SString m_diagnosticInfo;
};
};

Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/binder/inc/customassemblybinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CustomAssemblyBinder final : public AssemblyBinder
BINDER_SPACE::Assembly** ppExistingAssemblyOnConflict = nullptr) override;

HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName,
BINDER_SPACE::Assembly** ppAssembly) override;
BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL) override;

AssemblyLoaderAllocator* GetLoaderAllocator() override;

Expand All @@ -49,7 +49,7 @@ class CustomAssemblyBinder final : public AssemblyBinder
void ReleaseLoadContext();

private:
HRESULT BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly);
HRESULT BindAssemblyByNameWorker(BINDER_SPACE::AssemblyName *pAssemblyName, BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly, SString *pDiagnosticInfo = NULL);

DefaultAssemblyBinder *m_pDefaultBinder;

Expand Down
5 changes: 3 additions & 2 deletions src/coreclr/binder/inc/defaultassemblybinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class DefaultAssemblyBinder final : public AssemblyBinder
BINDER_SPACE::Assembly** ppExistingAssemblyOnConflict = nullptr) override;

HRESULT BindUsingAssemblyName(BINDER_SPACE::AssemblyName* pAssemblyName,
BINDER_SPACE::Assembly** ppAssembly) override;
BINDER_SPACE::Assembly** ppAssembly, SString* pDiagnosticInfo = NULL) override;

AssemblyLoaderAllocator* GetLoaderAllocator() override
{
Expand All @@ -48,7 +48,8 @@ class DefaultAssemblyBinder final : public AssemblyBinder
BINDER_SPACE::AssemblyName *pAssemblyName,
BINDER_SPACE::Assembly **ppCoreCLRFoundAssembly,
bool excludeAppPaths,
BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure = nullptr);
BINDER_SPACE::Assembly **ppExistingAssemblyOnFailure = nullptr,
SString *pDiagnosticInfo = NULL);
};

#endif // __DEFAULT_ASSEMBLY_BINDER_H__
6 changes: 4 additions & 2 deletions src/coreclr/binder/inc/failurecache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ namespace BINDER_SPACE
~FailureCache();

HRESULT Add(/* in */ SString &assemblyNameorPath,
/* in */ HRESULT hrBindResult);
HRESULT Lookup(/* in */ SString &assemblyNameorPath);
/* in */ HRESULT hrBindResult,
/* in */ const SString &diagnosticInfo);
HRESULT Lookup(/* in */ SString &assemblyNameorPath,
/* out */ SString *pDiagnosticInfo = NULL);
void Remove(/* in */ SString &assemblyName);
};
};
Expand Down
Loading
Loading