|
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
// AssemblyResolver.cs
//
// Author:
// Jb Evain (jbevain@novell.com)
//
// (C) 2007 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using ILLink.Shared;
using Mono.Cecil;
namespace Mono.Linker
{
public class AssemblyResolver : IAssemblyResolver
{
readonly List<string> _references = new ();
readonly LinkContext _context;
readonly List<string> _directories = new ();
readonly Dictionary<AssemblyDefinition, string> _assemblyToPath = new ();
readonly Dictionary<string, AssemblyDefinition> _pathToAssembly = new ();
readonly List<MemoryMappedViewStream> _viewStreams = new ();
readonly ReaderParameters _defaultReaderParameters;
readonly HashSet<string> _unresolvedAssembliesProbing = new ();
readonly HashSet<string> _unresolvedAssemblies = new ();
HashSet<string>? _reportedUnresolvedAssemblies;
public AssemblyResolver (LinkContext context, ReaderParameters readerParameters)
{
readerParameters.AssemblyResolver = this;
_context = context;
_defaultReaderParameters = readerParameters;
}
public IDictionary<string, AssemblyDefinition> AssemblyCache { get; } = new Dictionary<string, AssemblyDefinition> (StringComparer.OrdinalIgnoreCase);
public string GetAssemblyLocation (AssemblyDefinition assembly)
{
if (_assemblyToPath.TryGetValue (assembly, out string? path))
return path;
throw new InternalErrorException ($"Assembly '{assembly}' was not loaded using ILLink resolver");
}
/// <summary>
/// We need to track unresolved assemblies separately when probing vs not probing.
///
/// This prevents a TryResolve call that fails to resolve an assembly from silencing a later Resolve call that fails to resolve the same
/// assembly when SkipUnresolved is false.
/// </summary>
/// <param name="probing"></param>
/// <returns>The known unresolved assemblies for probing mode or non probing mode</returns>
HashSet<string> GetUnresolvedAssemblies (bool probing) => probing ? _unresolvedAssembliesProbing : _unresolvedAssemblies;
AssemblyDefinition? ResolveFromReferences (AssemblyNameReference name)
{
foreach (var reference in _references) {
foreach (var extension in Extensions) {
var fileName = name.Name + extension;
if (Path.GetFileName (reference) != fileName)
continue;
try {
return GetAssembly (reference);
} catch (BadImageFormatException) {
continue;
}
}
}
return null;
}
public AssemblyDefinition? Resolve (AssemblyNameReference name, bool probing)
{
if (AssemblyCache.TryGetValue (name.Name, out AssemblyDefinition? asm))
return asm;
var unresolvedAssemblies = GetUnresolvedAssemblies (probing);
if (unresolvedAssemblies.Contains (name.Name)) {
if (!probing)
ReportUnresolvedAssembly (name);
return null;
}
// Any full path explicit reference takes precedence over other look up logic
asm = ResolveFromReferences (name);
asm ??= SearchDirectory (name);
if (asm == null) {
if (!probing)
ReportUnresolvedAssembly (name);
unresolvedAssemblies.Add (name.Name);
return null;
}
CacheAssembly (asm);
return asm;
}
void ReportUnresolvedAssembly (AssemblyNameReference reference)
{
_reportedUnresolvedAssemblies ??= new HashSet<string> ();
if (!_reportedUnresolvedAssemblies.Add (reference.Name))
return;
if (_context.IgnoreUnresolved)
_context.LogMessage ($"Ignoring unresolved assembly '{reference.Name}' reference.");
else
_context.LogError (null, DiagnosticId.CouldNotFindAssemblyReference, reference.Name);
}
public virtual void AddSearchDirectory (string directory)
{
_directories.Add (directory);
}
public AssemblyDefinition GetAssembly (string file)
{
// Sanitize the path for caching purposes
file = Path.GetFullPath (file);
if (_pathToAssembly.TryGetValue (file, out var loadedAssembly))
return loadedAssembly;
MemoryMappedViewStream? viewStream = null;
try {
// Create stream because CreateFromFile(string, ...) uses FileShare.None which is too strict
using var fileStream = new FileStream (file, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false);
using var mappedFile = MemoryMappedFile.CreateFromFile (
fileStream, null, fileStream.Length, MemoryMappedFileAccess.Read, HandleInheritability.None, true);
viewStream = mappedFile.CreateViewStream (0, 0, MemoryMappedFileAccess.Read);
AssemblyDefinition result = ModuleDefinition.ReadModule (viewStream, _defaultReaderParameters).Assembly;
_assemblyToPath.Add (result, file);
_pathToAssembly.Add (file, result);
_viewStreams.Add (viewStream);
// We transferred the ownership of the viewStream to the collection.
viewStream = null;
return result;
} finally {
viewStream?.Dispose ();
}
}
public virtual AssemblyDefinition? Resolve (AssemblyNameReference name)
{
return Resolve (name, probing: false);
}
AssemblyDefinition IAssemblyResolver.Resolve (AssemblyNameReference name, ReaderParameters parameters)
{
// This is never used by cecil in ILLink context
throw new NotSupportedException ();
}
static readonly string[] Extensions = new[] { ".dll", ".exe", ".winmd" };
AssemblyDefinition? SearchDirectory (AssemblyNameReference name)
{
foreach (var directory in _directories) {
foreach (var extension in Extensions) {
string file = Path.Combine (directory, name.Name + extension);
if (!File.Exists (file))
continue;
try {
return GetAssembly (file);
} catch (BadImageFormatException) {
continue;
}
}
}
return null;
}
public virtual void CacheAssembly (AssemblyDefinition assembly)
{
if (AssemblyCache.TryGetValue (assembly.Name.Name, out var existing)) {
if (existing != assembly)
throw new ArgumentException ("Cannot overwrite an existing assembly with a different assembly");
return;
}
AssemblyCache.Add (assembly.Name.Name, assembly);
_context.RegisterAssembly (assembly);
}
public void AddReferenceAssembly (string referencePath)
{
_references.Add (referencePath);
}
public List<string> GetReferencePaths ()
{
return _references;
}
public void Dispose ()
{
Dispose (true);
}
protected virtual void Dispose (bool disposing)
{
if (!disposing)
return;
foreach (var asm in AssemblyCache.Values) {
asm.Dispose ();
}
AssemblyCache.Clear ();
_unresolvedAssemblies.Clear ();
_unresolvedAssembliesProbing.Clear ();
_reportedUnresolvedAssemblies?.Clear ();
foreach (var viewStream in _viewStreams) {
viewStream.Dispose ();
}
_viewStreams.Clear ();
}
}
}
|