// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis { using MetadataOrDiagnostic = System.Object; internal abstract class CommonReferenceManager { /// /// Must be acquired whenever the following data are about to be modified: /// - Compilation.lazyAssemblySymbol /// - Compilation.referenceManager /// - ReferenceManager state /// - /// - /// /// All the above data should be updated at once while holding this lock. /// Once lazyAssemblySymbol is set the Compilation.referenceManager field and ReferenceManager /// state should not change. /// internal static object SymbolCacheAndReferenceManagerStateGuard = new object(); /// /// Enumerates all referenced assemblies. /// internal abstract IEnumerable> GetReferencedAssemblies(); /// /// Enumerates all referenced assemblies and their aliases. /// internal abstract IEnumerable>> GetReferencedAssemblyAliases(); internal abstract MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol); internal abstract ImmutableArray ExplicitReferences { get; } internal abstract ImmutableArray ImplicitReferences { get; } internal abstract IEnumerable> GetImplicitlyResolvedAssemblyReferences(); } internal partial class CommonReferenceManager : CommonReferenceManager { /// /// If the compilation being built represents an assembly its assembly name. /// If the compilation being built represents a module, the name of the /// containing assembly or /// if not specified (/moduleassemblyname command line option). /// internal readonly string SimpleAssemblyName; /// /// Used to compares assembly identities. /// May implement unification and portability policies specific to the target platform. /// internal readonly AssemblyIdentityComparer IdentityComparer; /// /// Metadata observed by the compiler. /// May be shared across multiple Reference Managers. /// Access only under lock(). /// internal readonly Dictionary ObservedMetadata; /// /// Once this is non-zero the state of the manager is fully initialized and immutable. /// private int _isBound; /// /// True if the compilation has a reference that refers back to the assembly being compiled. /// /// /// If we have a circular reference the bound references can't be shared with other compilations. /// private ThreeState _lazyHasCircularReference; /// /// A map from a metadata reference to an index to array. Do not access /// directly, use property instead. /// private Dictionary _lazyReferencedAssembliesMap; /// /// A map from a net-module metadata reference to the index of the corresponding module /// symbol in the source assembly symbol for the current compilation. /// /// /// Subtract one from the index (for the manifest module) to find the corresponding elements /// of and . /// private Dictionary _lazyReferencedModuleIndexMap; /// /// Maps (containing syntax tree file name, reference string) of #r directive to a resolved metadata reference. /// If multiple #r's in the same tree use the same value as a reference the resolved metadata reference is the same as well. /// private IDictionary, MetadataReference> _lazyReferenceDirectiveMap; /// /// Array of unique bound #r references. /// /// /// The references are in the order they appear in syntax trees. This order is currently preserved /// as syntax trees are added or removed, but we might decide to share reference manager between compilations /// with different order of #r's. It doesn't seem this would be an issue since all #r's within the compilation /// have the same "priority" with respect to each other. /// private ImmutableArray _lazyDirectiveReferences; private ImmutableArray _lazyExplicitReferences; private ImmutableArray _lazyImplicitReferences; /// /// Diagnostics produced during reference resolution and binding. /// /// /// When reporting diagnostics be sure not to include any information that can't be shared among /// compilations that share the same reference manager (such as full identity of the compilation, /// simple assembly name is ok). /// private ImmutableArray _lazyDiagnostics; /// /// COR library symbol, or null if the compilation itself is the COR library. /// /// /// If the compilation being built is the COR library we don't want to store its source assembly symbol /// here since we wouldn't be able to share the state among subsequent compilations that are derived from it /// (each of them has its own source assembly symbol). /// private TAssemblySymbol _lazyCorLibraryOpt; /// /// Standalone modules referenced by the compilation (doesn't include the manifest module of the compilation). /// /// /// [i] corresponds to [i]. /// private ImmutableArray _lazyReferencedModules; /// /// References of standalone modules referenced by the compilation (doesn't include the manifest module of the compilation). /// /// /// [i] corresponds to [i]. /// private ImmutableArray> _lazyReferencedModulesReferences; /// /// Assemblies referenced directly by the source module of the compilation. /// private ImmutableArray _lazyReferencedAssemblies; /// /// Assemblies referenced directly by the source module of the compilation. /// /// /// Aliases [i] are of an assembly [i]. /// private ImmutableArray> _lazyAliasesOfReferencedAssemblies; /// /// Unified assemblies referenced directly by the source module of the compilation. /// private ImmutableArray> _lazyUnifiedAssemblies; public CommonReferenceManager(string simpleAssemblyName, AssemblyIdentityComparer identityComparer, Dictionary observedMetadata) { Debug.Assert(simpleAssemblyName != null); Debug.Assert(identityComparer != null); this.SimpleAssemblyName = simpleAssemblyName; this.IdentityComparer = identityComparer; this.ObservedMetadata = observedMetadata ?? new Dictionary(); } internal ImmutableArray Diagnostics { get { AssertBound(); return _lazyDiagnostics; } } internal bool HasCircularReference { get { AssertBound(); return _lazyHasCircularReference == ThreeState.True; } } internal Dictionary ReferencedAssembliesMap { get { AssertBound(); return _lazyReferencedAssembliesMap; } } internal Dictionary ReferencedModuleIndexMap { get { AssertBound(); return _lazyReferencedModuleIndexMap; } } internal IDictionary, MetadataReference> ReferenceDirectiveMap { get { AssertBound(); return _lazyReferenceDirectiveMap; } } internal ImmutableArray DirectiveReferences { get { AssertBound(); return _lazyDirectiveReferences; } } internal override ImmutableArray ImplicitReferences { get { AssertBound(); return _lazyImplicitReferences; } } internal override ImmutableArray ExplicitReferences { get { AssertBound(); return _lazyExplicitReferences; } } #region Symbols necessary to set up source assembly and module internal TAssemblySymbol CorLibraryOpt { get { AssertBound(); return _lazyCorLibraryOpt; } } internal ImmutableArray ReferencedModules { get { AssertBound(); return _lazyReferencedModules; } } internal ImmutableArray> ReferencedModulesReferences { get { AssertBound(); return _lazyReferencedModulesReferences; } } internal ImmutableArray ReferencedAssemblies { get { AssertBound(); return _lazyReferencedAssemblies; } } internal ImmutableArray> AliasesOfReferencedAssemblies { get { AssertBound(); return _lazyAliasesOfReferencedAssemblies; } } internal ImmutableArray> UnifiedAssemblies { get { AssertBound(); return _lazyUnifiedAssemblies; } } #endregion /// /// Call only while holding . /// [Conditional("DEBUG")] internal void AssertUnbound() { Debug.Assert(_isBound == 0); Debug.Assert(_lazyHasCircularReference == ThreeState.Unknown); Debug.Assert(_lazyReferencedAssembliesMap == null); Debug.Assert(_lazyReferencedModuleIndexMap == null); Debug.Assert(_lazyReferenceDirectiveMap == null); Debug.Assert(_lazyDirectiveReferences.IsDefault); Debug.Assert(_lazyImplicitReferences.IsDefault); Debug.Assert(_lazyExplicitReferences.IsDefault); Debug.Assert(_lazyReferencedModules.IsDefault); Debug.Assert(_lazyReferencedModulesReferences.IsDefault); Debug.Assert(_lazyReferencedAssemblies.IsDefault); Debug.Assert(_lazyAliasesOfReferencedAssemblies.IsDefault); Debug.Assert(_lazyUnifiedAssemblies.IsDefault); Debug.Assert(_lazyCorLibraryOpt == null); } [Conditional("DEBUG")] internal void AssertBound() { Debug.Assert(_isBound != 0); Debug.Assert(_lazyHasCircularReference != ThreeState.Unknown); Debug.Assert(_lazyReferencedAssembliesMap != null); Debug.Assert(_lazyReferencedModuleIndexMap != null); Debug.Assert(_lazyReferenceDirectiveMap != null); Debug.Assert(!_lazyDirectiveReferences.IsDefault); Debug.Assert(!_lazyImplicitReferences.IsDefault); Debug.Assert(!_lazyExplicitReferences.IsDefault); Debug.Assert(!_lazyReferencedModules.IsDefault); Debug.Assert(!_lazyReferencedModulesReferences.IsDefault); Debug.Assert(!_lazyReferencedAssemblies.IsDefault); Debug.Assert(!_lazyAliasesOfReferencedAssemblies.IsDefault); Debug.Assert(!_lazyUnifiedAssemblies.IsDefault); // lazyCorLibrary is null if the compilation is corlib Debug.Assert(_lazyReferencedAssemblies.Length == 0 || _lazyCorLibraryOpt != null); } [Conditional("DEBUG")] internal void AssertCanReuseForCompilation(TCompilation compilation) { Debug.Assert(compilation.MakeSourceAssemblySimpleName() == this.SimpleAssemblyName); } internal bool IsBound { get { return _isBound != 0; } } /// /// Call only while holding . /// internal void InitializeNoLock( Dictionary referencedAssembliesMap, Dictionary referencedModulesMap, IDictionary, MetadataReference> boundReferenceDirectiveMap, ImmutableArray directiveReferences, ImmutableArray explicitReferences, ImmutableArray implicitReferences, bool containsCircularReferences, ImmutableArray diagnostics, TAssemblySymbol corLibraryOpt, ImmutableArray referencedModules, ImmutableArray> referencedModulesReferences, ImmutableArray referencedAssemblies, ImmutableArray> aliasesOfReferencedAssemblies, ImmutableArray> unifiedAssemblies) { AssertUnbound(); Debug.Assert(referencedModules.Length == referencedModulesReferences.Length); Debug.Assert(referencedModules.Length == referencedModulesMap.Count); Debug.Assert(referencedAssemblies.Length == aliasesOfReferencedAssemblies.Length); _lazyReferencedAssembliesMap = referencedAssembliesMap; _lazyReferencedModuleIndexMap = referencedModulesMap; _lazyDiagnostics = diagnostics; _lazyReferenceDirectiveMap = boundReferenceDirectiveMap; _lazyDirectiveReferences = directiveReferences; _lazyExplicitReferences = explicitReferences; _lazyImplicitReferences = implicitReferences; _lazyCorLibraryOpt = corLibraryOpt; _lazyReferencedModules = referencedModules; _lazyReferencedModulesReferences = referencedModulesReferences; _lazyReferencedAssemblies = referencedAssemblies; _lazyAliasesOfReferencedAssemblies = aliasesOfReferencedAssemblies; _lazyUnifiedAssemblies = unifiedAssemblies; _lazyHasCircularReference = containsCircularReferences.ToThreeState(); // once we flip this bit the state of the manager is immutable and available to any readers: Interlocked.Exchange(ref _isBound, 1); } /// /// Global namespaces of assembly references that have been superseded by an assembly reference with a higher version are /// hidden behind to avoid ambiguity when they are accessed from source. /// All existing aliases of a superseded assembly are discarded. /// private static readonly ImmutableArray s_supersededAlias = ImmutableArray.Create(""); protected static void BuildReferencedAssembliesAndModulesMaps( BoundInputAssembly[] bindingResult, ImmutableArray references, ImmutableArray referenceMap, int referencedModuleCount, int explicitlyReferencedAsemblyCount, IReadOnlyDictionary> assemblyReferencesBySimpleName, bool supersedeLowerVersions, out Dictionary referencedAssembliesMap, out Dictionary referencedModulesMap, out ImmutableArray> aliasesOfReferencedAssemblies) { referencedAssembliesMap = new Dictionary(referenceMap.Length); referencedModulesMap = new Dictionary(referencedModuleCount); var aliasesOfReferencedAssembliesBuilder = ArrayBuilder>.GetInstance(referenceMap.Length - referencedModuleCount); bool hasRecursiveAliases = false; for (int i = 0; i < referenceMap.Length; i++) { if (referenceMap[i].IsSkipped) { continue; } if (referenceMap[i].Kind == MetadataImageKind.Module) { // add 1 for the manifest module: int moduleIndex = 1 + referenceMap[i].Index; referencedModulesMap.Add(references[i], moduleIndex); } else { // index into assembly data array int assemblyIndex = referenceMap[i].Index; Debug.Assert(aliasesOfReferencedAssembliesBuilder.Count == assemblyIndex); referencedAssembliesMap.Add(references[i], assemblyIndex); aliasesOfReferencedAssembliesBuilder.Add(referenceMap[i].AliasesOpt); hasRecursiveAliases |= !referenceMap[i].RecursiveAliasesOpt.IsDefault; } } if (hasRecursiveAliases) { PropagateRecursiveAliases(bindingResult, referenceMap, aliasesOfReferencedAssembliesBuilder); } Debug.Assert(!aliasesOfReferencedAssembliesBuilder.Any(a => a.IsDefault)); if (supersedeLowerVersions) { foreach (var assemblyReference in assemblyReferencesBySimpleName) { // the item in the list is the highest version, by construction for (int i = 1; i < assemblyReference.Value.Count; i++) { int assemblyIndex = assemblyReference.Value[i].GetAssemblyIndex(explicitlyReferencedAsemblyCount); aliasesOfReferencedAssembliesBuilder[assemblyIndex] = s_supersededAlias; } } } aliasesOfReferencedAssemblies = aliasesOfReferencedAssembliesBuilder.ToImmutableAndFree(); } /// /// Calculates map from the identities of specified symbols to the corresponding identities in the original EnC baseline metadata. /// The map only includes an entry for identities that differ, i.e. for symbols representing assembly references of the current compilation that have different identities /// than the corresponding identity in baseline metadata AssemblyRef table. The key comparer of the map ignores build and revision parts of the version number, /// since these might change if the original version included wildcard. /// /// Assembly symbols for references of the current compilation. /// Identities in the baseline. [i] corresponds to [i]. internal ImmutableDictionary GetAssemblyReferenceIdentityBaselineMap(ImmutableArray symbols, ImmutableArray originalIdentities) { Debug.Assert(originalIdentities.Length == symbols.Length); ImmutableDictionary.Builder lazyBuilder = null; for (int i = 0; i < originalIdentities.Length; i++) { var symbolIdentity = symbols[i].Identity; var versionPattern = symbols[i].AssemblyVersionPattern; var originalIdentity = originalIdentities[i]; if ((object)versionPattern != null) { Debug.Assert(versionPattern.Build == ushort.MaxValue || versionPattern.Revision == ushort.MaxValue); lazyBuilder = lazyBuilder ?? ImmutableDictionary.CreateBuilder(); var sourceIdentity = symbolIdentity.WithVersion(versionPattern); if (lazyBuilder.ContainsKey(sourceIdentity)) { // The compilation references multiple assemblies whose versions only differ in auto-generated build and/or revision numbers. throw new NotSupportedException(CodeAnalysisResources.CompilationReferencesAssembliesWithDifferentAutoGeneratedVersion); } lazyBuilder.Add(sourceIdentity, originalIdentity); } else { // by construction of the arguments: Debug.Assert(originalIdentity == symbolIdentity); } } return lazyBuilder?.ToImmutable() ?? ImmutableDictionary.Empty; } internal static bool CompareVersionPartsSpecifiedInSource(Version version, Version candidateVersion, TAssemblySymbol candidateSymbol) { // major and minor parts must match exactly if (version.Major != candidateVersion.Major || version.Minor != candidateVersion.Minor) { return false; } // build and revision parts can differ only if the corresponding source versions were auto-generated: var versionPattern = candidateSymbol.AssemblyVersionPattern; Debug.Assert((object)versionPattern == null || versionPattern.Build == ushort.MaxValue || versionPattern.Revision == ushort.MaxValue); if (((object)versionPattern == null || versionPattern.Build < ushort.MaxValue) && version.Build != candidateVersion.Build) { return false; } if ((object)versionPattern == null && version.Revision != candidateVersion.Revision) { return false; } return true; } // #r references are recursive, their aliases should be merged into all their dependencies. // // For example, if a compilation has a reference to LibA with alias A and the user #r's LibB with alias B, // which references LibA, LibA should be available under both aliases A and B. B is usually "global", // which means LibA namespaces should become available to the compilation without any qualification when #r LibB // is encountered. // // Pairs: (assembly index -- index into bindingResult array; index of the #r reference in referenceMap array). private static void PropagateRecursiveAliases( BoundInputAssembly[] bindingResult, ImmutableArray referenceMap, ArrayBuilder> aliasesOfReferencedAssembliesBuilder) { var assemblyIndicesToProcess = ArrayBuilder.GetInstance(); var visitedAssemblies = BitVector.Create(bindingResult.Length); // +1 for assembly being built Debug.Assert(bindingResult.Length == aliasesOfReferencedAssembliesBuilder.Count + 1); foreach (ResolvedReference reference in referenceMap) { if (!reference.IsSkipped && !reference.RecursiveAliasesOpt.IsDefault) { var recursiveAliases = reference.RecursiveAliasesOpt; Debug.Assert(reference.Kind == MetadataImageKind.Assembly); visitedAssemblies.Clear(); Debug.Assert(assemblyIndicesToProcess.Count == 0); assemblyIndicesToProcess.Add(reference.Index); while (assemblyIndicesToProcess.Count > 0) { int assemblyIndex = assemblyIndicesToProcess.Pop(); visitedAssemblies[assemblyIndex] = true; // merge aliases: aliasesOfReferencedAssembliesBuilder[assemblyIndex] = MergedAliases.Merge(aliasesOfReferencedAssembliesBuilder[assemblyIndex], recursiveAliases); // push dependencies onto the stack: // +1 for the assembly being built: foreach (var binding in bindingResult[assemblyIndex + 1].ReferenceBinding) { if (binding.IsBound) { // -1 for the assembly being built: int dependentAssemblyIndex = binding.DefinitionIndex - 1; if (!visitedAssemblies[dependentAssemblyIndex]) { assemblyIndicesToProcess.Add(dependentAssemblyIndex); } } } } } } for (int i = 0; i < aliasesOfReferencedAssembliesBuilder.Count; i++) { if (aliasesOfReferencedAssembliesBuilder[i].IsDefault) { aliasesOfReferencedAssembliesBuilder[i] = ImmutableArray.Empty; } } assemblyIndicesToProcess.Free(); } #region Compilation APIs Implementation // for testing purposes internal IEnumerable ExternAliases => AliasesOfReferencedAssemblies.SelectMany(aliases => aliases); internal sealed override IEnumerable> GetReferencedAssemblies() { return ReferencedAssembliesMap.Select(ra => KeyValuePair.Create(ra.Key, (IAssemblySymbol)ReferencedAssemblies[ra.Value])); } internal TAssemblySymbol GetReferencedAssemblySymbol(MetadataReference reference) { int index; return ReferencedAssembliesMap.TryGetValue(reference, out index) ? ReferencedAssemblies[index] : null; } internal int GetReferencedModuleIndex(MetadataReference reference) { int index; return ReferencedModuleIndexMap.TryGetValue(reference, out index) ? index : -1; } /// /// Gets the that corresponds to the assembly symbol. /// internal override MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol) { foreach (var entry in ReferencedAssembliesMap) { if ((object)ReferencedAssemblies[entry.Value] == assemblySymbol) { return entry.Key; } } return null; } internal override IEnumerable>> GetReferencedAssemblyAliases() { for (int i = 0; i < ReferencedAssemblies.Length; i++) { yield return ValueTuple.Create((IAssemblySymbol)ReferencedAssemblies[i], AliasesOfReferencedAssemblies[i]); } } public bool DeclarationsAccessibleWithoutAlias(int referencedAssemblyIndex) { var aliases = AliasesOfReferencedAssemblies[referencedAssemblyIndex]; return aliases.Length == 0 || aliases.IndexOf(MetadataReferenceProperties.GlobalAlias, StringComparer.Ordinal) >= 0; } internal override IEnumerable> GetImplicitlyResolvedAssemblyReferences() { foreach (PortableExecutableReference reference in ImplicitReferences) { yield return KeyValuePair.Create(ReferencedAssemblies[ReferencedAssembliesMap[reference]].Identity, reference); } } #endregion } }