Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException - #126782

Merged
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics
May 13, 2026
Merged

Propagate actual error info when loading a TPA assembly fails with FileNotFoundException#126782
elinor-fung merged 21 commits into
dotnet:mainfrom
elinor-fung:copilot/improve-fnfe-diagnostics

Conversation

@elinor-fung

@elinor-fungelinor-fung commented Apr 11, 2026

Copy link
Copy Markdown
Member

Note

This PR was generated with the assistance of GitHub Copilot.

Today, when a TPA assembly fails to load (file locked, corrupt, missing), the exception says only Could not load file or assembly 'X'. The system cannot find the file specified. — with no indication of the actual file path, the step that failed, or the original error code.

This pull request propagates the actual error when a TPA assembly fails to load, including the path and the error code. This populates the existing FusionLog property, which is currently always empty. The name is questionable, but it is supposedly for more diagnostic info on failure - not sure if we should leave it be and use a new private member instead.

Key changes include:

Diagnostic Information Propagation

  • Added a pDiagnosticInfo parameter to key methods in the assembly binding pipeline, including BindAssembly, BindByTpaList, GetAssembly, and related helpers in assemblybindercommon.cpp, customassemblybinder.cpp, and defaultassemblybinder.cpp. This allows diagnostic messages to be collected and passed along during binding attempts.
  • Enhanced GetAssembly to append human-readable error messages to pDiagnosticInfo when assembly initialization fails, using resource-based formatting and HRESULT decoding.

Failure Cache Improvements

  • Modified the failure cache (FailureCache::Add and related calls) to accept and store diagnostic information alongside the binding result, ensuring that failure entries can provide richer context.
  • Updated failure cache lookups to optionally return diagnostic information as part of the cache hit.

Exception Handling Enhancements

  • Extended the FileNotFoundException constructor to accept a diagnosticInfo string, which is stored in the FusionLog property, and updated exception creation logic to use this new constructor when diagnostic info is available.
// before
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
// after
System.IO.FileNotFoundException: Could not load file or assembly 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
File name: 'TpaLibLocked, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.UseTpaLibLocked() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 177
at Program.TpaScenario_Locked(String dllPath) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 222
at Program.<>c__DisplayClass2_0.<Main>b__0() in C:\repro\FnfeDiagnosticRepro\Program.cs:line 35
at Program.RunScenario(String name, Action action) in C:\repro\FnfeDiagnosticRepro\Program.cs:line 56
Failed to open file 'C:\repro\FnfeDiagnosticRepro\bin\Release\net10.0\TpaLibLocked.dll'. The process cannot access the file because it is being used by another process. (0x80070020)

cc @dotnet/appmodel @AaronRobinsonMSFT

elinor-fungand others added 6 commits April 9, 2026 21:19
Thread diagnostic context through the assembly binding pipeline so that
FileNotFoundException carries actionable information about what failed
and where, instead of just a generic message.
Key changes:
- Populate FusionLog on FileNotFoundException with binding diagnostic
info (file open errors, metadata read failures, cached failure replays)
- Preserve original Win32 error through BinderAcquirePEImage instead of
discarding it via EX_CATCH_HRESULT
- Fix TryOpenFile to return ERROR_OPEN_FAILED (not ERROR_FILE_NOT_FOUND)
when GetLastError() is zero, making the error distinguishable
- Extend EEFileLoadException with m_diagnosticInfo field and thread it
through CreateThrowable to the managed FileLoadException.Create bridge
- Extend FailureCacheEntry with diagnostic context so cached failure
replays include the original error info with a '(Cached)' prefix
- Thread SString* pDiagnosticInfo through the bind chain:
BindAssembly -> BindByName -> BindLocked -> BindByTpaList -> GetAssembly
- All diagnostic message format strings are in mscorrc.rc resources
All diagnostic string formatting occurs only in error paths. The happy
path has zero additional allocations or string work.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Thread SString* pDiagnosticInfo through:
AssemblyBinder::BindAssemblyByName
-> BindUsingAssemblyName (virtual, Default + Custom overrides)
-> BindAssemblyByNameWorker
-> AssemblyBinderCommon::BindAssembly
-> AssemblySpec::Bind
-> AppDomain::BindAssemblySpec
Add EEFileLoadException::Throw overload accepting diagnostic SString.
In AppDomain::BindAssemblySpec, pass StackSString to Bind() and use it
at the EEFileLoadException::Throw site so the diagnostic info reaches
FileNotFoundException.FusionLog on the managed side.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restore the original ERROR_FILE_NOT_FOUND fallback when GetLastError()
is zero. The GetLastError() caching fix (using a local variable) is
kept.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace FormatMessage (%1, %2) with Printf-style (%s, %08x) format
strings in mscorrc.rc resources. Use AppendPrintf instead of
FormatMessage/Set so diagnostic info accumulates across multiple
failure points rather than overwriting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use GetHRMsg to convert HRESULTs to human-readable messages (e.g.
'The process cannot access the file because it is being used by
another process') instead of raw hex codes. Update cached failure
prefix to '[cached failure]'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add TpaBindFailureTest with three scenarios, each using a dedicated TPA
assembly referenced as a normal ProjectReference:
- TpaAssembly_NotFound: delete the DLL, use the type via [NoInlining]
wrapper, verify exception string contains the assembly path
- TpaAssembly_SharingViolation: lock the DLL exclusively, use the type,
verify exception string contains the file path and HRESULT 80070020
- TpaAssembly_Corrupt: overwrite with garbage bytes, use the type,
verify exception string contains the file path
Each scenario triggers the load via direct type usage (not explicit
Assembly.Load) to exercise the realistic JIT/type-system load path
through BindByTpaList.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves diagnostics when assembly loading fails by threading native binder failure context through the VM into managed FileNotFoundException.FusionLog, making it clearer which step failed, which path was involved, and which HRESULT caused the failure.

Changes:

  • Thread a diagnostic string through the native binder pipeline (including failure caching) and into EEFileLoadException, then across the unmanaged→managed bridge.
  • Populate FileNotFoundException.FusionLog from native diagnostic context (including cached-failure labeling).
  • Add System.Runtime.Loader tests and support assemblies to validate path/HRESULT presence for missing/locked/corrupt TPA scenarios.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
src/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.csAdds new tests asserting exception output includes failing path + HRESULT for key TPA failure modes.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Tests.csprojWires in new test + support projects; deletes “Missing/Corrupt” DLLs post-build so they stay in deps/TPA but not on disk.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/TestClass.csSupport assembly used to trigger “missing on disk” bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Missing/System.Runtime.Loader.Test.BindFailure.Missing.csprojDefines the “Missing” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/TestClass.csSupport assembly used to trigger sharing-violation bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Locked/System.Runtime.Loader.Test.BindFailure.Locked.csprojDefines the “Locked” support project.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/TestClass.csSupport assembly used to trigger corrupt-image bind failure.
src/libraries/System.Runtime.Loader/tests/System.Runtime.Loader.Test.BindFailure.Corrupt/System.Runtime.Loader.Test.BindFailure.Corrupt.csprojDefines the “Corrupt” support project.
src/libraries/System.Private.CoreLib/src/System/IO/FileNotFoundException.csMakes FusionLog internally settable so CoreLib can attach diagnostics.
src/coreclr/System.Private.CoreLib/src/System/IO/FileNotFoundException.CoreCLR.csAdds internal ctor that accepts diagnostic info and assigns it to FusionLog.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.csExtends unmanaged bridge to accept a diagnostic string pointer and pass it into FileNotFoundException.
src/coreclr/vm/clrex.hExtends EEFileLoadException to carry a diagnostic SString and adds a Throw overload.
src/coreclr/vm/clrex.cppPasses diagnostic info across the FileLoadException.Create unmanaged-caller boundary; adds ctor/Throw overload implementation.
src/coreclr/vm/appdomain.cppThreads diagnostic info through AssemblySpec::Bind and uses it when throwing EEFileLoadException for file-not-found binds.
src/coreclr/vm/assemblyspec.hppUpdates AssemblySpec::Bind signature to optionally accept diagnostic info.
src/coreclr/vm/coreassemblyspec.cppCaptures open-file failures/exceptions with path + HRESULT/message into the diagnostic string.
src/coreclr/vm/assemblybinder.hExtends binder APIs to optionally accept diagnostic info.
src/coreclr/vm/assemblybinder.cppForwards diagnostic info through BindAssemblyByName to BindUsingAssemblyName.
src/coreclr/vm/peimage.cppAvoids double GetLastError() call when converting to HRESULT.
src/coreclr/dlls/mscorrc/resource.hAdds resource IDs for new binder diagnostic format strings.
src/coreclr/dlls/mscorrc/mscorrc.rcAdds diagnostic format strings used by the binder for FusionLog content.
src/coreclr/binder/inc/assemblybindercommon.hppThreads SString* pDiagnosticInfo through binder common APIs.
src/coreclr/binder/assemblybindercommon.cppRecords metadata init failures into diagnostics and propagates diagnostic info through bind steps.
src/coreclr/binder/inc/failurecachehashtraits.hppExtends FailureCacheEntry to store diagnostic info with cached failures.
src/coreclr/binder/inc/failurecache.hppExtends failure cache APIs to store/return diagnostic strings.
src/coreclr/binder/failurecache.cppStores diagnostic info in the failure cache and prefixes diagnostics when replaying cached failures.
src/coreclr/binder/inc/applicationcontext.hppExtends failure-cache integration to accept diagnostic info.
src/coreclr/binder/inc/applicationcontext.inlForwards diagnostic info into failure cache add.
src/coreclr/binder/inc/defaultassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/defaultassemblybinder.cppPropagates diagnostic info through default binder bind calls.
src/coreclr/binder/inc/customassemblybinder.hExtends BindUsingAssemblyName/worker signatures with optional diagnostic info.
src/coreclr/binder/customassemblybinder.cppPropagates diagnostic info through custom binder bind calls.

Comment threadsrc/coreclr/vm/coreassemblyspec.cpp
Comment threadsrc/coreclr/binder/assemblybindercommon.cpp Outdated
Comment threadsrc/coreclr/binder/failurecache.cpp
Get-only auto-properties can be set from constructors even across
partial class files, so no setter is needed. This preserves the
readonly backing field semantics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from 39ee0e5 to 7976c56CompareApril 11, 2026 02:57
CopilotAI review requested due to automatic review settings April 11, 2026 02:57

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/coreclr/vm/appdomain.cpp Outdated
- Make the diagnosticInfo constructor delegate to the base constructor
instead of duplicating its body.
- Remove the unnecessary IsEmpty check at the throw site in
AppDomain::BindAssemblySpec — always pass bindDiagnosticInfo since
CreateThrowable already gates on m_diagnosticInfo.IsEmpty().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fungelinor-fung changed the title Improve FileNotFoundException diagnostics in assembly loadingImprove FileNotFoundException diagnostics when loading a TPA assembly failsApr 11, 2026
CopilotAI review requested due to automatic review settings April 11, 2026 04:49
elinor-fungand others added 2 commits April 10, 2026 21:54
When multiple probing attempts contribute diagnostics (e.g., bundle
probe then TPA probe, or cached failure replay), insert a newline
before appending each new message so FusionLog is readable.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the diagnostic info right after pAssembly->Init fails instead
of in the shared Exit path. This makes the error attribution clearer
and avoids the else-if coupling with the IsFileNotFound normalization.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@elinor-fung
elinor-fungforce-pushed the copilot/improve-fnfe-diagnostics branch from c1416b4 to bdc3496CompareApril 11, 2026 04:56

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
Comment threadsrc/libraries/System.Runtime.Loader/tests/TpaLoadFailureTest.cs Outdated
The Missing and Corrupt assemblies are deleted by the
RemoveBindFailureTestAssemblies MSBuild target after build, not
by Private=false. Update comments to reflect this.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment threadsrc/coreclr/binder/inc/bindresult.hpp Outdated
Comment threadsrc/coreclr/vm/clrex.cpp
Address PR review feedback: replace 'SString& GetDiagnosticInfo()' that
shared a private SString field with a read accessor returning LPCWSTR
and an AppendDiagnosticInfo setter. Callers now build their own
SStrings and append into BindResult, so leaf helpers no longer need to
prepend newline separators.
Also remove an extra blank line in clrex.cpp.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @elinor-fung
See info in area-owners.md if you want to be subscribed.

elinor-fungand others added 2 commits May 1, 2026 12:15
…e-diagnostics
# Conflicts:
#	src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs
#	src/coreclr/vm/clrex.cpp
#	src/coreclr/vm/clrex.h
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings May 1, 2026 20:25

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/inc/failurecachehashtraits.hpp
Comment threadsrc/coreclr/binder/failurecache.cpp Outdated
CopilotAI review requested due to automatic review settings May 12, 2026 21:36

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 34/34 changed files
  • Comments generated: 1

Comment threadsrc/coreclr/binder/failurecache.cpp
@elinor-fung
elinor-fung merged commit c1edd14 into dotnet:mainMay 13, 2026
159 of 162 checks passed
@elinor-fung
elinor-fung deleted the copilot/improve-fnfe-diagnostics branch May 13, 2026 21:38
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 13, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@elinor-fung@AaronRobinsonMSFT@teo-tsirpanis