File: Symbols\Compilation_UsedAssemblies.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Threading;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Symbols.Retargeting;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    public partial class CSharpCompilation
    {
        private ConcurrentSet<AssemblySymbol>? _lazyUsedAssemblyReferences;
        private bool _usedAssemblyReferencesFrozen;
 
        public override ImmutableArray<MetadataReference> GetUsedAssemblyReferences(CancellationToken cancellationToken = default)
        {
            ConcurrentSet<AssemblySymbol>? usedAssemblies = GetCompleteSetOfUsedAssemblies(cancellationToken);
 
            if (usedAssemblies is null)
            {
                return ImmutableArray<MetadataReference>.Empty;
            }
 
            var setOfReferences = new HashSet<MetadataReference>(ReferenceEqualityComparer.Instance);
            ImmutableDictionary<MetadataReference, ImmutableArray<MetadataReference>> mergedAssemblyReferencesMap = GetBoundReferenceManager().MergedAssemblyReferencesMap;
 
            foreach (var reference in References)
            {
                if (reference.Properties.Kind == MetadataImageKind.Assembly)
                {
                    Symbol? symbol = GetBoundReferenceManager().GetReferencedAssemblySymbol(reference);
                    if (symbol is object && usedAssemblies.Contains((AssemblySymbol)symbol) &&
                        setOfReferences.Add(reference) &&
                        mergedAssemblyReferencesMap.TryGetValue(reference, out ImmutableArray<MetadataReference> merged))
                    {
                        // Include all "merged" references as well because they might "define" used extern aliases.
                        setOfReferences.AddAll(merged);
                    }
                }
            }
 
            // Use stable ordering for the result, matching the order in References.
            var builder = ArrayBuilder<MetadataReference>.GetInstance(setOfReferences.Count);
 
            foreach (var reference in References)
            {
                if (setOfReferences.Contains(reference))
                {
                    builder.Add(reference);
                }
            }
 
            return builder.ToImmutableAndFree();
        }
 
        private ConcurrentSet<AssemblySymbol>? GetCompleteSetOfUsedAssemblies(CancellationToken cancellationToken)
        {
            if (!_usedAssemblyReferencesFrozen && !Volatile.Read(ref _usedAssemblyReferencesFrozen))
            {
                var diagnostics = BindingDiagnosticBag.GetConcurrentInstance();
                RoslynDebug.Assert(diagnostics.DiagnosticBag is object);
 
                GetDiagnosticsWithoutSeverityFiltering(CompilationStage.Declare, includeEarlierStages: true, diagnostics, symbolFilter: null, cancellationToken);
 
                bool seenErrors = diagnostics.HasAnyErrors();
                if (!seenErrors)
                {
                    diagnostics.DiagnosticBag.Clear();
                    GetDiagnosticsForAllMethodBodies(diagnostics, doLowering: true, cancellationToken);
                    seenErrors = diagnostics.HasAnyErrors();
 
                    if (!seenErrors)
                    {
                        AddUsedAssemblies(diagnostics.DependenciesBag);
                    }
                }
 
                completeTheSetOfUsedAssemblies(seenErrors, cancellationToken);
 
                diagnostics.Free();
            }
 
            return _lazyUsedAssemblyReferences;
 
            void addUsedAssembly(AssemblySymbol dependency, ArrayBuilder<AssemblySymbol> stack)
            {
                if (AddUsedAssembly(dependency))
                {
                    stack.Push(dependency);
                }
            }
 
            void addReferencedAssemblies(AssemblySymbol assembly, bool includeMainModule, ArrayBuilder<AssemblySymbol> stack)
            {
                for (int i = (includeMainModule ? 0 : 1); i < assembly.Modules.Length; i++)
                {
                    foreach (var dependency in assembly.Modules[i].ReferencedAssemblySymbols)
                    {
                        addUsedAssembly(dependency, stack);
                    }
                }
            }
 
            void completeTheSetOfUsedAssemblies(bool seenErrors, CancellationToken cancellationToken)
            {
                if (_usedAssemblyReferencesFrozen || Volatile.Read(ref _usedAssemblyReferencesFrozen))
                {
                    return;
                }
 
                if (seenErrors)
                {
                    // Add all referenced assemblies
                    foreach (var assembly in SourceModule.ReferencedAssemblySymbols)
                    {
                        AddUsedAssembly(assembly);
                    }
                }
                else
                {
                    // Assume that all assemblies used by the added modules are also used
                    for (int i = 1; i < SourceAssembly.Modules.Length; i++)
                    {
                        foreach (var dependency in SourceAssembly.Modules[i].ReferencedAssemblySymbols)
                        {
                            AddUsedAssembly(dependency);
                        }
                    }
 
                    if (_usedAssemblyReferencesFrozen || Volatile.Read(ref _usedAssemblyReferencesFrozen))
                    {
                        return;
                    }
 
                    // Assume that all assemblies used by the used assemblies are also used
                    // This, for example, takes care of including facade assemblies that forward types around.
                    if (_lazyUsedAssemblyReferences is object)
                    {
                        lock (_lazyUsedAssemblyReferences)
                        {
                            if (_usedAssemblyReferencesFrozen || Volatile.Read(ref _usedAssemblyReferencesFrozen))
                            {
                                return;
                            }
 
                            var stack = ArrayBuilder<AssemblySymbol>.GetInstance(_lazyUsedAssemblyReferences.Count);
                            stack.AddRange(_lazyUsedAssemblyReferences);
 
                            while (stack.Count != 0)
                            {
                                AssemblySymbol current = stack.Pop();
                                ConcurrentSet<AssemblySymbol>? usedAssemblies;
 
                                switch (current)
                                {
                                    case SourceAssemblySymbol sourceAssembly:
                                        // The set of assemblies used by the referenced compilation feels like
                                        // a reasonable approximation to the set of assembly references that would
                                        // be emitted into the resulting binary for that compilation. An alternative
                                        // would be to attempt to emit and get the exact set of emitted references
                                        // in case of success. This might be too slow though.
                                        usedAssemblies = sourceAssembly.DeclaringCompilation.GetCompleteSetOfUsedAssemblies(cancellationToken);
                                        if (usedAssemblies is object)
                                        {
                                            foreach (AssemblySymbol dependency in usedAssemblies)
                                            {
                                                Debug.Assert(!dependency.IsLinked);
                                                addUsedAssembly(dependency, stack);
                                            }
                                        }
                                        break;
 
                                    case RetargetingAssemblySymbol retargetingAssembly:
                                        usedAssemblies = retargetingAssembly.UnderlyingAssembly.DeclaringCompilation.GetCompleteSetOfUsedAssemblies(cancellationToken);
                                        if (usedAssemblies is object)
                                        {
                                            foreach (AssemblySymbol underlyingDependency in retargetingAssembly.UnderlyingAssembly.SourceModule.ReferencedAssemblySymbols)
                                            {
                                                if (!underlyingDependency.IsLinked && usedAssemblies.Contains(underlyingDependency))
                                                {
                                                    AssemblySymbol dependency;
 
                                                    if (!((RetargetingModuleSymbol)retargetingAssembly.Modules[0]).RetargetingDefinitions(underlyingDependency, out dependency))
                                                    {
                                                        Debug.Assert(retargetingAssembly.Modules[0].ReferencedAssemblySymbols.Contains(underlyingDependency));
                                                        dependency = underlyingDependency;
                                                    }
 
                                                    addUsedAssembly(dependency, stack);
                                                }
                                            }
                                        }
 
                                        addReferencedAssemblies(retargetingAssembly, includeMainModule: false, stack);
                                        break;
                                    default:
                                        addReferencedAssemblies(current, includeMainModule: true, stack);
                                        break;
                                }
                            }
 
                            stack.Free();
                        }
                    }
 
                    if (SourceAssembly.CorLibrary is object)
                    {
                        // Add core library
                        AddUsedAssembly(SourceAssembly.CorLibrary);
                    }
                }
 
                _usedAssemblyReferencesFrozen = true;
            }
        }
 
        internal void AddUsedAssemblies(ICollection<AssemblySymbol>? assemblies)
        {
            if (!assemblies.IsNullOrEmpty())
            {
                foreach (var candidate in assemblies)
                {
                    AddUsedAssembly(candidate);
                }
            }
        }
 
        internal bool AddUsedAssembly(AssemblySymbol? assembly)
        {
            if (assembly is null || assembly == SourceAssembly || assembly.IsMissing)
            {
                return false;
            }
 
            if (_lazyUsedAssemblyReferences is null)
            {
                Interlocked.CompareExchange(ref _lazyUsedAssemblyReferences, new ConcurrentSet<AssemblySymbol>(), null);
            }
 
#if DEBUG
            bool wasFrozen = _usedAssemblyReferencesFrozen;
#endif
            bool added = _lazyUsedAssemblyReferences.Add(assembly);
 
#if DEBUG
            Debug.Assert(!added || !wasFrozen);
#endif
            return added;
        }
 
    }
}