|
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
internal partial class CommonReferenceManager<TCompilation, TAssemblySymbol>
{
private static readonly ObjectPool<MultiDictionary<string, (AssemblyData DefinitionData, int DefinitionIndex)>> s_pool =
new ObjectPool<MultiDictionary<string, (AssemblyData DefinitionData, int DefinitionIndex)>>(() => new MultiDictionary<string, (AssemblyData DefinitionData, int DefinitionIndex)>(AssemblyIdentityComparer.SimpleNameComparer));
/// <summary>
/// For the given set of AssemblyData objects, do the following:
/// 1) Resolve references from each assembly against other assemblies in the set.
/// 2) Choose suitable AssemblySymbol instance for each AssemblyData object.
///
/// The first element (index==0) of the assemblies array represents the assembly being built.
/// One can think about the rest of the items in assemblies array as assembly references given to the compiler to
/// build executable for the assembly being built.
/// </summary>
/// <param name="explicitAssemblies">
/// An array of <see cref="AssemblyData"/> objects describing assemblies, for which this method should
/// resolve references and find suitable AssemblySymbols. The first slot contains the assembly being built.
/// </param>
/// <param name="explicitModules">
/// An array of <see cref="PEModule"/> objects describing standalone modules referenced by the compilation.
/// </param>
/// <param name="explicitReferences">
/// An array of references passed to the compilation and resolved from #r directives.
/// May contain references that were skipped during resolution (they don't have a corresponding explicit assembly).
/// </param>
/// <param name="explicitReferenceMap">
/// Maps index to <paramref name="explicitReferences"/> to an index of a resolved assembly or module in <paramref name="explicitAssemblies"/> or modules.
/// </param>
/// <param name="resolverOpt">
/// Reference resolver used to look up missing assemblies.
/// </param>
/// <param name="supersedeLowerVersions">
/// Hide lower versions of dependencies that have multiple versions behind an alias.
/// </param>
/// <param name="assemblyReferencesBySimpleName">
/// Used to filter out assemblies that have the same strong or weak identity.
/// Maps simple name to a list of identities. The highest version of each name is the first.
/// </param>
/// <param name="importOptions">
/// Import options applied to implicitly resolved references.
/// </param>
/// <param name="allAssemblies">
/// Updated array <paramref name="explicitAssemblies"/> with resolved implicitly referenced assemblies appended.
/// </param>
/// <param name="implicitlyResolvedReferences">
/// Implicitly resolved references.
/// </param>
/// <param name="implicitlyResolvedReferenceMap">
/// Maps indices of implicitly resolved references to the corresponding indices of resolved assemblies in <paramref name="allAssemblies"/> (explicit + implicit).
/// </param>
/// <param name="implicitReferenceResolutions">
/// Map of implicit reference resolutions performed in the preceding script compilation.
/// Output contains additional implicit resolutions performed during binding of this script compilation references.
/// </param>
/// <param name="resolutionDiagnostics">
/// Any diagnostics reported while resolving missing assemblies.
/// </param>
/// <param name="hasCircularReference">
/// True if the assembly being compiled is indirectly referenced through some of its own references.
/// </param>
/// <param name="corLibraryIndex">
/// The definition index of the COR library.
/// </param>
/// <return>
/// An array of <see cref="BoundInputAssembly"/> structures describing the result. It has the same amount of items as
/// the input assemblies array, <see cref="BoundInputAssembly"/> for each input AssemblyData object resides
/// at the same position.
///
/// Each <see cref="BoundInputAssembly"/> contains the following data:
///
/// - Suitable AssemblySymbol instance for the corresponding assembly,
/// null reference if none is available/found. Always null for the first element, which corresponds to the assembly being built.
///
/// - Result of resolving assembly references of the corresponding assembly
/// against provided set of assembly definitions. Essentially, this is an array returned by
/// <see cref="AssemblyData.BindAssemblyReferences"/> method.
/// </return>
protected BoundInputAssembly[] Bind(
ImmutableArray<AssemblyData> explicitAssemblies,
ImmutableArray<PEModule> explicitModules,
ImmutableArray<MetadataReference> explicitReferences,
ImmutableArray<ResolvedReference> explicitReferenceMap,
MetadataReferenceResolver? resolverOpt,
MetadataImportOptions importOptions,
bool supersedeLowerVersions,
[In, Out] Dictionary<string, List<ReferencedAssemblyIdentity>> assemblyReferencesBySimpleName,
out ImmutableArray<AssemblyData> allAssemblies,
out ImmutableArray<MetadataReference> implicitlyResolvedReferences,
out ImmutableArray<ResolvedReference> implicitlyResolvedReferenceMap,
ref ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> implicitReferenceResolutions,
[In, Out] DiagnosticBag resolutionDiagnostics,
out bool hasCircularReference,
out int corLibraryIndex)
{
Debug.Assert(explicitAssemblies[0] is AssemblyDataForAssemblyBeingBuilt);
Debug.Assert(explicitReferences.Length == explicitReferenceMap.Length);
var referenceBindings = ArrayBuilder<AssemblyReferenceBinding[]>.GetInstance();
var explicitAssembliesMap = s_pool.Allocate();
explicitAssembliesMap.EnsureCapacity(explicitAssemblies.Length);
try
{
for (int i = 0; i < explicitAssemblies.Length; i++)
{
explicitAssembliesMap.Add(explicitAssemblies[i].Identity.Name, (explicitAssemblies[i], i));
}
// Based on assembly identity, for each assembly,
// bind its references against the other assemblies we have.
for (int i = 0; i < explicitAssemblies.Length; i++)
{
referenceBindings.Add(explicitAssemblies[i].BindAssemblyReferences(explicitAssembliesMap, IdentityComparer));
}
if (resolverOpt?.ResolveMissingAssemblies == true)
{
ResolveAndBindMissingAssemblies(
explicitAssemblies,
explicitAssembliesMap,
explicitModules,
explicitReferences,
explicitReferenceMap,
resolverOpt,
importOptions,
supersedeLowerVersions,
referenceBindings,
assemblyReferencesBySimpleName,
out allAssemblies,
out implicitlyResolvedReferences,
out implicitlyResolvedReferenceMap,
ref implicitReferenceResolutions,
resolutionDiagnostics);
}
else
{
allAssemblies = explicitAssemblies;
implicitlyResolvedReferences = ImmutableArray<MetadataReference>.Empty;
implicitlyResolvedReferenceMap = ImmutableArray<ResolvedReference>.Empty;
}
// implicitly resolved missing assemblies were added to both referenceBindings and assemblies:
Debug.Assert(referenceBindings.Count == allAssemblies.Length);
hasCircularReference = CheckCircularReference(referenceBindings);
corLibraryIndex = IndexOfCorLibrary(explicitAssemblies, assemblyReferencesBySimpleName, supersedeLowerVersions);
// For each assembly, locate AssemblySymbol with similar reference resolution
// What does similar mean?
// Similar means:
// 1) The same references are resolved against the assemblies that we are given
// (or were found during implicit assembly resolution).
// 2) The same assembly is used as the COR library.
var boundInputs = new BoundInputAssembly[referenceBindings.Count];
for (int i = 0; i < referenceBindings.Count; i++)
{
boundInputs[i].ReferenceBinding = referenceBindings[i];
}
var candidateInputAssemblySymbols = new TAssemblySymbol[allAssemblies.Length];
// If any assembly from assemblies array refers back to assemblyBeingBuilt,
// we know that we cannot reuse symbols for any assemblies containing NoPia
// local types. Because we cannot reuse symbols for assembly referring back
// to assemblyBeingBuilt.
if (!hasCircularReference)
{
// Deal with assemblies containing NoPia local types.
if (ReuseAssemblySymbolsWithNoPiaLocalTypes(boundInputs, candidateInputAssemblySymbols, allAssemblies, corLibraryIndex))
{
return boundInputs;
}
}
// NoPia shortcut either didn't apply or failed, go through general process
// of matching candidates.
ReuseAssemblySymbols(boundInputs, candidateInputAssemblySymbols, allAssemblies, corLibraryIndex);
return boundInputs;
}
finally
{
explicitAssembliesMap.Clear();
s_pool.Free(explicitAssembliesMap);
referenceBindings.Free();
}
}
private void ResolveAndBindMissingAssemblies(
ImmutableArray<AssemblyData> explicitAssemblies,
MultiDictionary<string, (AssemblyData DefinitionData, int DefinitionIndex)> explicitAssembliesMap,
ImmutableArray<PEModule> explicitModules,
ImmutableArray<MetadataReference> explicitReferences,
ImmutableArray<ResolvedReference> explicitReferenceMap,
MetadataReferenceResolver resolver,
MetadataImportOptions importOptions,
bool supersedeLowerVersions,
[In, Out] ArrayBuilder<AssemblyReferenceBinding[]> referenceBindings,
[In, Out] Dictionary<string, List<ReferencedAssemblyIdentity>> assemblyReferencesBySimpleName,
out ImmutableArray<AssemblyData> allAssemblies,
out ImmutableArray<MetadataReference> metadataReferences,
out ImmutableArray<ResolvedReference> resolvedReferences,
ref ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> implicitReferenceResolutions,
DiagnosticBag resolutionDiagnostics)
{
Debug.Assert(explicitAssemblies[0] is AssemblyDataForAssemblyBeingBuilt);
Debug.Assert(referenceBindings.Count == explicitAssemblies.Length);
Debug.Assert(explicitReferences.Length == explicitReferenceMap.Length);
// -1 for assembly being built:
int totalReferencedAssemblyCount = explicitAssemblies.Length - 1;
var implicitAssemblies = ArrayBuilder<AssemblyData>.GetInstance();
// assembly identities whose resolution failed for all attempted requesting references:
var resolutionFailures = PooledHashSet<AssemblyIdentity>.GetInstance();
var metadataReferencesBuilder = ArrayBuilder<MetadataReference>.GetInstance();
Dictionary<MetadataReference, MergedAliases>? lazyAliasMap = null;
// metadata references and corresponding bindings of their references, used to calculate a fixed point:
var referenceBindingsToProcess = ArrayBuilder<(MetadataReference, ArraySegment<AssemblyReferenceBinding>)>.GetInstance();
// collect all missing identities, resolve the assemblies and bind their references against explicit definitions:
GetInitialReferenceBindingsToProcess(explicitModules, explicitReferences, explicitReferenceMap, referenceBindings, totalReferencedAssemblyCount, referenceBindingsToProcess);
// NB: includes the assembly being built:
int explicitAssemblyCount = explicitAssemblies.Length;
MultiDictionary<string, (CommonReferenceManager<TCompilation, TAssemblySymbol>.AssemblyData DefinitionData, int DefinitionIndex)>? implicitAssembliesMap = null;
try
{
while (referenceBindingsToProcess.Count > 0)
{
var (requestingReference, bindings) = referenceBindingsToProcess.Pop();
foreach (var binding in bindings)
{
// only attempt to resolve unbound references (regardless of version difference of the bound ones)
if (binding.IsBound)
{
continue;
}
Debug.Assert(binding.ReferenceIdentity is object);
if (!TryResolveMissingReference(
requestingReference,
binding.ReferenceIdentity,
ref implicitReferenceResolutions,
resolver,
resolutionDiagnostics,
out AssemblyIdentity? resolvedAssemblyIdentity,
out AssemblyMetadata? resolvedAssemblyMetadata,
out PortableExecutableReference? resolvedReference))
{
// Note the failure, but do not commit it to implicitReferenceResolutions until we are done with resolving all missing references.
resolutionFailures.Add(binding.ReferenceIdentity);
continue;
}
// One attempt for resolution succeeded. The attempt is cached in implicitReferenceResolutions, so further attempts won't fail and add it back.
// Since the failures tracked in resolutionFailures do not affect binding there is no need to revert any decisions made so far.
resolutionFailures.Remove(binding.ReferenceIdentity);
// The resolver may return different version than we asked for, so it may happen that
// it returns the same identity for two different input identities (e.g. if a higher version
// of an assembly is available than what the assemblies reference: "A, v1" -> "A, v3" and "A, v2" -> "A, v3").
// If such case occurs merge the properties (aliases) of the resulting references in the same way we do
// during initial explicit references resolution.
// -1 for assembly being built:
int index = explicitAssemblyCount - 1 + metadataReferencesBuilder.Count;
var existingReference = TryAddAssembly(resolvedAssemblyIdentity, resolvedReference, index, resolutionDiagnostics, Location.None, assemblyReferencesBySimpleName, supersedeLowerVersions);
if (existingReference != null)
{
MergeReferenceProperties(existingReference, resolvedReference, resolutionDiagnostics, ref lazyAliasMap);
continue;
}
metadataReferencesBuilder.Add(resolvedReference);
var data = CreateAssemblyDataForResolvedMissingAssembly(resolvedAssemblyMetadata, resolvedReference, importOptions);
implicitAssemblies.Add(data);
var referenceBinding = data.BindAssemblyReferences(explicitAssembliesMap, IdentityComparer);
referenceBindings.Add(referenceBinding);
referenceBindingsToProcess.Push((resolvedReference, new ArraySegment<AssemblyReferenceBinding>(referenceBinding)));
}
}
// record failures for resolution in subsequent submissions:
foreach (var assemblyIdentity in resolutionFailures)
{
implicitReferenceResolutions = implicitReferenceResolutions.Add(assemblyIdentity, null);
}
if (implicitAssemblies.Count == 0)
{
Debug.Assert(lazyAliasMap == null);
resolvedReferences = ImmutableArray<ResolvedReference>.Empty;
metadataReferences = ImmutableArray<MetadataReference>.Empty;
allAssemblies = explicitAssemblies;
return;
}
// Rebind assembly references that were initially missing. All bindings established above
// are against explicitly specified references.
// We only need to resolve against implicitly resolved assemblies,
// since we already resolved against explicitly specified ones.
implicitAssembliesMap = s_pool.Allocate();
implicitAssembliesMap.EnsureCapacity(implicitAssemblies.Count);
for (int i = 0; i < implicitAssemblies.Count; i++)
{
implicitAssembliesMap.Add(implicitAssemblies[i].Identity.Name, (implicitAssemblies[i], explicitAssemblyCount + i));
}
allAssemblies = explicitAssemblies.AddRange(implicitAssemblies);
for (int bindingsIndex = 0; bindingsIndex < referenceBindings.Count; bindingsIndex++)
{
var referenceBinding = referenceBindings[bindingsIndex];
for (int i = 0; i < referenceBinding.Length; i++)
{
var binding = referenceBinding[i];
// We don't rebind references bound to a non-matching version of a reference that was explicitly
// specified, even if we have a better version now.
if (binding.IsBound)
{
continue;
}
// We only need to resolve against implicitly resolved assemblies,
// since we already resolved against explicitly specified ones.
Debug.Assert(binding.ReferenceIdentity is object);
referenceBinding[i] = ResolveReferencedAssembly(
binding.ReferenceIdentity,
implicitAssembliesMap,
resolveAgainstAssemblyBeingBuilt: false,
IdentityComparer);
}
}
UpdateBindingsOfAssemblyBeingBuilt(referenceBindings, explicitAssemblyCount, implicitAssemblies);
metadataReferences = metadataReferencesBuilder.ToImmutable();
resolvedReferences = ToResolvedAssemblyReferences(metadataReferences, lazyAliasMap, explicitAssemblyCount);
}
finally
{
if (implicitAssembliesMap is not null)
{
implicitAssembliesMap.Clear();
s_pool.Free(implicitAssembliesMap);
}
implicitAssemblies.Free();
referenceBindingsToProcess.Free();
metadataReferencesBuilder.Free();
resolutionFailures.Free();
}
}
private void GetInitialReferenceBindingsToProcess(
ImmutableArray<PEModule> explicitModules,
ImmutableArray<MetadataReference> explicitReferences,
ImmutableArray<ResolvedReference> explicitReferenceMap,
ArrayBuilder<AssemblyReferenceBinding[]> referenceBindings,
int totalReferencedAssemblyCount,
[Out] ArrayBuilder<(MetadataReference, ArraySegment<AssemblyReferenceBinding>)> result)
{
Debug.Assert(result.Count == 0);
// maps module index to explicitReferences index
var explicitModuleToReferenceMap = CalculateModuleToReferenceMap(explicitModules, explicitReferenceMap);
// add module bindings of assembly being built:
var bindingsOfAssemblyBeingBuilt = referenceBindings[0];
int bindingIndex = totalReferencedAssemblyCount;
for (int moduleIndex = 0; moduleIndex < explicitModules.Length; moduleIndex++)
{
var moduleReference = explicitReferences[explicitModuleToReferenceMap[moduleIndex]];
var moduleBindingsCount = explicitModules[moduleIndex].ReferencedAssemblies.Length;
result.Add(
(moduleReference,
new ArraySegment<AssemblyReferenceBinding>(bindingsOfAssemblyBeingBuilt, bindingIndex, moduleBindingsCount)));
bindingIndex += moduleBindingsCount;
}
Debug.Assert(bindingIndex == bindingsOfAssemblyBeingBuilt.Length);
// the first binding is for the assembly being built, all its references are bound or added above
for (int referenceIndex = 0; referenceIndex < explicitReferenceMap.Length; referenceIndex++)
{
var explicitReferenceMapping = explicitReferenceMap[referenceIndex];
if (explicitReferenceMapping.IsSkipped || explicitReferenceMapping.Kind == MetadataImageKind.Module)
{
continue;
}
// +1 for the assembly being built
result.Add(
(explicitReferences[referenceIndex],
new ArraySegment<AssemblyReferenceBinding>(referenceBindings[explicitReferenceMapping.Index + 1])));
}
// we have a reference binding for each module and for each referenced assembly:
Debug.Assert(result.Count == explicitModules.Length + totalReferencedAssemblyCount);
}
private static ImmutableArray<int> CalculateModuleToReferenceMap(ImmutableArray<PEModule> modules, ImmutableArray<ResolvedReference> resolvedReferences)
{
if (modules.Length == 0)
{
return ImmutableArray<int>.Empty;
}
var result = ArrayBuilder<int>.GetInstance(modules.Length);
result.ZeroInit(modules.Length);
for (int i = 0; i < resolvedReferences.Length; i++)
{
var resolvedReference = resolvedReferences[i];
if (!resolvedReference.IsSkipped && resolvedReference.Kind == MetadataImageKind.Module)
{
result[resolvedReference.Index] = i;
}
}
return result.ToImmutableAndFree();
}
private static ImmutableArray<ResolvedReference> ToResolvedAssemblyReferences(
ImmutableArray<MetadataReference> references,
Dictionary<MetadataReference, MergedAliases>? propertyMapOpt,
int explicitAssemblyCount)
{
var result = ArrayBuilder<ResolvedReference>.GetInstance(references.Length);
for (int i = 0; i < references.Length; i++)
{
// -1 for assembly being built
result.Add(GetResolvedReferenceAndFreePropertyMapEntry(references[i], explicitAssemblyCount - 1 + i, MetadataImageKind.Assembly, propertyMapOpt));
}
return result.ToImmutableAndFree();
}
private static void UpdateBindingsOfAssemblyBeingBuilt(ArrayBuilder<AssemblyReferenceBinding[]> referenceBindings, int explicitAssemblyCount, ArrayBuilder<AssemblyData> implicitAssemblies)
{
var referenceBindingsOfAssemblyBeingBuilt = referenceBindings[0];
// add implicitly resolved assemblies to the bindings of the assembly being built:
var bindingsOfAssemblyBeingBuilt = ArrayBuilder<AssemblyReferenceBinding>.GetInstance(referenceBindingsOfAssemblyBeingBuilt.Length + implicitAssemblies.Count);
// add bindings for explicitly specified assemblies (-1 for the assembly being built):
bindingsOfAssemblyBeingBuilt.AddRange(referenceBindingsOfAssemblyBeingBuilt, explicitAssemblyCount - 1);
// add bindings for implicitly resolved assemblies:
for (int i = 0; i < implicitAssemblies.Count; i++)
{
bindingsOfAssemblyBeingBuilt.Add(new AssemblyReferenceBinding(implicitAssemblies[i].Identity, explicitAssemblyCount + i));
}
// add bindings for assemblies referenced by modules added to the compilation:
bindingsOfAssemblyBeingBuilt.AddRange(referenceBindingsOfAssemblyBeingBuilt, explicitAssemblyCount - 1, referenceBindingsOfAssemblyBeingBuilt.Length - explicitAssemblyCount + 1);
referenceBindings[0] = bindingsOfAssemblyBeingBuilt.ToArrayAndFree();
}
/// <summary>
/// Resolve <paramref name="referenceIdentity"/> using a given <paramref name="resolver"/>.
///
/// We make sure not to query the resolver for the same identity multiple times (across submissions).
/// Doing so ensures that we don't create multiple assembly symbols within the same chain of script compilations
/// for the same implicitly resolved identity. Failure to do so results in cast errors like "can't convert T to T".
///
/// The method only records successful resolution results by updating <paramref name="implicitReferenceResolutions"/>.
/// Failures are only recorded after all resolution attempts have been completed.
///
/// This approach addresses the following scenario. Consider a script:
/// <code>
/// #r "dir1\a.dll"
/// #r "dir2\b.dll"
/// </code>
/// where both a.dll and b.dll reference x.dll, which is present only in dir2. Let's assume the resolver first
/// attempts to resolve "x" referenced from "dir1\a.dll". The resolver may fail to find the dependency if it only
/// looks up the directory containing the referencing assembly (dir1). If we recorded and this failure immediately
/// we would not call the resolver to resolve "x" within the context of "dir2\b.dll" (or any other referencing assembly).
///
/// This behavior would ensure consistency and if the types from x.dll do leak thru to the script compilation, but it
/// would result in a missing assembly error. By recording the failure after all resolution attempts are complete
/// we also achieve a consistent behavior but are able to bind the reference to "x.dll". Besides, this approach
/// also avoids dependency on the order in which we evaluate the assembly references in the scenario above.
/// In general, the result of the resolution may still depend on the order of #r - if there are different assemblies
/// of the same identity in different directories.
/// </summary>
private bool TryResolveMissingReference(
MetadataReference requestingReference,
AssemblyIdentity referenceIdentity,
ref ImmutableDictionary<AssemblyIdentity, PortableExecutableReference?> implicitReferenceResolutions,
MetadataReferenceResolver resolver,
DiagnosticBag resolutionDiagnostics,
[NotNullWhen(true)] out AssemblyIdentity? resolvedAssemblyIdentity,
[NotNullWhen(true)] out AssemblyMetadata? resolvedAssemblyMetadata,
[NotNullWhen(true)] out PortableExecutableReference? resolvedReference)
{
resolvedAssemblyIdentity = null;
resolvedAssemblyMetadata = null;
bool isNewlyResolvedReference = false;
// Check if we have previously resolved an identity and reuse the previously resolved reference if so.
// Use the resolver to find the missing reference.
// Note that the resolver may return an assembly of a different identity than requested, e.g. a higher version.
if (!implicitReferenceResolutions.TryGetValue(referenceIdentity, out resolvedReference))
{
resolvedReference = resolver.ResolveMissingAssembly(requestingReference, referenceIdentity);
isNewlyResolvedReference = true;
}
if (resolvedReference == null)
{
return false;
}
resolvedAssemblyMetadata = GetAssemblyMetadata(resolvedReference, resolutionDiagnostics);
if (resolvedAssemblyMetadata == null)
{
return false;
}
var resolvedAssembly = resolvedAssemblyMetadata.GetAssembly();
Debug.Assert(resolvedAssembly is object);
// Allow reference and definition identities to differ in version, but not other properties.
// Don't need to compare if we are reusing a previously resolved reference.
if (isNewlyResolvedReference &&
IdentityComparer.Compare(referenceIdentity, resolvedAssembly.Identity) == AssemblyIdentityComparer.ComparisonResult.NotEquivalent)
{
return false;
}
resolvedAssemblyIdentity = resolvedAssembly.Identity;
implicitReferenceResolutions = implicitReferenceResolutions.Add(referenceIdentity, resolvedReference);
return true;
}
private AssemblyData CreateAssemblyDataForResolvedMissingAssembly(
AssemblyMetadata assemblyMetadata,
PortableExecutableReference peReference,
MetadataImportOptions importOptions)
{
var assembly = assemblyMetadata.GetAssembly();
Debug.Assert(assembly is object);
return CreateAssemblyDataForFile(
assembly,
assemblyMetadata.CachedSymbols,
peReference.DocumentationProvider,
SimpleAssemblyName,
importOptions,
peReference.Properties.EmbedInteropTypes);
}
private bool ReuseAssemblySymbolsWithNoPiaLocalTypes(BoundInputAssembly[] boundInputs, TAssemblySymbol[] candidateInputAssemblySymbols, ImmutableArray<AssemblyData> assemblies, int corLibraryIndex)
{
int totalAssemblies = assemblies.Length;
for (int i = 1; i < totalAssemblies; i++)
{
if (!assemblies[i].ContainsNoPiaLocalTypes)
{
continue;
}
foreach (TAssemblySymbol candidateAssembly in assemblies[i].AvailableSymbols)
{
// Candidate should be referenced the same way (/r or /l) by the compilation,
// which originated the symbols. We need this restriction in order to prevent
// non-interface generic types closed over NoPia local types from crossing
// assembly boundaries.
if (IsLinked(candidateAssembly) != assemblies[i].IsLinked)
{
continue;
}
ImmutableArray<TAssemblySymbol> resolutionAssemblies = GetNoPiaResolutionAssemblies(candidateAssembly);
if (resolutionAssemblies.IsDefault)
{
continue;
}
Array.Clear(candidateInputAssemblySymbols, 0, candidateInputAssemblySymbols.Length);
// In order to reuse candidateAssembly, we need to make sure that
// 1) all assemblies in resolutionAssemblies are among assemblies represented
// by assemblies array.
// 2) From assemblies represented by assemblies array all assemblies, except
// assemblyBeingBuilt are among resolutionAssemblies.
bool match = true;
foreach (TAssemblySymbol assembly in resolutionAssemblies)
{
match = false;
for (int j = 1; j < totalAssemblies; j++)
{
if (assemblies[j].IsMatchingAssembly(assembly) &&
IsLinked(assembly) == assemblies[j].IsLinked)
{
candidateInputAssemblySymbols[j] = assembly;
match = true;
// We could break out of the loop unless assemblies array
// can contain duplicate values. Let's play safe and loop
// through all items.
}
}
if (!match)
{
// Requirement #1 is not met.
break;
}
}
if (!match)
{
continue;
}
for (int j = 1; j < totalAssemblies; j++)
{
if (candidateInputAssemblySymbols[j] == null)
{
// Requirement #2 is not met.
match = false;
break;
}
else
{
// Let's check if different assembly is used as the COR library.
// It shouldn't be possible to get in this situation, but let's play safe.
if (corLibraryIndex < 0)
{
// we don't have COR library.
if (GetCorLibrary(candidateInputAssemblySymbols[j]) != null)
{
// but this assembly has
// I am leaving the Assert here because it will likely indicate a bug somewhere.
Debug.Assert(GetCorLibrary(candidateInputAssemblySymbols[j]) == null);
match = false;
break;
}
}
else
{
// We can't be compiling corlib and have a corlib reference at the same time:
Debug.Assert(corLibraryIndex != 0);
// We have COR library, it should match COR library of the candidate.
if (!ReferenceEquals(candidateInputAssemblySymbols[corLibraryIndex], GetCorLibrary(candidateInputAssemblySymbols[j])))
{
// I am leaving the Assert here because it will likely indicate a bug somewhere.
Debug.Assert(candidateInputAssemblySymbols[corLibraryIndex] == null);
match = false;
break;
}
}
}
}
if (match)
{
// We found a match, use it.
for (int j = 1; j < totalAssemblies; j++)
{
Debug.Assert(candidateInputAssemblySymbols[j] != null);
boundInputs[j].AssemblySymbol = candidateInputAssemblySymbols[j];
}
return true;
}
}
// Prepare candidateMatchingSymbols for next operations.
Array.Clear(candidateInputAssemblySymbols, 0, candidateInputAssemblySymbols.Length);
// Why it doesn't make sense to examine other assemblies with local types?
// Since we couldn't find a suitable match for this assembly,
// we know that requirement #2 cannot be met for any other assembly
// containing local types.
break;
}
return false;
}
private static readonly ObjectPool<Queue<AssemblyReferenceCandidate>> s_candidatesToExaminePool = new ObjectPool<Queue<AssemblyReferenceCandidate>>(() => new Queue<AssemblyReferenceCandidate>());
private static readonly ObjectPool<List<TAssemblySymbol?>> s_candidateReferencedSymbolsPool = new ObjectPool<List<TAssemblySymbol?>>(() => new List<TAssemblySymbol?>(capacity: 1024));
private void ReuseAssemblySymbols(BoundInputAssembly[] boundInputs, TAssemblySymbol[] candidateInputAssemblySymbols, ImmutableArray<AssemblyData> assemblies, int corLibraryIndex)
{
// Queue of references we need to examine for consistency
Queue<AssemblyReferenceCandidate> candidatesToExamine = s_candidatesToExaminePool.Allocate();
// A reusable buffer to contain the AssemblySymbols a candidate symbol refers to
// ⚠ PERF: https://github.com/dotnet/roslyn/issues/47471
List<TAssemblySymbol?> candidateReferencedSymbols = s_candidateReferencedSymbolsPool.Allocate();
try
{
int totalAssemblies = assemblies.Length;
for (int i = 1; i < totalAssemblies; i++)
{
// We could have a match already
if (boundInputs[i].AssemblySymbol != null || assemblies[i].ContainsNoPiaLocalTypes)
{
continue;
}
foreach (TAssemblySymbol candidateAssembly in assemblies[i].AvailableSymbols)
{
bool match = true;
// We should examine this candidate, all its references that are supposed to
// match one of the given assemblies and do the same for their references, etc.
// The whole set of symbols we get at the end should be consistent with the set
// of assemblies we are given. The whole set of symbols should be accepted or rejected.
// The set of symbols is accumulated in candidateInputAssemblySymbols. It is merged into
// boundInputs after consistency is confirmed.
Array.Clear(candidateInputAssemblySymbols, 0, candidateInputAssemblySymbols.Length);
// Symbols and index of the corresponding assembly to match against are accumulated in the
// candidatesToExamine queue. They are examined one by one.
candidatesToExamine.Clear();
// This is a queue of symbols that we are picking up as a result of using
// symbols from candidateAssembly
candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(i, candidateAssembly));
while (match && candidatesToExamine.Count > 0)
{
AssemblyReferenceCandidate candidate = candidatesToExamine.Dequeue();
Debug.Assert(candidate.DefinitionIndex >= 0);
Debug.Assert(candidate.AssemblySymbol is object);
int candidateIndex = candidate.DefinitionIndex;
// Have we already chosen symbols for the corresponding assembly?
Debug.Assert(boundInputs[candidateIndex].AssemblySymbol == null ||
candidateInputAssemblySymbols[candidateIndex] == null);
TAssemblySymbol? inputAssembly = boundInputs[candidateIndex].AssemblySymbol;
if (inputAssembly == null)
{
inputAssembly = candidateInputAssemblySymbols[candidateIndex];
}
if (inputAssembly != null)
{
if (Object.ReferenceEquals(inputAssembly, candidate.AssemblySymbol))
{
// We already checked this AssemblySymbol, no reason to check it again
continue; // Proceed with the next assembly in candidatesToExamine queue.
}
// We are using different AssemblySymbol for this assembly
match = false;
break; // Stop processing items from candidatesToExamine queue.
}
// Candidate should be referenced the same way (/r or /l) by the compilation,
// which originated the symbols. We need this restriction in order to prevent
// non-interface generic types closed over NoPia local types from crossing
// assembly boundaries.
if (IsLinked(candidate.AssemblySymbol) != assemblies[candidateIndex].IsLinked)
{
match = false;
break; // Stop processing items from candidatesToExamine queue.
}
// Add symbols to the set at corresponding index
Debug.Assert(candidateInputAssemblySymbols[candidateIndex] == null);
candidateInputAssemblySymbols[candidateIndex] = candidate.AssemblySymbol;
// Now process references of the candidate.
// how we bound the candidate references for this compilation:
var candidateReferenceBinding = boundInputs[candidateIndex].ReferenceBinding;
// get the AssemblySymbols the candidate symbol refers to into candidateReferencedSymbols
candidateReferencedSymbols.Clear();
GetActualBoundReferencesUsedBy(candidate.AssemblySymbol, candidateReferencedSymbols);
Debug.Assert(candidateReferenceBinding is object);
Debug.Assert(candidateReferenceBinding.Length == candidateReferencedSymbols.Count);
int referencesCount = candidateReferencedSymbols.Count;
for (int k = 0; k < referencesCount; k++)
{
// All candidate's references that were /l-ed by the compilation,
// which originated the symbols, must be /l-ed by this compilation and
// other references must be either /r-ed or not referenced.
// We need this restriction in order to prevent non-interface generic types
// closed over NoPia local types from crossing assembly boundaries.
// if target reference isn't resolved against given assemblies,
// we cannot accept a candidate that has the reference resolved.
if (!candidateReferenceBinding[k].IsBound)
{
if (candidateReferencedSymbols[k] != null)
{
// can't use symbols
// If we decide do go back to accepting references like this,
// we should still not do this if the reference is a /l-ed assembly.
match = false;
break; // Stop processing references.
}
continue; // Proceed with the next reference.
}
// We resolved the reference, candidate must have that reference resolved too.
var currentCandidateReferencedSymbol = candidateReferencedSymbols[k];
if (currentCandidateReferencedSymbol == null)
{
// can't use symbols
match = false;
break; // Stop processing references.
}
int definitionIndex = candidateReferenceBinding[k].DefinitionIndex;
if (definitionIndex == 0)
{
// We can't reuse any assembly that refers to the assembly being built.
match = false;
break;
}
// Make sure symbols represent the same assembly/binary
if (!assemblies[definitionIndex].IsMatchingAssembly(currentCandidateReferencedSymbol))
{
// Mismatch between versions?
match = false;
break; // Stop processing references.
}
if (assemblies[definitionIndex].ContainsNoPiaLocalTypes)
{
// We already know that we cannot reuse any existing symbols for
// this assembly
match = false;
break; // Stop processing references.
}
if (IsLinked(currentCandidateReferencedSymbol) != assemblies[definitionIndex].IsLinked)
{
// Mismatch between reference kind.
match = false;
break; // Stop processing references.
}
// Add this reference to the queue so that we consider it as a candidate too
candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(definitionIndex, currentCandidateReferencedSymbol));
}
// Check that the COR library used by the candidate assembly symbol is the same as the one use by this compilation.
if (match)
{
TAssemblySymbol? candidateCorLibrary = GetCorLibrary(candidate.AssemblySymbol);
if (candidateCorLibrary == null)
{
// If the candidate didn't have a COR library, that is fine as long as we don't have one either.
if (corLibraryIndex >= 0)
{
match = false;
break; // Stop processing references.
}
}
else
{
// We can't be compiling corlib and have a corlib reference at the same time:
Debug.Assert(corLibraryIndex != 0);
Debug.Assert(ReferenceEquals(candidateCorLibrary, GetCorLibrary(candidateCorLibrary)));
// Candidate has COR library, we should have one too.
if (corLibraryIndex < 0)
{
match = false;
break; // Stop processing references.
}
// Make sure candidate COR library represent the same assembly/binary
if (!assemblies[corLibraryIndex].IsMatchingAssembly(candidateCorLibrary))
{
// Mismatch between versions?
match = false;
break; // Stop processing references.
}
Debug.Assert(!assemblies[corLibraryIndex].ContainsNoPiaLocalTypes);
Debug.Assert(!assemblies[corLibraryIndex].IsLinked);
Debug.Assert(!IsLinked(candidateCorLibrary));
// Add the candidate COR library to the queue so that we consider it as a candidate.
candidatesToExamine.Enqueue(new AssemblyReferenceCandidate(corLibraryIndex, candidateCorLibrary));
}
}
}
if (match)
{
// Merge the set of symbols into result
for (int k = 0; k < totalAssemblies; k++)
{
if (candidateInputAssemblySymbols[k] != null)
{
Debug.Assert(boundInputs[k].AssemblySymbol == null);
boundInputs[k].AssemblySymbol = candidateInputAssemblySymbols[k];
}
}
// No reason to examine other symbols for this assembly
break; // Stop processing assemblies[i].AvailableSymbols
}
}
}
}
finally
{
candidatesToExamine.Clear();
candidateReferencedSymbols.Clear();
s_candidatesToExaminePool.Free(candidatesToExamine);
s_candidateReferencedSymbolsPool.Free(candidateReferencedSymbols);
}
}
private static bool CheckCircularReference(IReadOnlyList<AssemblyReferenceBinding[]> referenceBindings)
{
for (int i = 1; i < referenceBindings.Count; i++)
{
foreach (AssemblyReferenceBinding index in referenceBindings[i])
{
if (index.BoundToAssemblyBeingBuilt)
{
return true;
}
}
}
return false;
}
private static bool IsSuperseded(AssemblyIdentity identity, IReadOnlyDictionary<string, List<ReferencedAssemblyIdentity>> assemblyReferencesBySimpleName)
{
var value = assemblyReferencesBySimpleName[identity.Name][0];
Debug.Assert(value.Identity is object);
return value.Identity.Version != identity.Version;
}
private static int IndexOfCorLibrary(ImmutableArray<AssemblyData> assemblies, IReadOnlyDictionary<string, List<ReferencedAssemblyIdentity>> assemblyReferencesBySimpleName, bool supersedeLowerVersions)
{
// Figure out COR library for this compilation.
ArrayBuilder<int>? corLibraryCandidates = null;
for (int i = 1; i < assemblies.Length; i++)
{
var assembly = assemblies[i];
// The logic about deciding what assembly is a candidate for being a Cor library here and in
// Microsoft.CodeAnalysis.VisualBasic.CommandLineCompiler.ResolveMetadataReferencesFromArguments
// should be equivalent.
// Linked references cannot be used as COR library.
// References containing NoPia local types also cannot be used as COR library.
if (!assembly.IsLinked &&
assembly.AssemblyReferences.Length == 0 &&
!assembly.ContainsNoPiaLocalTypes &&
(!supersedeLowerVersions || !IsSuperseded(assembly.Identity, assemblyReferencesBySimpleName)))
{
// We have referenced assembly that doesn't have assembly references,
// check if it declares baseless System.Object.
if (assembly.DeclaresTheObjectClass)
{
if (corLibraryCandidates == null)
{
corLibraryCandidates = ArrayBuilder<int>.GetInstance();
}
// This could be the COR library.
corLibraryCandidates.Add(i);
}
}
}
// If there is an ambiguous match, pretend there is no COR library.
// TODO: figure out if we need to be able to resolve this ambiguity.
if (corLibraryCandidates != null)
{
if (corLibraryCandidates.Count == 1)
{
// TODO: need to make sure we error if such assembly declares local type in source.
int result = corLibraryCandidates[0];
corLibraryCandidates.Free();
return result;
}
else
{
// TODO: C# seems to pick the first one (but produces warnings when looking up predefined types).
// See PredefinedTypes::Init(ErrorHandling*).
corLibraryCandidates.Free();
}
}
// If we have assembly being built and no references,
// assume the assembly we are building is the COR library.
if (assemblies.Length == 1 && assemblies[0].AssemblyReferences.Length == 0)
{
return 0;
}
return -1;
}
/// <summary>
/// Determines if it is possible that <paramref name="assembly"/> gives internals
/// access to assembly <paramref name="compilationName"/>. It does not make a conclusive
/// determination of visibility because the compilation's strong name key is not supplied.
/// </summary>
internal static bool InternalsMayBeVisibleToAssemblyBeingCompiled(string compilationName, PEAssembly assembly)
{
return !assembly.GetInternalsVisibleToPublicKeys(compilationName).IsEmpty();
}
// https://github.com/dotnet/roslyn/issues/40751 It should not be necessary to annotate this method to annotate overrides
/// <summary>
/// Compute AssemblySymbols referenced by the input AssemblySymbol and fill in <paramref name="referencedAssemblySymbols"/> with the result.
/// The AssemblySymbols must correspond
/// to the AssemblyNames returned by AssemblyData.AssemblyReferences property. If reference is not
/// resolved, null reference should be returned in the corresponding item.
/// </summary>
/// <param name="assemblySymbol">The target AssemblySymbol instance.</param>
/// <param name="referencedAssemblySymbols">A list which will be filled in with
/// AssemblySymbols referenced by the input AssemblySymbol. The caller is expected to clear
/// the list before calling this method.
/// Implementer may not cache the list; the caller may mutate it.</param>
protected abstract void GetActualBoundReferencesUsedBy(TAssemblySymbol assemblySymbol, List<TAssemblySymbol?> referencedAssemblySymbols);
/// <summary>
/// Return collection of assemblies involved in canonical type resolution of
/// NoPia local types defined within target assembly. In other words, all
/// references used by previous compilation referencing the target assembly.
/// </summary>
protected abstract ImmutableArray<TAssemblySymbol> GetNoPiaResolutionAssemblies(TAssemblySymbol candidateAssembly);
/// <summary>
/// Assembly is /l-ed by compilation that is using it as a reference.
/// </summary>
protected abstract bool IsLinked(TAssemblySymbol candidateAssembly);
/// <summary>
/// Get Assembly used as COR library for the candidate.
/// </summary>
protected abstract TAssemblySymbol? GetCorLibrary(TAssemblySymbol candidateAssembly);
}
}
|