Include requesting assembly chain in assembly load failure exceptions#125795
Include requesting assembly chain in assembly load failure exceptions#125795
Conversation
When an assembly fails to load, the FileLoadException/FileNotFoundException now includes the name of the requesting (parent) assembly in the FusionLog property. This helps diagnose dependency chain issues by showing which assembly triggered the failed load. The information flows from the native AssemblySpec::GetParentAssembly() through EEFileLoadException::CreateThrowable() to the managed exception's FusionLog property, which is displayed in ToString() output. Fixes #9185 Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
BadImageFormatException is also created by EEFileLoadException::CreateThrowable() when the HRESULT maps to kBadImageFormatException. Without the 3-arg constructor, the requesting assembly info would be silently dropped for bad image format errors. Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
|
@copilot we should include the full requesting assembly chain instead of just the parent. |
Walk the inner exception chain to build the full dependency path. When A→B→C→D and D fails, D's FusionLog now shows: Requesting assembly: 'C' Requesting assembly: 'B' Requesting assembly: 'A' At each Throw(AssemblySpec*) level, the current parent assembly name is appended to every inner EEFileLoadException's chain. The managed FormatRequestingAssemblyChain helper formats each newline-separated name with the "Requesting assembly:" resource string. Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
Updated in 228155e. Now at each The managed |
…dd tests The requesting assembly chain is now appended to the exception's Message property instead of FusionLog. This means users see the chain directly in the exception message when it is thrown, without needing to inspect FusionLog separately. Added tests for all three exception types (FileLoadException, FileNotFoundException, BadImageFormatException) that verify: - Single requesting assembly appears in Message - Multiple requesting assemblies (chain) appear in Message - FusionLog remains null Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
…missing transitive dependency Remove the 6 reflection-based tests that invoked private constructors via reflection from BadImageFormatExceptionTests, FileLoadExceptionTests, and FileNotFoundExceptionTests. Add an integration test in System.Runtime.Loader.Tests that creates a real dependency chain (Root -> Mid -> Leaf) where the transitive dependency (Leaf) is missing at runtime. The test verifies that a FileNotFoundException is thrown with the correct missing assembly name when the chain is exercised. Three helper assembly projects are added: - MissingDependency.Leaf (not deployed via PrivateAssets=all) - MissingDependency.Mid (depends on Leaf) - MissingDependency.Root (depends on Mid) Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
The test now asserts that the FileNotFoundException message contains "MissingDependency.Mid" (the requesting assembly), verifying that the entire dependency chain is reported in the exception message, not just the missing assembly file name. Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
The test now asserts that both MissingDependency.Mid and MissingDependency.Root appear in the exception message, verifying the entire dependency chain (Root → Mid → Leaf) is reported. Co-authored-by: elinor-fung <47805090+elinor-fung@users.noreply.github.com>
| @@ -77,6 +78,9 @@ public override string ToString() | |||
| if (!string.IsNullOrEmpty(FileName)) | |||
| s += Environment.NewLineConst + SR.Format(SR.IO_FileName_Name, FileName); | |||
|
|
|||
| if (!string.IsNullOrEmpty(_requestingAssemblyChain)) | |||
| s += Environment.NewLineConst + SR.Format(SR.IO_FileLoad_RequestedBy, _requestingAssemblyChain.ReplaceLineEndings()); | |||
|
|
|||
There was a problem hiding this comment.
The new _requestingAssemblyChain field is appended in ToString(), but it isn’t preserved across exception serialization/deserialization (the SerializationInfo ctor and GetObjectData only round-trip FileName/FusionLog). Since this type is still [Serializable] and already maintains custom serialization fields, please include the requesting assembly chain in the serialization payload and restore it in the serialization ctor so diagnostics survive remoting/logging scenarios that serialize exceptions.
This comment has been minimized.
This comment has been minimized.
This contradicts what we have just done in #126428 . We should be consistent - either keep updating the binary serializers or not at all. |
This comment has been minimized.
This comment has been minimized.
Restore the requesting assembly chain across legacy exception serialization round-trips for FileLoadException, FileNotFoundException, and BadImageFormatException, keeping the behavior consistent with other updated binary serializer paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| { | ||
| FileName = info.GetString("FileNotFound_FileName"); | ||
| FusionLog = info.GetString("FileNotFound_FusionLog"); | ||
| _requestingAssemblyChain = info.GetString("FileNotFound_RequestingAssemblyChain"); |
There was a problem hiding this comment.
The serialization constructor now unconditionally reads "FileNotFound_RequestingAssemblyChain" via SerializationInfo.GetString. If this exception is deserialized from older serialized data that doesn't contain this entry, GetString will throw a SerializationException. Consider treating this value as optional (e.g., catch SerializationException / check for the entry) and default to null when missing for better forward/backward compatibility.
| _requestingAssemblyChain = info.GetString("FileNotFound_RequestingAssemblyChain"); | |
| try | |
| { | |
| _requestingAssemblyChain = info.GetString("FileNotFound_RequestingAssemblyChain"); | |
| } | |
| catch (SerializationException) | |
| { | |
| _requestingAssemblyChain = null; | |
| } |
| protected FileLoadException(SerializationInfo info, StreamingContext context) | ||
| : base(info, context) | ||
| { | ||
| FileName = info.GetString("FileLoad_FileName"); | ||
| FusionLog = info.GetString("FileLoad_FusionLog"); | ||
| _requestingAssemblyChain = info.GetString("FileLoad_RequestingAssemblyChain"); | ||
| } |
There was a problem hiding this comment.
The serialization constructor now unconditionally reads "FileLoad_RequestingAssemblyChain" via SerializationInfo.GetString. If FileLoadException is deserialized from older serialized data that lacks this entry, GetString will throw. Consider making this field optional during deserialization (default to null when missing).
src/libraries/System.Private.CoreLib/src/System/BadImageFormatException.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs
Outdated
Show resolved
Hide resolved
This comment has been minimized.
This comment has been minimized.
Use SerializationInfo.GetValueNoThrow for the new *_RequestingAssemblyChain fields so older payloads that do not contain the data still deserialize successfully. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
…est.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🤖 Copilot Code Review — PR #125795Note This review was generated by GitHub Copilot using Claude Opus 4.6 and Claude Sonnet 4 models. Holistic AssessmentMotivation: The PR adds a requesting assembly chain to file load exceptions ( Approach: The approach is sound — the chain is built in native code (walking the binding cache), passed through the Summary: ✅ LGTM. The changes are correct, well-structured, and follow established patterns. The contract relaxation is justified, the serialization uses the correct backward-compatible approach, and the test coverage validates the end-to-end scenario. Minor observations below are non-blocking. Detailed Findings✅ Correctness — Moving
|
|
/azp run |
|
You have several pipelines (over 10) configured to build pull requests in this repository. Specify which pipelines you would like to run by using /azp run [pipelines] command. You can specify multiple pipelines using a comma separated list. |
|
/azp run runtime |
|
Azure Pipelines successfully started running 1 pipeline(s). |
This pull request enhances .NET exception diagnostics for assembly loading failures by tracking and surfacing the chain of requesting assemblies when a dependency is missing or invalid. This helps developers more easily identify which assemblies triggered a load failure, especially in complex dependency scenarios.
Exception information:
BadImageFormatException,FileLoadException, andFileNotFoundExceptionnow accept an additionalrequestingAssemblyChainparameter, which records the chain of assemblies that led to the load failure. This chain is stored and included in the exception’s string representation.ToString()methods of these exceptions now append a "Requested by: ..." message listing the requesting assembly chain, improving error clarity for users.IO_FileLoad_RequestedBy, is added for this message.Runtime and binding cache:
AssemblySpecBindingCache. New methods allow walking the chain of requesting assemblies up to a depth of 10, building a diagnostic chain for exceptions.EEFileLoadExceptionand related logic are updated to capture and propagate the requesting assembly chain, including in exception cloning and when thrown from the runtime.Testing and validation:
MissingDependency.Leaf,MissingDependency.Mid) and a test inAssemblyLoadContextTestto verify that the requesting assembly chain appears in exception messages when a transitive dependency is missing.These changes significantly improve the developer experience when diagnosing assembly loading failures by providing more actionable information in exception messages.
Example:
@dotnet/appmodel @AaronRobinsonMSFT
💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.