Skip to content

Tolerate assembly resolution errors#260

Merged
marek-safar merged 4 commits into
dotnet:masterfrom
sbomer:handleUnresolvedAssemblies
Feb 23, 2018
Merged

Tolerate assembly resolution errors#260
marek-safar merged 4 commits into
dotnet:masterfrom
sbomer:handleUnresolvedAssemblies

Conversation

@sbomer
Copy link
Copy Markdown
Member

@sbomer sbomer commented Feb 23, 2018

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

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.
@sbomer sbomer requested a review from marek-safar as a code owner February 23, 2018 00:25
@sbomer sbomer force-pushed the handleUnresolvedAssemblies branch from 2a05c2d to 88653c9 Compare February 23, 2018 03:39
Introduce a SkipUnresolved attribute
@sbomer sbomer force-pushed the handleUnresolvedAssemblies branch from 88653c9 to d47d9d7 Compare February 23, 2018 04:28
Comment thread linker/Linker/AssemblyResolver.cs Outdated

readonly Dictionary<string, AssemblyDefinition> _assemblies;
readonly HashSet<string> _unresolvedAssemblies;
bool _ignoreUnresolved = false;
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.

redundant initialization

Comment thread linker/Linker/AssemblyResolver.cs Outdated
if (!_ignoreUnresolved) {
throw;
}
Console.WriteLine($"warning: unresolved assembly {name.Name}");
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.

Use LinkContext::LogMessage instead

Comment thread linker/Linker/AssemblyResolver.cs Outdated
public AssemblyResolver (Dictionary<string, AssemblyDefinition> assembly_cache)
{
_assemblies = assembly_cache;
_unresolvedAssemblies = new HashSet<string>();
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.

I'd prefer to delay initialization of this to the catch block

Comment thread linker/Linker/AssemblyResolver.cs Outdated
AssemblyDefinition asm = null;
if (!_assemblies.TryGetValue (name.Name, out asm) && !_unresolvedAssemblies.Contains (name.Name)) {
try
{
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.

Follow existing coding style

Comment thread linker/Linker/LinkContext.cs Outdated
set { _ignoreUnresolved = value; }
set {
_ignoreUnresolved = value;
_resolver.IgnoreUnresolved = value;
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.

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
@sbomer
Copy link
Copy Markdown
Member Author

sbomer commented Feb 23, 2018

@marek-safar I addressed your feedback, thanks!

@marek-safar
Copy link
Copy Markdown
Contributor

build

@sbomer
Copy link
Copy Markdown
Member Author

sbomer commented Feb 23, 2018

Hold off on merging this for a minute - just discovered a bug. I need to check for null in AssemblyResolver.Dispose.

Edit: fixed now.

Comment thread linker/Linker/AssemblyResolver.cs Outdated
}

_assemblies.Clear ();
_unresolvedAssemblies.Clear ();
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.

_unresolvedAssemblies can be null, setting it to null should do it

set { _context = value; }
}

public override AssemblyDefinition Resolve (AssemblyNameReference name, ReaderParameters parameters)
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.

I think it'd be better to pass LinkContext Context as the parameter

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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?

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.

hmm, I see

}

_assemblies.Clear ();
if (_unresolvedAssemblies != null)
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.

Just set it to null

@marek-safar marek-safar merged commit 2dee7d0 into dotnet:master Feb 23, 2018
tkapin pushed a commit to tkapin/runtime that referenced this pull request Jan 31, 2023
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants