File: CommandLine\CommonCompiler.ExistingReferencesResolver.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis
{
    internal abstract partial class CommonCompiler
    {
        /// <summary>
        /// Looks for metadata references among the assembly file references given to the compilation when constructed.
        /// When scripts are included into a project we don't want #r's to reference other assemblies than those 
        /// specified explicitly in the project references.
        /// </summary>
        internal sealed class ExistingReferencesResolver : MetadataReferenceResolver, IEquatable<ExistingReferencesResolver>
        {
            private readonly MetadataReferenceResolver _resolver;
            private readonly ImmutableArray<MetadataReference> _availableReferences;
            private readonly Lazy<HashSet<AssemblyIdentity>> _lazyAvailableReferences;
 
            public ExistingReferencesResolver(MetadataReferenceResolver resolver, ImmutableArray<MetadataReference> availableReferences)
            {
                Debug.Assert(resolver != null);
                Debug.Assert(availableReferences != null);
 
                _resolver = resolver;
                _availableReferences = availableReferences;
 
                // Delay reading assembly identities until they are actually needed (only when #r is encountered).
                _lazyAvailableReferences = new Lazy<HashSet<AssemblyIdentity>>(() => new HashSet<AssemblyIdentity>(
                    from reference in _availableReferences
                    let identity = TryGetIdentity(reference)
                    where identity != null
                    select identity!));
            }
 
            public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string? baseFilePath, MetadataReferenceProperties properties)
            {
                var resolvedReferences = _resolver.ResolveReference(reference, baseFilePath, properties);
                return resolvedReferences.WhereAsArray(r => _lazyAvailableReferences.Value.Contains(TryGetIdentity(r)!));
            }
 
            private static AssemblyIdentity? TryGetIdentity(MetadataReference metadataReference)
            {
                var peReference = metadataReference as PortableExecutableReference;
                if (peReference == null || peReference.Properties.Kind != MetadataImageKind.Assembly)
                {
                    return null;
                }
 
                try
                {
                    PEAssembly assembly = ((AssemblyMetadata)peReference.GetMetadataNoCopy()).GetAssembly()!;
                    return assembly.Identity;
                }
                catch (Exception e) when (e is BadImageFormatException || e is IOException)
                {
                    // ignore, metadata reading errors are reported by the compiler for the existing references
                    return null;
                }
            }
 
            public override int GetHashCode()
            {
                return _resolver.GetHashCode();
            }
 
            public bool Equals(ExistingReferencesResolver? other)
            {
                return
                    other is object &&
                    _resolver.Equals(other._resolver) &&
                    _availableReferences.SequenceEqual(other._availableReferences);
            }
 
            public override bool Equals(object? other) => other is ExistingReferencesResolver obj && Equals(obj);
        }
    }
}