File: Symbols\UsedAssemblies.vb
Web Access
Project: src\roslyn\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' 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.

Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Retargeting

Namespace Microsoft.CodeAnalysis.VisualBasic

    Partial Public Class VisualBasicCompilation

        Private _lazyUsedAssemblyReferences As ConcurrentSet(Of AssemblySymbol)
        Private _usedAssemblyReferencesFrozen As Boolean

        Public Overrides Function GetUsedAssemblyReferences(Optional cancellationToken As CancellationToken = Nothing) As ImmutableArray(Of MetadataReference)
            Dim usedAssemblies As ConcurrentSet(Of AssemblySymbol) = GetCompleteSetOfUsedAssemblies(cancellationToken)

            If usedAssemblies Is Nothing Then
                Return ImmutableArray(Of MetadataReference).Empty
            End If

            ' Use stable ordering for the result, matching the order in References.
            Dim builder = ArrayBuilder(Of MetadataReference).GetInstance(usedAssemblies.Count)

            For Each reference In References
                If reference.Properties.Kind = MetadataImageKind.Assembly Then
                    Dim symbol As Symbol = GetBoundReferenceManager().GetReferencedAssemblySymbol(reference)

                    If symbol IsNot Nothing AndAlso usedAssemblies.Contains(DirectCast(symbol, AssemblySymbol)) Then
                        builder.Add(reference)
                    End If
                End If
            Next

            Return builder.ToImmutableAndFree()
        End Function

        Private Function GetCompleteSetOfUsedAssemblies(cancellationToken As CancellationToken) As ConcurrentSet(Of AssemblySymbol)

            If Not _usedAssemblyReferencesFrozen AndAlso Not Volatile.Read(_usedAssemblyReferencesFrozen) Then

                Dim diagnostics = BindingDiagnosticBag.GetConcurrentInstance()
                RoslynDebug.Assert(diagnostics.AccumulatesDiagnostics)

                GetDiagnosticsWithoutFiltering(CompilationStage.Declare, includeEarlierStages:=True, diagnostics, cancellationToken)

                Dim seenErrors As Boolean = diagnostics.HasAnyErrors()

                If Not seenErrors Then
                    diagnostics.DiagnosticBag.Clear()
                    GetDiagnosticsForAllMethodBodies(hasDeclarationErrors:=False, diagnostics, doLowering:=True, cancellationToken)
                    seenErrors = diagnostics.HasAnyErrors()

                    If Not seenErrors Then
                        AddUsedAssemblies(diagnostics.DependenciesBag)
                    End If
                End If

                CompleteTheSetOfUsedAssemblies(seenErrors, cancellationToken)

                diagnostics.Free()
            End If

            Return _lazyUsedAssemblyReferences
        End Function

        Private Sub AddUsedAssembly(dependency As AssemblySymbol, stack As ArrayBuilder(Of AssemblySymbol))
            If AddUsedAssembly(dependency) Then
                stack.Push(dependency)
            End If
        End Sub

        Private Sub AddReferencedAssemblies(assembly As AssemblySymbol, includeMainModule As Boolean, stack As ArrayBuilder(Of AssemblySymbol))
            For i As Integer = If(includeMainModule, 0, 1) To assembly.Modules.Length - 1
                For Each dependency In assembly.Modules(i).ReferencedAssemblySymbols
                    AddUsedAssembly(dependency, stack)
                Next
            Next
        End Sub

        Private Sub CompleteTheSetOfUsedAssemblies(seenErrors As Boolean, cancellationToken As CancellationToken)

            If _usedAssemblyReferencesFrozen OrElse Volatile.Read(_usedAssemblyReferencesFrozen) Then
                Return
            End If

            If seenErrors Then
                ' Add all referenced assemblies
                For Each assembly As AssemblySymbol In SourceModule.ReferencedAssemblySymbols
                    AddUsedAssembly(assembly)
                Next
            Else
                ' Assume that all assemblies used by the added modules are also used
                For i As Integer = 1 To SourceAssembly.Modules.Length - 1
                    For Each dependency In SourceAssembly.Modules(i).ReferencedAssemblySymbols
                        AddUsedAssembly(dependency)
                    Next
                Next

                If _usedAssemblyReferencesFrozen OrElse Volatile.Read(_usedAssemblyReferencesFrozen) Then
                    Return
                End If

                ' 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 IsNot Nothing Then
                    SyncLock _lazyUsedAssemblyReferences
                        If _usedAssemblyReferencesFrozen OrElse Volatile.Read(_usedAssemblyReferencesFrozen) Then
                            Return
                        End If

                        Dim stack = ArrayBuilder(Of AssemblySymbol).GetInstance(_lazyUsedAssemblyReferences.Count)
                        stack.AddRange(_lazyUsedAssemblyReferences)

                        While stack.Count <> 0
                            Dim current As AssemblySymbol = stack.Pop()
                            Dim usedAssemblies As ConcurrentSet(Of AssemblySymbol)

                            Dim sourceAssembly = TryCast(current, SourceAssemblySymbol)

                            If sourceAssembly IsNot Nothing Then
                                ' 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 IsNot Nothing Then
                                    For Each dependency As AssemblySymbol In usedAssemblies
                                        Debug.Assert(Not dependency.IsLinked)
                                        AddUsedAssembly(dependency, stack)
                                    Next
                                End If

                                Continue While
                            End If

                            Dim retargetingAssembly = TryCast(current, RetargetingAssemblySymbol)

                            If retargetingAssembly IsNot Nothing Then
                                usedAssemblies = retargetingAssembly.UnderlyingAssembly.DeclaringCompilation.GetCompleteSetOfUsedAssemblies(cancellationToken)
                                If usedAssemblies IsNot Nothing Then
                                    For Each underlyingDependency As AssemblySymbol In retargetingAssembly.UnderlyingAssembly.SourceModule.ReferencedAssemblySymbols
                                        If Not underlyingDependency.IsLinked AndAlso usedAssemblies.Contains(underlyingDependency) Then
                                            Dim dependency As AssemblySymbol = Nothing

                                            If Not DirectCast(retargetingAssembly.Modules(0), RetargetingModuleSymbol).RetargetingDefinitions(underlyingDependency, dependency) Then
                                                Debug.Assert(retargetingAssembly.Modules(0).ReferencedAssemblySymbols.Contains(underlyingDependency))
                                                dependency = underlyingDependency
                                            End If

                                            AddUsedAssembly(dependency, stack)
                                        End If
                                    Next
                                End If

                                AddReferencedAssemblies(retargetingAssembly, includeMainModule:=False, stack)

                                Continue While
                            End If

                            AddReferencedAssemblies(current, includeMainModule:=True, stack)
                        End While

                        stack.Free()
                    End SyncLock
                End If

                If SourceAssembly.CorLibrary IsNot Nothing Then
                    ' Add core library
                    AddUsedAssembly(sourceAssembly.CorLibrary)
                End If
            End If

            _usedAssemblyReferencesFrozen = True
        End Sub

        Friend Sub AddUsedAssemblies(assemblies As ICollection(Of AssemblySymbol))
            If Not assemblies.IsNullOrEmpty() Then
                For Each candidate In assemblies
                    AddUsedAssembly(candidate)
                Next
            End If
        End Sub

        Friend Function AddUsedAssembly(assembly As AssemblySymbol) As Boolean

            If assembly Is Nothing OrElse assembly Is SourceAssembly OrElse assembly.IsMissing Then
                Return False
            End If

            If _lazyUsedAssemblyReferences Is Nothing Then
                Interlocked.CompareExchange(_lazyUsedAssemblyReferences, New ConcurrentSet(Of AssemblySymbol)(), Nothing)
            End If

#If DEBUG Then
            Dim wasFrozen As Boolean = _usedAssemblyReferencesFrozen
#End If
            Dim added As Boolean = _lazyUsedAssemblyReferences.Add(assembly)

#If DEBUG Then
            Debug.Assert(Not added OrElse Not wasFrozen)
#End If
            Return added
        End Function

    End Class
End Namespace