// 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
}
}