Tolerate assembly resolution errors#260
Conversation
Previously, we swallowed AssemblyResolutionException in a few places in various steps. We need an option to be generally more tolerant of AssemblyResolutionExceptions, because there can be cases during portable publish in which referenced assemblies are not present during publish because they're part of the shared framework. Instead of handling AssemblyResolutionException in every place in the linker that tries to resolve methods/types/etc, this change adds an option that enables the resolver to return null when an assembly is not found. The null result is also cached to avoid multiple lookups. Only a few places in the linker need to be modified to handle null results, and the existing places where we used to catch AssemblyResolutionException now only need to deal with a null result instead. The downside of this approach is that these places cannot distinguish between types not found in an existing assembly, and assemblies that were not found. The new behavior is grouped under the option --skip-unresolved, which already handles unresolved types and methods in some cases. When this option is enabled, a warning is logged, and no exception is thrown.
2a05c2d to
88653c9
Compare
Introduce a SkipUnresolved attribute
88653c9 to
d47d9d7
Compare
|
|
||
| readonly Dictionary<string, AssemblyDefinition> _assemblies; | ||
| readonly HashSet<string> _unresolvedAssemblies; | ||
| bool _ignoreUnresolved = false; |
There was a problem hiding this comment.
redundant initialization
| if (!_ignoreUnresolved) { | ||
| throw; | ||
| } | ||
| Console.WriteLine($"warning: unresolved assembly {name.Name}"); |
There was a problem hiding this comment.
Use LinkContext::LogMessage instead
| public AssemblyResolver (Dictionary<string, AssemblyDefinition> assembly_cache) | ||
| { | ||
| _assemblies = assembly_cache; | ||
| _unresolvedAssemblies = new HashSet<string>(); |
There was a problem hiding this comment.
I'd prefer to delay initialization of this to the catch block
| AssemblyDefinition asm = null; | ||
| if (!_assemblies.TryGetValue (name.Name, out asm) && !_unresolvedAssemblies.Contains (name.Name)) { | ||
| try | ||
| { |
There was a problem hiding this comment.
Follow existing coding style
| set { _ignoreUnresolved = value; } | ||
| set { | ||
| _ignoreUnresolved = value; | ||
| _resolver.IgnoreUnresolved = value; |
There was a problem hiding this comment.
I don't like this, _resolver could be a shared/global instance and you are changing it under the hood
- follow coding conventions - use LinkContext::LogMessage instead of Console.WriteLine - delay initialization of unresolved assembly cache to catch block - keep AssemblyResolver.IgnoreUnresolved independent of LinkContext.IgnoreUnresolved - remove redundant initialization
|
@marek-safar I addressed your feedback, thanks! |
|
build |
|
Hold off on merging this for a minute - just discovered a bug. I need to check for null in AssemblyResolver.Dispose. Edit: fixed now. |
| } | ||
|
|
||
| _assemblies.Clear (); | ||
| _unresolvedAssemblies.Clear (); |
There was a problem hiding this comment.
_unresolvedAssemblies can be null, setting it to null should do it
| set { _context = value; } | ||
| } | ||
|
|
||
| public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters) |
There was a problem hiding this comment.
I think it'd be better to pass LinkContext Context as the parameter
There was a problem hiding this comment.
The problem is that the Resolve method signature is part of IAssemblyResolver, defined in cecil. I want warnings to be logged when an assembly can't be resolved while trying to resolve TypeReferences to TypeDefinitions using cecil, for example. Do you have any suggestions?
| } | ||
|
|
||
| _assemblies.Clear (); | ||
| if (_unresolvedAssemblies != null) |
* Tolerate assembly resolution errors Previously, we swallowed AssemblyResolutionException in a few places in various steps. We need an option to be generally more tolerant of AssemblyResolutionExceptions, because there can be cases during portable publish in which referenced assemblies are not present during publish because they're part of the shared framework. Instead of handling AssemblyResolutionException in every place in the linker that tries to resolve methods/types/etc, this change adds an option that enables the resolver to return null when an assembly is not found. The null result is also cached to avoid multiple lookups. Only a few places in the linker need to be modified to handle null results, and the existing places where we used to catch AssemblyResolutionException now only need to deal with a null result instead. The downside of this approach is that these places cannot distinguish between types not found in an existing assembly, and assemblies that were not found. The new behavior is grouped under the option --skip-unresolved, which already handles unresolved types and methods in some cases. When this option is enabled, a warning is logged, and no exception is thrown. * Attempt to fix test failure Introduce a SkipUnresolved attribute * Address PR feedback - follow coding conventions - use LinkContext::LogMessage instead of Console.WriteLine - delay initialization of unresolved assembly cache to catch block - keep AssemblyResolver.IgnoreUnresolved independent of LinkContext.IgnoreUnresolved - remove redundant initialization * Handle null _unresolvedAssemblies in Dispose method Commit migrated from dotnet/linker@2dee7d0
Previously, we swallowed AssemblyResolutionException in a few places
in various steps. We need an option to be generally more tolerant of
AssemblyResolutionExceptions, because there can be cases during
portable publish in which referenced assemblies are not present during
publish because they're part of the shared framework.
Instead of handling AssemblyResolutionException in every place in the
linker that tries to resolve methods/types/etc, this change adds an
option that enables the resolver to return null when an assembly is
not found. The null result is also cached to avoid multiple lookups.
Only a few places in the linker need to be modified to handle null
results, and the existing places where we used to catch
AssemblyResolutionException now only need to deal with a null result
instead. The downside of this approach is that these places cannot
distinguish between types not found in an existing assembly, and
assemblies that were not found.
The new behavior is grouped under the option --skip-unresolved, which
already handles unresolved types and methods in some cases. When this
option is enabled, a warning is logged, and no exception is thrown.
@erozenfeld, please review