|
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.DiaSymReader;
using Roslyn.Utilities;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// The compilation object is an immutable representation of a single invocation of the
/// compiler. Although immutable, a compilation is also on-demand, and will realize and cache
/// data as necessary. A compilation can produce a new compilation from existing compilation
/// with the application of small deltas. In many cases, it is more efficient than creating a
/// new compilation from scratch, as the new compilation can reuse information from the old
/// compilation.
/// </summary>
public abstract partial class Compilation
{
/// <summary>
/// Optional data collected during testing only.
/// Used for instance for nullable analysis (NullableWalker.NullableAnalysisData)
/// and inferred delegate types (InferredDelegateTypeData).
/// </summary>
internal object? TestOnlyCompilationData;
/// <summary>
/// Returns true if this is a case sensitive compilation, false otherwise. Case sensitivity
/// affects compilation features such as name lookup as well as choosing what names to emit
/// when there are multiple different choices (for example between a virtual method and an
/// override).
/// </summary>
public abstract bool IsCaseSensitive { get; }
/// <summary>
/// Used for test purposes only to emulate missing members.
/// </summary>
private SmallDictionary<int, bool>? _lazyMakeWellKnownTypeMissingMap;
/// <summary>
/// Used for test purposes only to emulate missing members.
/// </summary>
private SmallDictionary<int, bool>? _lazyMakeMemberMissingMap;
// Protected for access in CSharpCompilation.WithAdditionalFeatures
protected readonly IReadOnlyDictionary<string, string> _features;
public ScriptCompilationInfo? ScriptCompilationInfo => CommonScriptCompilationInfo;
internal abstract ScriptCompilationInfo? CommonScriptCompilationInfo { get; }
internal Compilation(
string? name,
ImmutableArray<MetadataReference> references,
IReadOnlyDictionary<string, string> features,
bool isSubmission,
SemanticModelProvider? semanticModelProvider,
AsyncQueue<CompilationEvent>? eventQueue)
{
RoslynDebug.Assert(!references.IsDefault);
RoslynDebug.Assert(features != null);
this.AssemblyName = name;
this.ExternalReferences = references;
this.SemanticModelProvider = semanticModelProvider;
this.EventQueue = eventQueue;
_lazySubmissionSlotIndex = isSubmission ? SubmissionSlotIndexToBeAllocated : SubmissionSlotIndexNotApplicable;
_features = features;
}
protected static IReadOnlyDictionary<string, string> SyntaxTreeCommonFeatures(IEnumerable<SyntaxTree> trees)
{
IReadOnlyDictionary<string, string>? set = null;
foreach (var tree in trees)
{
var treeFeatures = tree.Options.Features;
if (set == null)
{
set = treeFeatures;
}
else
{
if ((object)set != treeFeatures && !set.SetEquals(treeFeatures))
{
throw new ArgumentException(CodeAnalysisResources.InconsistentSyntaxTreeFeature, nameof(trees));
}
}
}
if (set == null)
{
// Edge case where there are no syntax trees
set = ImmutableDictionary<string, string>.Empty;
}
return set;
}
internal abstract AnalyzerDriver CreateAnalyzerDriver(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerManager analyzerManager, SeverityFilter severityFilter);
/// <summary>
/// Gets the source language ("C#" or "Visual Basic").
/// </summary>
public abstract string Language { get; }
internal abstract void SerializePdbEmbeddedCompilationOptions(BlobBuilder builder);
/// <summary>
/// This method generates a string that represents the input content to the compiler which impacts
/// the output of the build. This string is effectively a content key for a <see cref="Compilation"/>
/// with these values that can be used to identify the outputs.
///
/// The returned string has the following properties:
///
/// <list type="bullet">
/// <item>
/// <description>
/// The format is undefined. Consumers should assume the format and content can change between
/// compiler versions.
/// </description>
/// </item>
/// <item>
/// <description>
/// It is designed to be human readable and diffable. This is to help developers
/// understand the difference between two compilations which is impacting the deterministic
/// output
/// </description>
/// </item>
/// <item>
/// <description>
/// It is *not* in a minimal form. If used as a key in say a content addressable storage consumers
/// should first pass it through a strong hashing function.
/// </description>
/// </item>
/// </list>
///
/// Compilations which do not use the /deterministic option can still use this API but
/// the results will change on every invocation.
/// </summary>
/// <remarks>
/// The set of inputs that impact deterministic output are described in the following document
/// - https://github.com/dotnet/roslyn/blob/main/docs/compilers/Deterministic%20Inputs.md
///
/// There are a few dark corners of determinism that are not captured with this key as they are
/// considered outside the scope of this work:
///
/// <list type="number">
/// <item>
/// <description>
/// Environment variables: clever developers can subvert determinism by manipulation of
/// environment variables that impact program execution. For example changing normal library
/// loading by manipulating the %LIBPATH% environment variable. Doing so can cause a change
/// in deterministic output of compilation by changing compiler, runtime or generator
/// dependencies.
/// </description>
/// </item>
/// <item>
/// <description>
/// Manipulation of strong name keys: strong name keys are read "on demand" by the compiler
/// and both normal compilation and this key can have non-deterministic output if they are
/// manipulated at the correct point in program execution. That is an existing limitation
/// of compilation that is tracked by https://github.com/dotnet/roslyn/issues/57940
/// </description>
/// </item>
/// </list>
/// This API can throw exceptions in a few cases like invalid file paths.
/// </remarks>
internal static string GetDeterministicKey(
CompilationOptions compilationOptions,
ImmutableArray<SyntaxTree> syntaxTrees,
ImmutableArray<MetadataReference> references,
ImmutableArray<byte> publicKey,
ImmutableArray<AdditionalText> additionalTexts = default,
ImmutableArray<DiagnosticAnalyzer> analyzers = default,
ImmutableArray<ISourceGenerator> generators = default,
ImmutableArray<KeyValuePair<string, string>> pathMap = default,
EmitOptions? emitOptions = null,
DeterministicKeyOptions options = DeterministicKeyOptions.Default)
{
return DeterministicKey.GetDeterministicKey(
compilationOptions, syntaxTrees, references, publicKey, additionalTexts, analyzers, generators, pathMap, emitOptions, options);
}
internal string GetDeterministicKey(
ImmutableArray<AdditionalText> additionalTexts = default,
ImmutableArray<DiagnosticAnalyzer> analyzers = default,
ImmutableArray<ISourceGenerator> generators = default,
ImmutableArray<KeyValuePair<string, string>> pathMap = default,
EmitOptions? emitOptions = null,
DeterministicKeyOptions options = DeterministicKeyOptions.Default)
=> GetDeterministicKey(
Options,
CommonSyntaxTrees,
ExternalReferences.Concat(DirectiveReferences),
Assembly.Identity.PublicKey,
additionalTexts,
analyzers,
generators,
pathMap,
emitOptions,
options);
internal static void ValidateScriptCompilationParameters(Compilation? previousScriptCompilation, Type? returnType, ref Type? globalsType)
{
if (globalsType != null && !IsValidHostObjectType(globalsType))
{
throw new ArgumentException(CodeAnalysisResources.ReturnTypeCannotBeValuePointerbyRefOrOpen, nameof(globalsType));
}
if (returnType != null && !IsValidSubmissionReturnType(returnType))
{
throw new ArgumentException(CodeAnalysisResources.ReturnTypeCannotBeVoidByRefOrOpen, nameof(returnType));
}
if (previousScriptCompilation != null)
{
if (globalsType == null)
{
globalsType = previousScriptCompilation.HostObjectType;
}
else if (globalsType != previousScriptCompilation.HostObjectType)
{
throw new ArgumentException(CodeAnalysisResources.TypeMustBeSameAsHostObjectTypeOfPreviousSubmission, nameof(globalsType));
}
// Force the previous submission to be analyzed. This is required for anonymous types unification.
if (previousScriptCompilation.GetDiagnostics().Any(static d => d.Severity == DiagnosticSeverity.Error))
{
throw new InvalidOperationException(CodeAnalysisResources.PreviousSubmissionHasErrors);
}
}
}
/// <summary>
/// Checks options passed to submission compilation constructor.
/// Throws an exception if the options are not applicable to submissions.
/// </summary>
internal static void CheckSubmissionOptions(CompilationOptions? options)
{
if (options == null)
{
return;
}
if (options.OutputKind.IsValid() && options.OutputKind != OutputKind.DynamicallyLinkedLibrary)
{
throw new ArgumentException(CodeAnalysisResources.InvalidOutputKindForSubmission, nameof(options));
}
if (options.CryptoKeyContainer != null ||
options.CryptoKeyFile != null ||
options.DelaySign != null ||
!options.CryptoPublicKey.IsEmpty ||
(options.DelaySign == true && options.PublicSign))
{
throw new ArgumentException(CodeAnalysisResources.InvalidCompilationOptions, nameof(options));
}
}
/// <summary>
/// Creates a new compilation equivalent to this one with different symbol instances.
/// </summary>
public Compilation Clone()
{
return CommonClone();
}
protected abstract Compilation CommonClone();
/// <summary>
/// Returns a new compilation with a given event queue.
/// </summary>
internal abstract Compilation WithEventQueue(AsyncQueue<CompilationEvent>? eventQueue);
/// <summary>
/// Returns a new compilation with a given semantic model provider.
/// </summary>
internal abstract Compilation WithSemanticModelProvider(SemanticModelProvider semanticModelProvider);
/// <summary>
/// Gets a new <see cref="SemanticModel"/> for the specified syntax tree.
/// </summary>
/// <param name="syntaxTree">The specified syntax tree.</param>
/// <param name="ignoreAccessibility">
/// True if the SemanticModel should ignore accessibility rules when answering semantic questions.
/// </param>
#pragma warning disable RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
public SemanticModel GetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility = false)
#pragma warning restore RS0027
#pragma warning disable RSEXPERIMENTAL001 // internal usage of experimental API
=> GetSemanticModel(syntaxTree, ignoreAccessibility ? SemanticModelOptions.IgnoreAccessibility : SemanticModelOptions.None);
#pragma warning restore RSEXPERIMENTAL001
[Experimental(RoslynExperiments.NullableDisabledSemanticModel, UrlFormat = RoslynExperiments.NullableDisabledSemanticModel_Url)]
public SemanticModel GetSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options)
=> CommonGetSemanticModel(syntaxTree, options);
/// <summary>
/// Gets a <see cref="SemanticModel"/> for the given <paramref name="syntaxTree"/>.
/// If <see cref="SemanticModelProvider"/> is non-null, it attempts to use <see cref="SemanticModelProvider.GetSemanticModel(SyntaxTree, Compilation, SemanticModelOptions)"/>
/// to get a semantic model. Otherwise, it creates a new semantic model using <see cref="CreateSemanticModel(SyntaxTree, SemanticModelOptions)"/>.
/// </summary>
[Experimental(RoslynExperiments.NullableDisabledSemanticModel, UrlFormat = RoslynExperiments.NullableDisabledSemanticModel_Url)]
protected abstract SemanticModel CommonGetSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options);
/// <summary>
/// Creates a new <see cref="SemanticModel"/> for the given <paramref name="syntaxTree"/>.
/// Unlike the <see cref="GetSemanticModel(SyntaxTree, bool)"/> and <see cref="CommonGetSemanticModel(SyntaxTree, SemanticModelOptions)"/>,
/// it does not attempt to use the <see cref="SemanticModelProvider"/> to get a semantic model, but instead always creates a new semantic model.
/// </summary>
#pragma warning disable RSEXPERIMENTAL001 // internal usage of experimental API
internal abstract SemanticModel CreateSemanticModel(SyntaxTree syntaxTree, SemanticModelOptions options);
#pragma warning restore RSEXPERIMENTAL001 // internal usage of experimental API
/// <summary>
/// Returns a new INamedTypeSymbol representing an error type with the given name and arity
/// in the given optional container.
/// </summary>
public INamedTypeSymbol CreateErrorTypeSymbol(INamespaceOrTypeSymbol? container, string name, int arity)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (arity < 0)
{
throw new ArgumentException($"{nameof(arity)} must be >= 0", nameof(arity));
}
return CommonCreateErrorTypeSymbol(container, name, arity);
}
protected abstract INamedTypeSymbol CommonCreateErrorTypeSymbol(INamespaceOrTypeSymbol? container, string name, int arity);
/// <summary>
/// Returns a new INamespaceSymbol representing an error (missing) namespace with the given name.
/// </summary>
public INamespaceSymbol CreateErrorNamespaceSymbol(INamespaceSymbol container, string name)
{
if (container == null)
{
throw new ArgumentNullException(nameof(container));
}
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
return CommonCreateErrorNamespaceSymbol(container, name);
}
protected abstract INamespaceSymbol CommonCreateErrorNamespaceSymbol(INamespaceSymbol container, string name);
/// <summary>
/// Returns a new IPreprocessingSymbol representing a preprocessing symbol with the given name.
/// </summary>
public IPreprocessingSymbol CreatePreprocessingSymbol(string name)
=> CommonCreatePreprocessingSymbol(name ?? throw new ArgumentNullException(nameof(name)));
protected abstract IPreprocessingSymbol CommonCreatePreprocessingSymbol(string name);
#region Name
internal const string UnspecifiedModuleAssemblyName = "?";
/// <summary>
/// Simple assembly name, or null if not specified.
/// </summary>
/// <remarks>
/// The name is used for determining internals-visible-to relationship with referenced assemblies.
///
/// If the compilation represents an assembly the value of <see cref="AssemblyName"/> is its simple name.
///
/// Unless <see cref="CompilationOptions.ModuleName"/> specifies otherwise the module name
/// written to metadata is <see cref="AssemblyName"/> with an extension based upon <see cref="CompilationOptions.OutputKind"/>.
/// </remarks>
public string? AssemblyName { get; }
internal void CheckAssemblyName(DiagnosticBag diagnostics)
{
// We could only allow name == null if OutputKind is Module.
// However, it does no harm that we allow name == null for assemblies as well, so we don't enforce it.
if (this.AssemblyName != null)
{
MetadataHelpers.CheckAssemblyOrModuleName(this.AssemblyName, MessageProvider, MessageProvider.ERR_BadAssemblyName, diagnostics);
}
}
internal string MakeSourceAssemblySimpleName()
{
return AssemblyName ?? UnspecifiedModuleAssemblyName;
}
internal string MakeSourceModuleName()
{
return Options.ModuleName ??
(AssemblyName != null ? AssemblyName + Options.OutputKind.GetDefaultExtension() : UnspecifiedModuleAssemblyName);
}
/// <summary>
/// Creates a compilation with the specified assembly name.
/// </summary>
/// <param name="assemblyName">The new assembly name.</param>
/// <returns>A new compilation.</returns>
public Compilation WithAssemblyName(string? assemblyName)
{
return CommonWithAssemblyName(assemblyName);
}
protected abstract Compilation CommonWithAssemblyName(string? outputName);
#endregion
#region Options
/// <summary>
/// Gets the options the compilation was created with.
/// </summary>
public CompilationOptions Options { get { return CommonOptions; } }
protected abstract CompilationOptions CommonOptions { get; }
/// <summary>
/// Creates a new compilation with the specified compilation options.
/// </summary>
/// <param name="options">The new options.</param>
/// <returns>A new compilation.</returns>
public Compilation WithOptions(CompilationOptions options)
{
return CommonWithOptions(options);
}
protected abstract Compilation CommonWithOptions(CompilationOptions options);
#endregion
#region Submissions
// An index in the submission slot array. Allocated lazily in compilation phase based upon the slot index of the previous submission.
// Special values:
// -1 ... neither this nor previous submissions in the chain allocated a slot (the submissions don't contain code)
// -2 ... the slot of this submission hasn't been determined yet
// -3 ... this is not a submission compilation
private int _lazySubmissionSlotIndex;
private const int SubmissionSlotIndexNotApplicable = -3;
private const int SubmissionSlotIndexToBeAllocated = -2;
/// <summary>
/// True if the compilation represents an interactive submission.
/// </summary>
internal bool IsSubmission
{
get
{
return _lazySubmissionSlotIndex != SubmissionSlotIndexNotApplicable;
}
}
/// <summary>
/// The previous submission, if any, or null.
/// </summary>
private Compilation? PreviousSubmission
{
get
{
return ScriptCompilationInfo?.PreviousScriptCompilation;
}
}
/// <summary>
/// Gets or allocates a runtime submission slot index for this compilation.
/// </summary>
/// <returns>Non-negative integer if this is a submission and it or a previous submission contains code, negative integer otherwise.</returns>
internal int GetSubmissionSlotIndex()
{
if (_lazySubmissionSlotIndex == SubmissionSlotIndexToBeAllocated)
{
// TODO (tomat): remove recursion
int lastSlotIndex = ScriptCompilationInfo!.PreviousScriptCompilation?.GetSubmissionSlotIndex() ?? 0;
_lazySubmissionSlotIndex = HasCodeToEmit() ? lastSlotIndex + 1 : lastSlotIndex;
}
return _lazySubmissionSlotIndex;
}
// The type of interactive submission result requested by the host, or null if this compilation doesn't represent a submission.
//
// The type is resolved to a symbol when the Script's instance ctor symbol is constructed. The symbol needs to be resolved against
// the references of this compilation.
//
// Consider (tomat): As an alternative to Reflection Type we could hold onto any piece of information that lets us
// resolve the type symbol when needed.
/// <summary>
/// The type object that represents the type of submission result the host requested.
/// </summary>
internal Type? SubmissionReturnType => ScriptCompilationInfo?.ReturnTypeOpt;
internal static bool IsValidSubmissionReturnType(Type type)
{
return !(type == typeof(void) || type.IsByRef || type.GetTypeInfo().ContainsGenericParameters);
}
/// <summary>
/// The type of the globals object or null if not specified for this compilation.
/// </summary>
internal Type? HostObjectType => ScriptCompilationInfo?.GlobalsType;
internal static bool IsValidHostObjectType(Type type)
{
var info = type.GetTypeInfo();
return !(info.IsValueType || info.IsPointer || info.IsByRef || info.ContainsGenericParameters);
}
internal abstract bool HasSubmissionResult();
public Compilation WithScriptCompilationInfo(ScriptCompilationInfo? info) => CommonWithScriptCompilationInfo(info);
protected abstract Compilation CommonWithScriptCompilationInfo(ScriptCompilationInfo? info);
#endregion
#region Syntax Trees
/// <summary>
/// Gets the syntax trees (parsed from source code) that this compilation was created with.
/// </summary>
public IEnumerable<SyntaxTree> SyntaxTrees { get { return CommonSyntaxTrees; } }
protected internal abstract ImmutableArray<SyntaxTree> CommonSyntaxTrees { get; }
/// <summary>
/// Creates a new compilation with additional syntax trees.
/// </summary>
/// <param name="trees">The new syntax trees.</param>
/// <returns>A new compilation.</returns>
public Compilation AddSyntaxTrees(params SyntaxTree[] trees)
{
return CommonAddSyntaxTrees(trees);
}
/// <summary>
/// Creates a new compilation with additional syntax trees.
/// </summary>
/// <param name="trees">The new syntax trees.</param>
/// <returns>A new compilation.</returns>
public Compilation AddSyntaxTrees(IEnumerable<SyntaxTree> trees)
{
return CommonAddSyntaxTrees(trees);
}
protected abstract Compilation CommonAddSyntaxTrees(IEnumerable<SyntaxTree> trees);
/// <summary>
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
/// </summary>
/// <param name="trees">The new syntax trees.</param>
/// <returns>A new compilation.</returns>
public Compilation RemoveSyntaxTrees(params SyntaxTree[] trees)
{
return CommonRemoveSyntaxTrees(trees);
}
/// <summary>
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
/// </summary>
/// <param name="trees">The new syntax trees.</param>
/// <returns>A new compilation.</returns>
public Compilation RemoveSyntaxTrees(IEnumerable<SyntaxTree> trees)
{
return CommonRemoveSyntaxTrees(trees);
}
protected abstract Compilation CommonRemoveSyntaxTrees(IEnumerable<SyntaxTree> trees);
/// <summary>
/// Creates a new compilation without any syntax trees. Preserves metadata info for use with
/// trees added later.
/// </summary>
public Compilation RemoveAllSyntaxTrees()
{
return CommonRemoveAllSyntaxTrees();
}
protected abstract Compilation CommonRemoveAllSyntaxTrees();
/// <summary>
/// Creates a new compilation with an old syntax tree replaced with a new syntax tree.
/// Reuses metadata from old compilation object.
/// </summary>
/// <param name="newTree">The new tree.</param>
/// <param name="oldTree">The old tree.</param>
/// <returns>A new compilation.</returns>
public Compilation ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree)
{
return CommonReplaceSyntaxTree(oldTree, newTree);
}
protected abstract Compilation CommonReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree);
/// <summary>
/// Returns true if this compilation contains the specified tree. False otherwise.
/// </summary>
/// <param name="syntaxTree">A syntax tree.</param>
public bool ContainsSyntaxTree(SyntaxTree syntaxTree)
{
return CommonContainsSyntaxTree(syntaxTree);
}
protected abstract bool CommonContainsSyntaxTree(SyntaxTree? syntaxTree);
/// <summary>
/// Optional semantic model provider for this compilation.
/// </summary>
internal SemanticModelProvider? SemanticModelProvider { get; }
/// <summary>
/// The event queue that this compilation was created with.
/// </summary>
internal AsyncQueue<CompilationEvent>? EventQueue { get; }
/// <summary>
/// If this value is not 0, we might be about to enqueue more events into <see cref="EventQueue"/>.
/// In this case, we need to wait for the count to go to zero before completing the queue.
///
/// This is necessary in cases where multi-step operations that impact the queue occur. For
/// example when a thread of execution is storing cached data on a symbol before pushing
/// an event to the queue. If another thread were to come in between those two steps, see the
/// cached data it could mistakenly believe the operation was complete and cause the queue
/// to close. This counter ensures that the queue will remain open for the duration of a
/// complex operation.
/// </summary>
private int _eventQueueEnqueuePendingCount;
#endregion
#region References
internal static ImmutableArray<MetadataReference> ValidateReferences<T>(IEnumerable<MetadataReference>? references)
where T : CompilationReference
{
var result = references.AsImmutableOrEmpty();
for (int i = 0; i < result.Length; i++)
{
var reference = result[i];
if (reference == null)
{
throw new ArgumentNullException($"{nameof(references)}[{i}]");
}
var peReference = reference as PortableExecutableReference;
if (peReference == null && !(reference is T))
{
Debug.Assert(reference is UnresolvedMetadataReference || reference is CompilationReference);
throw new ArgumentException(string.Format(CodeAnalysisResources.ReferenceOfTypeIsInvalid1, reference.GetType()),
$"{nameof(references)}[{i}]");
}
}
return result;
}
internal CommonReferenceManager GetBoundReferenceManager()
{
return CommonGetBoundReferenceManager();
}
internal abstract CommonReferenceManager CommonGetBoundReferenceManager();
/// <summary>
/// Metadata references passed to the compilation constructor.
/// </summary>
public ImmutableArray<MetadataReference> ExternalReferences { get; }
/// <summary>
/// Unique metadata references specified via #r directive in the source code of this compilation.
/// </summary>
public abstract ImmutableArray<MetadataReference> DirectiveReferences { get; }
/// <summary>
/// All reference directives used in this compilation.
/// </summary>
internal abstract IEnumerable<ReferenceDirective> ReferenceDirectives { get; }
/// <summary>
/// Maps values of #r references to resolved metadata references.
/// </summary>
internal abstract IDictionary<(string path, string content), MetadataReference> ReferenceDirectiveMap { get; }
/// <summary>
/// All metadata references -- references passed to the compilation
/// constructor as well as references specified via #r directives.
/// </summary>
public IEnumerable<MetadataReference> References
{
get
{
foreach (var reference in ExternalReferences)
{
yield return reference;
}
foreach (var reference in DirectiveReferences)
{
yield return reference;
}
}
}
/// <summary>
/// Creates a metadata reference for this compilation.
/// </summary>
/// <param name="aliases">
/// Optional aliases that can be used to refer to the compilation root namespace via extern alias directive.
/// </param>
/// <param name="embedInteropTypes">
/// Embed the COM types from the reference so that the compiled
/// application no longer requires a primary interop assembly (PIA).
/// </param>
public abstract CompilationReference ToMetadataReference(ImmutableArray<string> aliases = default(ImmutableArray<string>), bool embedInteropTypes = false);
/// <summary>
/// Creates a new compilation with the specified references.
/// </summary>
/// <param name="newReferences">
/// The new references.
/// </param>
/// <returns>A new compilation.</returns>
public Compilation WithReferences(IEnumerable<MetadataReference> newReferences)
{
return this.CommonWithReferences(newReferences);
}
/// <summary>
/// Creates a new compilation with the specified references.
/// </summary>
/// <param name="newReferences">The new references.</param>
/// <returns>A new compilation.</returns>
public Compilation WithReferences(params MetadataReference[] newReferences)
{
return this.WithReferences((IEnumerable<MetadataReference>)newReferences);
}
/// <summary>
/// Creates a new compilation with the specified references.
/// </summary>
protected abstract Compilation CommonWithReferences(IEnumerable<MetadataReference> newReferences);
/// <summary>
/// Creates a new compilation with additional metadata references.
/// </summary>
/// <param name="references">The new references.</param>
/// <returns>A new compilation.</returns>
public Compilation AddReferences(params MetadataReference[] references)
{
return AddReferences((IEnumerable<MetadataReference>)references);
}
/// <summary>
/// Creates a new compilation with additional metadata references.
/// </summary>
/// <param name="references">The new references.</param>
/// <returns>A new compilation.</returns>
public Compilation AddReferences(IEnumerable<MetadataReference> references)
{
if (references == null)
{
throw new ArgumentNullException(nameof(references));
}
if (references.IsEmpty())
{
return this;
}
return CommonWithReferences(this.ExternalReferences.Union(references));
}
/// <summary>
/// Creates a new compilation without the specified metadata references.
/// </summary>
/// <param name="references">The new references.</param>
/// <returns>A new compilation.</returns>
public Compilation RemoveReferences(params MetadataReference[] references)
{
return RemoveReferences((IEnumerable<MetadataReference>)references);
}
/// <summary>
/// Creates a new compilation without the specified metadata references.
/// </summary>
/// <param name="references">The new references.</param>
/// <returns>A new compilation.</returns>
public Compilation RemoveReferences(IEnumerable<MetadataReference> references)
{
if (references == null)
{
throw new ArgumentNullException(nameof(references));
}
if (references.IsEmpty())
{
return this;
}
var refSet = new HashSet<MetadataReference>(this.ExternalReferences);
//EDMAURER if AddingReferences accepts duplicates, then a consumer supplying a list with
//duplicates to add will not know exactly which to remove. Let them supply a list with
//duplicates here.
foreach (var r in references.Distinct())
{
if (!refSet.Remove(r))
{
throw new ArgumentException(string.Format(CodeAnalysisResources.MetadataRefNotFoundToRemove1, r),
nameof(references));
}
}
return CommonWithReferences(refSet);
}
/// <summary>
/// Creates a new compilation without any metadata references.
/// </summary>
public Compilation RemoveAllReferences()
{
return CommonWithReferences(SpecializedCollections.EmptyEnumerable<MetadataReference>());
}
/// <summary>
/// Creates a new compilation with an old metadata reference replaced with a new metadata
/// reference.
/// </summary>
/// <param name="newReference">The new reference.</param>
/// <param name="oldReference">The old reference.</param>
/// <returns>A new compilation.</returns>
public Compilation ReplaceReference(MetadataReference oldReference, MetadataReference? newReference)
{
if (oldReference == null)
{
throw new ArgumentNullException(nameof(oldReference));
}
if (newReference == null)
{
return this.RemoveReferences(oldReference);
}
return this.RemoveReferences(oldReference).AddReferences(newReference);
}
/// <summary>
/// Gets the <see cref="IAssemblySymbol"/> or <see cref="IModuleSymbol"/> for a metadata reference used to create this
/// compilation.
/// </summary>
/// <param name="reference">The target reference.</param>
/// <returns>
/// Assembly or module symbol corresponding to the given reference or null if there is none.
/// </returns>
public ISymbol? GetAssemblyOrModuleSymbol(MetadataReference reference)
{
return CommonGetAssemblyOrModuleSymbol(reference);
}
protected abstract ISymbol? CommonGetAssemblyOrModuleSymbol(MetadataReference reference);
[return: NotNullIfNotNull(nameof(symbol))]
internal abstract TSymbol? GetSymbolInternal<TSymbol>(ISymbol? symbol) where TSymbol : class, ISymbolInternal;
/// <summary>
/// Gets the <see cref="MetadataReference"/> that corresponds to the assembly symbol.
/// </summary>
/// <param name="assemblySymbol">The target symbol.</param>
public MetadataReference? GetMetadataReference(IAssemblySymbol assemblySymbol)
{
return CommonGetMetadataReference(assemblySymbol);
}
private protected abstract MetadataReference? CommonGetMetadataReference(IAssemblySymbol assemblySymbol);
/// <summary>
/// Assembly identities of all assemblies directly referenced by this compilation.
/// </summary>
/// <remarks>
/// Includes identities of references passed in the compilation constructor
/// as well as those specified via directives in source code.
/// </remarks>
public abstract IEnumerable<AssemblyIdentity> ReferencedAssemblyNames { get; }
#endregion
#region Symbols
/// <summary>
/// The <see cref="IAssemblySymbol"/> that represents the assembly being created.
/// </summary>
public IAssemblySymbol Assembly { get { return CommonAssembly; } }
protected abstract IAssemblySymbol CommonAssembly { get; }
/// <summary>
/// Gets the <see cref="IModuleSymbol"/> for the module being created by compiling all of
/// the source code.
/// </summary>
public IModuleSymbol SourceModule { get { return CommonSourceModule; } }
protected abstract IModuleSymbol CommonSourceModule { get; }
/// <summary>
/// The root namespace that contains all namespaces and types defined in source code or in
/// referenced metadata, merged into a single namespace hierarchy.
/// </summary>
public INamespaceSymbol GlobalNamespace { get { return CommonGlobalNamespace; } }
protected abstract INamespaceSymbol CommonGlobalNamespace { get; }
/// <summary>
/// Gets the corresponding compilation namespace for the specified module or assembly namespace.
/// </summary>
public INamespaceSymbol? GetCompilationNamespace(INamespaceSymbol namespaceSymbol)
{
return CommonGetCompilationNamespace(namespaceSymbol);
}
protected abstract INamespaceSymbol? CommonGetCompilationNamespace(INamespaceSymbol namespaceSymbol);
internal abstract CommonAnonymousTypeManager CommonAnonymousTypeManager { get; }
/// <summary>
/// Returns the Main method that will serves as the entry point of the assembly, if it is
/// executable (and not a script).
/// </summary>
public IMethodSymbol? GetEntryPoint(CancellationToken cancellationToken)
{
return CommonGetEntryPoint(cancellationToken);
}
protected abstract IMethodSymbol? CommonGetEntryPoint(CancellationToken cancellationToken);
/// <summary>
/// Get the symbol for the predefined type from the Cor Library referenced by this
/// compilation.
/// </summary>
public INamedTypeSymbol GetSpecialType(SpecialType specialType)
{
if (specialType <= SpecialType.None || specialType > SpecialType.Count)
{
throw new ArgumentOutOfRangeException(nameof(specialType), $"Unexpected SpecialType: '{(int)specialType}'.");
}
return (INamedTypeSymbol)CommonGetSpecialType(specialType).GetITypeSymbol();
}
/// <summary>
/// Get the symbol for the predefined type member from the COR Library referenced by this compilation.
/// </summary>
internal abstract ISymbolInternal CommonGetSpecialTypeMember(SpecialMember specialMember);
/// <summary>
/// Returns true if the type is System.Type.
/// </summary>
internal abstract bool IsSystemTypeReference(ITypeSymbolInternal type);
private protected abstract INamedTypeSymbolInternal CommonGetSpecialType(SpecialType specialType);
/// <summary>
/// Lookup member declaration in well known type used by this Compilation.
/// </summary>
internal abstract ISymbolInternal? CommonGetWellKnownTypeMember(WellKnownMember member);
/// <summary>
/// Lookup well-known type used by this Compilation.
/// </summary>
internal abstract ITypeSymbolInternal CommonGetWellKnownType(WellKnownType wellknownType);
/// <summary>
/// Returns true if the specified type is equal to or derives from System.Attribute well-known type.
/// </summary>
internal abstract bool IsAttributeType(ITypeSymbol type);
/// <summary>
/// The INamedTypeSymbol for the .NET System.Object type, which could have a TypeKind of
/// Error if there was no COR Library in this Compilation.
/// </summary>
public INamedTypeSymbol ObjectType { get { return CommonObjectType; } }
protected abstract INamedTypeSymbol CommonObjectType { get; }
/// <summary>
/// The TypeSymbol for the type 'dynamic' in this Compilation.
/// </summary>
/// <exception cref="NotSupportedException">If the compilation is a VisualBasic compilation.</exception>
public ITypeSymbol DynamicType { get { return CommonDynamicType; } }
protected abstract ITypeSymbol CommonDynamicType { get; }
/// <summary>
/// A symbol representing the script globals type.
/// </summary>
internal ITypeSymbol? ScriptGlobalsType => CommonScriptGlobalsType;
protected abstract ITypeSymbol? CommonScriptGlobalsType { get; }
/// <summary>
/// A symbol representing the implicit Script class. This is null if the class is not
/// defined in the compilation.
/// </summary>
public INamedTypeSymbol? ScriptClass { get { return CommonScriptClass; } }
protected abstract INamedTypeSymbol? CommonScriptClass { get; }
/// <summary>
/// Resolves a symbol that represents script container (Script class). Uses the
/// full name of the container class stored in <see cref="CompilationOptions.ScriptClassName"/> to find the symbol.
/// </summary>
/// <returns>The Script class symbol or null if it is not defined.</returns>
protected INamedTypeSymbol? CommonBindScriptClass()
{
string scriptClassName = this.Options.ScriptClassName ?? "";
string[] parts = scriptClassName.Split('.');
INamespaceSymbol container = this.SourceModule.GlobalNamespace;
for (int i = 0; i < parts.Length - 1; i++)
{
INamespaceSymbol? next = container.GetNestedNamespace(parts[i]);
if (next == null)
{
AssertNoScriptTrees();
return null;
}
container = next;
}
foreach (INamedTypeSymbol candidate in container.GetTypeMembers(parts[parts.Length - 1]))
{
if (candidate.IsScriptClass)
{
return candidate;
}
}
AssertNoScriptTrees();
return null;
}
[Conditional("DEBUG")]
private void AssertNoScriptTrees()
{
foreach (var tree in this.CommonSyntaxTrees)
{
Debug.Assert(tree.Options.Kind != SourceCodeKind.Script);
}
}
/// <summary>
/// Returns a new ArrayTypeSymbol representing an array type tied to the base types of the
/// COR Library in this Compilation.
/// </summary>
public IArrayTypeSymbol CreateArrayTypeSymbol(ITypeSymbol elementType, int rank = 1, NullableAnnotation elementNullableAnnotation = NullableAnnotation.None)
{
return CommonCreateArrayTypeSymbol(elementType, rank, elementNullableAnnotation);
}
/// <summary>
/// Returns a new ArrayTypeSymbol representing an array type tied to the base types of the
/// COR Library in this Compilation.
/// </summary>
/// <remarks>This overload is for backwards compatibility. Do not remove.</remarks>
public IArrayTypeSymbol CreateArrayTypeSymbol(ITypeSymbol elementType, int rank)
{
return CreateArrayTypeSymbol(elementType, rank, elementNullableAnnotation: default);
}
protected abstract IArrayTypeSymbol CommonCreateArrayTypeSymbol(ITypeSymbol elementType, int rank, NullableAnnotation elementNullableAnnotation);
/// <summary>
/// Returns a new IPointerTypeSymbol representing a pointer type tied to a type in this
/// Compilation.
/// </summary>
/// <exception cref="NotSupportedException">If the compilation is a VisualBasic compilation.</exception>
public IPointerTypeSymbol CreatePointerTypeSymbol(ITypeSymbol pointedAtType)
{
return CommonCreatePointerTypeSymbol(pointedAtType);
}
protected abstract IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol elementType);
/// <summary>
/// Returns a new IFunctionPointerTypeSymbol representing a function pointer type tied to types in this
/// Compilation.
/// </summary>
/// <exception cref="NotSupportedException">If the compilation is a VisualBasic compilation.</exception>
/// <exception cref="ArgumentException">
/// If:
/// * <see cref="RefKind.Out"/> is passed as the returnRefKind.
/// * parameterTypes and parameterRefKinds do not have the same length.
/// </exception>
/// <exception cref="ArgumentNullException">
/// If returnType is <see langword="null"/>, or if parameterTypes or parameterRefKinds are default,
/// or if any of the types in parameterTypes are null.</exception>
public IFunctionPointerTypeSymbol CreateFunctionPointerTypeSymbol(
ITypeSymbol returnType,
RefKind returnRefKind,
ImmutableArray<ITypeSymbol> parameterTypes,
ImmutableArray<RefKind> parameterRefKinds,
SignatureCallingConvention callingConvention = SignatureCallingConvention.Default,
ImmutableArray<INamedTypeSymbol> callingConventionTypes = default)
{
return CommonCreateFunctionPointerTypeSymbol(returnType, returnRefKind, parameterTypes, parameterRefKinds, callingConvention, callingConventionTypes);
}
protected abstract IFunctionPointerTypeSymbol CommonCreateFunctionPointerTypeSymbol(
ITypeSymbol returnType,
RefKind returnRefKind,
ImmutableArray<ITypeSymbol> parameterTypes,
ImmutableArray<RefKind> parameterRefKinds,
SignatureCallingConvention callingConvention,
ImmutableArray<INamedTypeSymbol> callingConventionTypes);
/// <summary>
/// Returns a new INamedTypeSymbol representing a native integer.
/// </summary>
/// <exception cref="NotSupportedException">If the compilation is a VisualBasic compilation.</exception>
public INamedTypeSymbol CreateNativeIntegerTypeSymbol(bool signed)
{
return CommonCreateNativeIntegerTypeSymbol(signed);
}
protected abstract INamedTypeSymbol CommonCreateNativeIntegerTypeSymbol(bool signed);
// PERF: ETW Traces show that analyzers may use this method frequently, often requesting
// the same symbol over and over again. XUnit analyzers, in particular, were consuming almost
// 1% of CPU time when building Roslyn itself. This is an extremely simple cache that evicts on
// hash code conflicts, but seems to do the trick. The size is mostly arbitrary. My guess
// is that there are maybe a couple dozen analyzers in the solution and each one has
// ~0-2 unique well-known types, and the chance of hash collision is very low.
private ConcurrentCache<string, INamedTypeSymbol?>? _getTypeCache;
private ConcurrentCache<string, ImmutableArray<INamedTypeSymbol>>? _getTypesCache;
/// <summary>
/// Gets the type within the compilation's assembly and all referenced assemblies (other than
/// those that can only be referenced via an extern alias) using its canonical CLR metadata name.
/// This lookup follows the following order:
/// <list type="number">
/// <item><description>If the type is found in the compilation's assembly, that type is returned.</description></item>
/// <item><description>
/// Next, the core library (the library that defines <c>System.Object</c> and has no assembly references) is searched.
/// If the type is found there, that type is returned.
/// </description></item>
/// <item><description>
/// Finally, all remaining referenced non-extern assemblies are searched. If one and only one type matching the provided metadata name is found, that
/// single type is returned. Accessibility is ignored for this check.
/// </description></item>
/// </list>
/// </summary>
/// <returns>Null if the type can't be found or there was an ambiguity during lookup.</returns>
/// <remarks>
/// <para>
/// Since VB does not have the concept of extern aliases, it considers all referenced assemblies.
/// </para>
/// <para>
/// In C#, if the core library is referenced as an extern assembly, it will be searched. All other extern-aliased assemblies will not be searched.
/// </para>
/// <para>
/// Because accessibility to the current assembly is ignored when searching for types that match the provided metadata name, if multiple referenced
/// assemblies define the same type symbol (as often happens when users copy well-known types from the BCL or other sources) then this API will return null,
/// even if all but one of those symbols would be otherwise inaccessible to user-written code in the current assembly. For fine-grained control over ambiguity
/// resolution, consider using <see cref="GetTypesByMetadataName(string)" /> instead and filtering the results for the symbol required.
/// </para>
/// <para>
/// Assemblies can contain multiple modules. Within each assembly, the search is performed based on module's position in the module list of that assembly. When
/// a match is found in one module in an assembly, no further modules within that assembly are searched.
/// </para>
/// <para>Type forwarders are ignored, and not considered part of the assembly where the TypeForwardAttribute is written.</para>
/// <para>
/// Ambiguities are detected on each nested level. For example, if <c>A+B</c> is requested, and there are multiple <c>A</c>s but only one of them has a <c>B</c> nested
/// type, the lookup will be considered ambiguous and null will be returned.
/// </para>
/// </remarks>
public INamedTypeSymbol? GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
var getTypeCache = RoslynLazyInitializer.EnsureInitialized(
ref _getTypeCache, static () => new ConcurrentCache<string, INamedTypeSymbol?>(50, ReferenceEqualityComparer.Instance));
if (!getTypeCache.TryGetValue(fullyQualifiedMetadataName, out INamedTypeSymbol? val))
{
val = CommonGetTypeByMetadataName(fullyQualifiedMetadataName);
var result = getTypeCache.TryAdd(fullyQualifiedMetadataName, val);
Debug.Assert(result
|| !getTypeCache.TryGetValue(fullyQualifiedMetadataName, out var addedType) // Could fail if the type was already evicted from the cache
|| ReferenceEquals(addedType, val));
}
return val;
}
protected abstract INamedTypeSymbol? CommonGetTypeByMetadataName(string metadataName);
/// <summary>
/// Gets all types with the compilation's assembly and all referenced assemblies that have the
/// given canonical CLR metadata name. Accessibility to the current assembly is ignored when
/// searching for matching type names.
/// </summary>
/// <returns>Empty array if no types match. Otherwise, all types that match the name, current assembly first if present.</returns>
/// <remarks>
/// <para>
/// Assemblies can contain multiple modules. Within each assembly, the search is performed based on module's position in the module list of that assembly. When
/// a match is found in one module in an assembly, no further modules within that assembly are searched.
/// </para>
/// <para>Type forwarders are ignored, and not considered part of the assembly where the TypeForwardAttribute is written.</para>
/// </remarks>
public ImmutableArray<INamedTypeSymbol> GetTypesByMetadataName(string fullyQualifiedMetadataName)
{
var getTypesCache = RoslynLazyInitializer.EnsureInitialized(
ref _getTypesCache, static () => new ConcurrentCache<string, ImmutableArray<INamedTypeSymbol>>(50, ReferenceEqualityComparer.Instance));
if (!getTypesCache.TryGetValue(fullyQualifiedMetadataName, out ImmutableArray<INamedTypeSymbol> val))
{
val = getTypesByMetadataNameImpl();
var result = getTypesCache.TryAdd(fullyQualifiedMetadataName, val);
Debug.Assert(result
|| !getTypesCache.TryGetValue(fullyQualifiedMetadataName, out var addedArray) // Could fail if the type was already evicted from the cache
|| Enumerable.SequenceEqual(addedArray, val, ReferenceEqualityComparer.Instance));
}
return val;
ImmutableArray<INamedTypeSymbol> getTypesByMetadataNameImpl()
{
ArrayBuilder<INamedTypeSymbol>? typesByMetadataName = null;
// Start with the current assembly, then corlib, then look through all references, to mimic GetTypeByMetadataName search order.
addIfNotNull(Assembly.GetTypeByMetadataName(fullyQualifiedMetadataName));
var corLib = ObjectType.ContainingAssembly;
if (!ReferenceEquals(corLib, Assembly))
{
addIfNotNull(corLib.GetTypeByMetadataName(fullyQualifiedMetadataName));
}
foreach (var referencedAssembly in SourceModule.ReferencedAssemblySymbols)
{
if (ReferenceEquals(referencedAssembly, corLib))
{
continue;
}
addIfNotNull(referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName));
}
return typesByMetadataName?.ToImmutableAndFree() ?? ImmutableArray<INamedTypeSymbol>.Empty;
void addIfNotNull(INamedTypeSymbol? toAdd)
{
if (toAdd != null)
{
typesByMetadataName ??= ArrayBuilder<INamedTypeSymbol>.GetInstance();
typesByMetadataName.Add(toAdd);
}
}
}
}
/// <summary>
/// Returns a new INamedTypeSymbol with the given element types and
/// (optional) element names, locations, and nullable annotations.
/// </summary>
public INamedTypeSymbol CreateTupleTypeSymbol(
ImmutableArray<ITypeSymbol> elementTypes,
ImmutableArray<string?> elementNames = default,
ImmutableArray<Location?> elementLocations = default,
ImmutableArray<NullableAnnotation> elementNullableAnnotations = default)
{
if (elementTypes.IsDefault)
{
throw new ArgumentNullException(nameof(elementTypes));
}
int n = elementTypes.Length;
if (elementTypes.Length <= 1)
{
throw new ArgumentException(CodeAnalysisResources.TuplesNeedAtLeastTwoElements, nameof(elementNames));
}
elementNames = CheckTupleElementNames(n, elementNames);
CheckTupleElementLocations(n, elementLocations);
CheckTupleElementNullableAnnotations(n, elementNullableAnnotations);
for (int i = 0; i < n; i++)
{
if (elementTypes[i] == null)
{
throw new ArgumentNullException($"{nameof(elementTypes)}[{i}]");
}
if (!elementLocations.IsDefault && elementLocations[i] == null)
{
throw new ArgumentNullException($"{nameof(elementLocations)}[{i}]");
}
}
return CommonCreateTupleTypeSymbol(elementTypes, elementNames, elementLocations, elementNullableAnnotations);
}
/// <summary>
/// Returns a new INamedTypeSymbol with the given element types, names, and locations.
/// </summary>
/// <remarks>This overload is for backwards compatibility. Do not remove.</remarks>
public INamedTypeSymbol CreateTupleTypeSymbol(
ImmutableArray<ITypeSymbol> elementTypes,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations)
{
return CreateTupleTypeSymbol(elementTypes, elementNames, elementLocations, elementNullableAnnotations: default);
}
protected static void CheckTupleElementNullableAnnotations(
int cardinality,
ImmutableArray<NullableAnnotation> elementNullableAnnotations)
{
if (!elementNullableAnnotations.IsDefault)
{
if (elementNullableAnnotations.Length != cardinality)
{
throw new ArgumentException(CodeAnalysisResources.TupleElementNullableAnnotationCountMismatch, nameof(elementNullableAnnotations));
}
}
}
/// <summary>
/// Check that if any names are provided, and their number matches the expected cardinality.
/// Returns a normalized version of the element names (empty array if all the names are null).
/// </summary>
protected static ImmutableArray<string?> CheckTupleElementNames(int cardinality, ImmutableArray<string?> elementNames)
{
if (!elementNames.IsDefault)
{
if (elementNames.Length != cardinality)
{
throw new ArgumentException(CodeAnalysisResources.TupleElementNameCountMismatch, nameof(elementNames));
}
for (int i = 0; i < elementNames.Length; i++)
{
if (elementNames[i] == "")
{
throw new ArgumentException(CodeAnalysisResources.TupleElementNameEmpty, $"{nameof(elementNames)}[{i}]");
}
}
if (elementNames.All(n => n == null))
{
return default;
}
}
return elementNames;
}
protected static void CheckTupleElementLocations(
int cardinality,
ImmutableArray<Location?> elementLocations)
{
if (!elementLocations.IsDefault)
{
if (elementLocations.Length != cardinality)
{
throw new ArgumentException(CodeAnalysisResources.TupleElementLocationCountMismatch, nameof(elementLocations));
}
}
}
protected abstract INamedTypeSymbol CommonCreateTupleTypeSymbol(
ImmutableArray<ITypeSymbol> elementTypes,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations,
ImmutableArray<NullableAnnotation> elementNullableAnnotations);
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Returns a new INamedTypeSymbol with the given underlying type and
/// (optional) element names, locations, and nullable annotations.
/// The underlying type needs to be tuple-compatible.
/// </summary>
public INamedTypeSymbol CreateTupleTypeSymbol(
INamedTypeSymbol underlyingType,
ImmutableArray<string?> elementNames = default,
ImmutableArray<Location?> elementLocations = default,
ImmutableArray<NullableAnnotation> elementNullableAnnotations = default)
{
if ((object)underlyingType == null)
{
throw new ArgumentNullException(nameof(underlyingType));
}
return CommonCreateTupleTypeSymbol(underlyingType, elementNames, elementLocations, elementNullableAnnotations);
}
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Returns a new INamedTypeSymbol with the given underlying type and element names and locations.
/// The underlying type needs to be tuple-compatible.
/// </summary>
/// <remarks>This overload is for backwards compatibility. Do not remove.</remarks>
public INamedTypeSymbol CreateTupleTypeSymbol(
INamedTypeSymbol underlyingType,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations)
{
return CreateTupleTypeSymbol(underlyingType, elementNames, elementLocations, elementNullableAnnotations: default);
}
protected abstract INamedTypeSymbol CommonCreateTupleTypeSymbol(
INamedTypeSymbol underlyingType,
ImmutableArray<string?> elementNames,
ImmutableArray<Location?> elementLocations,
ImmutableArray<NullableAnnotation> elementNullableAnnotations);
/// <summary>
/// Returns a new anonymous type symbol with the given member types, names, source locations, and nullable annotations.
/// Anonymous type members will be readonly by default. Writable properties are
/// supported in VB and can be created by passing in <see langword="false"/> in the
/// appropriate locations in <paramref name="memberIsReadOnly"/>.
/// </summary>
public INamedTypeSymbol CreateAnonymousTypeSymbol(
ImmutableArray<ITypeSymbol> memberTypes,
ImmutableArray<string> memberNames,
ImmutableArray<bool> memberIsReadOnly = default,
ImmutableArray<Location> memberLocations = default,
ImmutableArray<NullableAnnotation> memberNullableAnnotations = default)
{
if (memberTypes.IsDefault)
{
throw new ArgumentNullException(nameof(memberTypes));
}
if (memberNames.IsDefault)
{
throw new ArgumentNullException(nameof(memberNames));
}
if (memberTypes.Length != memberNames.Length)
{
throw new ArgumentException(string.Format(CodeAnalysisResources.AnonymousTypeMemberAndNamesCountMismatch2,
nameof(memberTypes), nameof(memberNames)));
}
if (!memberLocations.IsDefault && memberLocations.Length != memberTypes.Length)
{
throw new ArgumentException(string.Format(CodeAnalysisResources.AnonymousTypeArgumentCountMismatch2,
nameof(memberLocations), nameof(memberNames)));
}
if (!memberIsReadOnly.IsDefault && memberIsReadOnly.Length != memberTypes.Length)
{
throw new ArgumentException(string.Format(CodeAnalysisResources.AnonymousTypeArgumentCountMismatch2,
nameof(memberIsReadOnly), nameof(memberNames)));
}
if (!memberNullableAnnotations.IsDefault && memberNullableAnnotations.Length != memberTypes.Length)
{
throw new ArgumentException(string.Format(CodeAnalysisResources.AnonymousTypeArgumentCountMismatch2,
nameof(memberNullableAnnotations), nameof(memberNames)));
}
for (int i = 0, n = memberTypes.Length; i < n; i++)
{
if (memberTypes[i] == null)
{
throw new ArgumentNullException($"{nameof(memberTypes)}[{i}]");
}
if (memberNames[i] == null)
{
throw new ArgumentNullException($"{nameof(memberNames)}[{i}]");
}
if (!memberLocations.IsDefault && memberLocations[i] == null)
{
throw new ArgumentNullException($"{nameof(memberLocations)}[{i}]");
}
}
return CommonCreateAnonymousTypeSymbol(memberTypes, memberNames, memberLocations, memberIsReadOnly, memberNullableAnnotations);
}
/// <summary>
/// Returns a new anonymous type symbol with the given member types, names, and source locations.
/// Anonymous type members will be readonly by default. Writable properties are
/// supported in VB and can be created by passing in <see langword="false"/> in the
/// appropriate locations in <paramref name="memberIsReadOnly"/>.
/// </summary>
/// <remarks>This overload is for backwards compatibility. Do not remove.</remarks>
public INamedTypeSymbol CreateAnonymousTypeSymbol(
ImmutableArray<ITypeSymbol> memberTypes,
ImmutableArray<string> memberNames,
ImmutableArray<bool> memberIsReadOnly,
ImmutableArray<Location> memberLocations)
{
return CreateAnonymousTypeSymbol(memberTypes, memberNames, memberIsReadOnly, memberLocations, memberNullableAnnotations: default);
}
protected abstract INamedTypeSymbol CommonCreateAnonymousTypeSymbol(
ImmutableArray<ITypeSymbol> memberTypes,
ImmutableArray<string> memberNames,
ImmutableArray<Location> memberLocations,
ImmutableArray<bool> memberIsReadOnly,
ImmutableArray<NullableAnnotation> memberNullableAnnotations);
/// <summary>
/// Creates an <see cref="IMethodSymbol"/> whose <see cref="IMethodSymbol.MethodKind"/> is <see
/// cref="MethodKind.BuiltinOperator"/> for a binary operator. Built-in operators are commonly created for
/// symbols like <c>bool int.operator ==(int v1, int v2)</c> which the language implicitly supports, even if such
/// a symbol is not explicitly defined for that type in either source or metadata.
/// </summary>
/// <param name="name">The binary operator name. Should be one of the names from <see cref="WellKnownMemberNames"/>.</param>
/// <param name="returnType">The return type of the binary operator.</param>
/// <param name="leftType">The type of the left operand of the binary operator.</param>
/// <param name="rightType">The type of the right operand of the binary operator.</param>
public IMethodSymbol CreateBuiltinOperator(string name, ITypeSymbol returnType, ITypeSymbol leftType, ITypeSymbol rightType)
{
// Can't check 'name' here as VB and C# support a different subset of names.
if (returnType is null)
throw new ArgumentNullException(nameof(returnType));
if (leftType is null)
throw new ArgumentNullException(nameof(leftType));
if (rightType is null)
throw new ArgumentNullException(nameof(rightType));
return CommonCreateBuiltinOperator(name, returnType, leftType, rightType);
}
protected abstract IMethodSymbol CommonCreateBuiltinOperator(string name, ITypeSymbol returnType, ITypeSymbol leftType, ITypeSymbol rightType);
/// <summary>
/// Creates an <see cref="IMethodSymbol"/> whose <see cref="IMethodSymbol.MethodKind"/> is <see
/// cref="MethodKind.BuiltinOperator"/> for a unary operator. Built-in operators are commonly created for
/// symbols like <c>bool int.operator -(int value)</c> which the language implicitly supports, even if such a
/// symbol is not explicitly defined for that type in either source or metadata.
/// </summary>
/// <param name="name">The unary operator name. Should be one of the names from <see cref="WellKnownMemberNames"/>.</param>
/// <param name="returnType">The return type of the unary operator.</param>
/// <param name="operandType">The type the operator applies to.</param>
public IMethodSymbol CreateBuiltinOperator(string name, ITypeSymbol returnType, ITypeSymbol operandType)
{
// Can't check 'name' here as VB and C# support a different subset of names.
if (returnType is null)
throw new ArgumentNullException(nameof(returnType));
if (operandType is null)
throw new ArgumentNullException(nameof(operandType));
return CommonCreateBuiltinOperator(name, returnType, operandType);
}
protected abstract IMethodSymbol CommonCreateBuiltinOperator(string name, ITypeSymbol returnType, ITypeSymbol operandType);
/// <summary>
/// Classifies a conversion from <paramref name="source"/> to <paramref name="destination"/> according
/// to this compilation's programming language.
/// </summary>
/// <param name="source">Source type of value to be converted</param>
/// <param name="destination">Destination type of value to be converted</param>
/// <returns>A <see cref="CommonConversion"/> that classifies the conversion from the
/// <paramref name="source"/> type to the <paramref name="destination"/> type.</returns>
public abstract CommonConversion ClassifyCommonConversion(ITypeSymbol source, ITypeSymbol destination);
/// <summary>
/// Returns true if there is an implicit (C#) or widening (VB) conversion from
/// <paramref name="fromType"/> to <paramref name="toType"/>. Returns false if
/// either <paramref name="fromType"/> or <paramref name="toType"/> is null, or
/// if no such conversion exists.
/// </summary>
public bool HasImplicitConversion(ITypeSymbol? fromType, ITypeSymbol? toType)
=> fromType != null && toType != null && this.ClassifyCommonConversion(fromType, toType).IsImplicit;
/// <summary>
/// Checks if <paramref name="symbol"/> is accessible from within <paramref name="within"/>. An optional qualifier of type
/// <paramref name="throughType"/> is used to resolve protected access for instance members. All symbols are
/// required to be from this compilation or some assembly referenced (<see cref="References"/>) by this
/// compilation. <paramref name="within"/> is required to be an <see cref="INamedTypeSymbol"/> or <see cref="IAssemblySymbol"/>.
/// </summary>
/// <remarks>
/// <para>Submissions can reference symbols from previous submissions and their referenced assemblies, even
/// though those references are missing from <see cref="References"/>.
/// See https://github.com/dotnet/roslyn/issues/27356.
/// This implementation works around that by permitting symbols from previous submissions as well.</para>
/// <para>It is advised to avoid the use of this API within the compilers, as the compilers have additional
/// requirements for access checking that are not satisfied by this implementation, including the
/// avoidance of infinite recursion that could result from the use of the ISymbol APIs here, the detection
/// of use-site diagnostics, and additional returned details (from the compiler's internal APIs) that are
/// helpful for more precisely diagnosing reasons for accessibility failure.</para>
/// </remarks>
public bool IsSymbolAccessibleWithin(
ISymbol symbol,
ISymbol within,
ITypeSymbol? throughType = null)
{
if (symbol is null)
{
throw new ArgumentNullException(nameof(symbol));
}
if (within is null)
{
throw new ArgumentNullException(nameof(within));
}
if (!(within is INamedTypeSymbol || within is IAssemblySymbol))
{
throw new ArgumentException(string.Format(CodeAnalysisResources.IsSymbolAccessibleBadWithin, nameof(within)), nameof(within));
}
checkInCompilationReferences(symbol, nameof(symbol));
checkInCompilationReferences(within, nameof(within));
if (throughType is object)
{
checkInCompilationReferences(throughType, nameof(throughType));
}
return IsSymbolAccessibleWithinCore(symbol, within, throughType);
void checkInCompilationReferences(ISymbol s, string parameterName)
{
if (!isContainingAssemblyInReferences(s))
{
throw new ArgumentException(string.Format(CodeAnalysisResources.IsSymbolAccessibleWrongAssembly, parameterName), parameterName);
}
}
bool assemblyIsInReferences(IAssemblySymbol a)
{
if (assemblyIsInCompilationReferences(a, this))
{
return true;
}
if (this.IsSubmission)
{
// Submissions can reference symbols from previous submissions and their referenced assemblies, even
// though those references are missing from this.References. We work around that by digging in
// to find references of previous submissions. See https://github.com/dotnet/roslyn/issues/27356
for (Compilation? c = this.PreviousSubmission; c != null; c = c.PreviousSubmission)
{
if (assemblyIsInCompilationReferences(a, c))
{
return true;
}
}
}
return false;
}
bool assemblyIsInCompilationReferences(IAssemblySymbol a, Compilation compilation)
{
if (a.Equals(compilation.Assembly))
{
return true;
}
foreach (var reference in compilation.References)
{
if (a.Equals(compilation.GetAssemblyOrModuleSymbol(reference)))
{
return true;
}
}
return false;
}
bool isContainingAssemblyInReferences(ISymbol s)
{
while (true)
{
switch (s.Kind)
{
case SymbolKind.Assembly:
return assemblyIsInReferences((IAssemblySymbol)s);
case SymbolKind.PointerType:
s = ((IPointerTypeSymbol)s).PointedAtType;
continue;
case SymbolKind.ArrayType:
s = ((IArrayTypeSymbol)s).ElementType;
continue;
case SymbolKind.Alias:
s = ((IAliasSymbol)s).Target;
continue;
case SymbolKind.Discard:
s = ((IDiscardSymbol)s).Type;
continue;
case SymbolKind.FunctionPointerType:
var funcPtr = (IFunctionPointerTypeSymbol)s;
if (!isContainingAssemblyInReferences(funcPtr.Signature.ReturnType))
{
return false;
}
foreach (var param in funcPtr.Signature.Parameters)
{
if (!isContainingAssemblyInReferences(param.Type))
{
return false;
}
}
return true;
case SymbolKind.DynamicType:
case SymbolKind.ErrorType:
case SymbolKind.Preprocessing:
case SymbolKind.Namespace:
// these symbols are not restricted in where they can be accessed, so unless they report
// a containing assembly, we treat them as in the current assembly for access purposes
return assemblyIsInReferences(s.ContainingAssembly ?? this.Assembly);
default:
return assemblyIsInReferences(s.ContainingAssembly);
}
}
}
}
private protected abstract bool IsSymbolAccessibleWithinCore(
ISymbol symbol,
ISymbol within,
ITypeSymbol? throughType);
internal abstract IConvertibleConversion ClassifyConvertibleConversion(IOperation source, ITypeSymbol destination, out ConstantValue? constantValue);
#endregion
#region Diagnostics
internal const CompilationStage DefaultDiagnosticsStage = CompilationStage.Compile;
/// <summary>
/// Gets the diagnostics produced during the parsing stage.
/// </summary>
public abstract ImmutableArray<Diagnostic> GetParseDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Gets the diagnostics produced during symbol declaration.
/// </summary>
public abstract ImmutableArray<Diagnostic> GetDeclarationDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Gets the diagnostics produced during the analysis of method bodies and field initializers.
/// </summary>
public abstract ImmutableArray<Diagnostic> GetMethodBodyDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Gets all the diagnostics for the compilation, including syntax, declaration, and
/// binding. Does not include any diagnostics that might be produced during emit, see
/// <see cref="EmitResult"/>.
/// </summary>
public abstract ImmutableArray<Diagnostic> GetDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
internal abstract void GetDiagnostics(CompilationStage stage, bool includeEarlierStages, DiagnosticBag diagnostics, CancellationToken cancellationToken = default);
/// <summary>
/// Unique metadata assembly references that are considered to be used by this compilation.
/// For example, if a type declared in a referenced assembly is referenced in source code
/// within this compilation, the reference is considered to be used. Etc.
/// The returned set is a subset of references returned by <see cref="References"/> API.
/// The result is undefined if the compilation contains errors.
///
/// The effect of imported namespaces on result of this API depends on whether reporting of
/// unused imports is disabled for the compilation. The reporting of unused imports is disabled
/// if <see cref="ParseOptions.DocumentationMode"/> is set to <see cref="DocumentationMode.None"/>.
///
/// When unused imports reporting is disabled, all referenced assemblies containing any types
/// that belong to imported namespaces are included in the result. I.e. considered used.
///
/// When unused imports reporting is enabled, imported namespaces do not have effect on the result
/// of this API. Therefore, removing assembly references that aren't in the result, could potentially
/// cause error "CS0246: The type or namespace name could not be found (are you missing a using directive or an assembly reference?)"
/// on an unused namespace import. However, that import would be reported by compiler as unused
/// for the compilation on which this API was invoked. In order to avoid the errors, it is recommended to
/// remove unused assembly references and unused imports at the same time.
/// </summary>
public abstract ImmutableArray<MetadataReference> GetUsedAssemblyReferences(CancellationToken cancellationToken = default(CancellationToken));
internal void EnsureCompilationEventQueueCompleted()
{
RoslynDebug.Assert(EventQueue != null);
lock (EventQueue)
{
if (!EventQueue.IsCompleted)
{
CompleteCompilationEventQueue_NoLock();
}
}
}
internal void RegisterPossibleUpcomingEventEnqueue()
{
Interlocked.Increment(ref _eventQueueEnqueuePendingCount);
}
internal void UnregisterPossibleUpcomingEventEnqueue()
{
Interlocked.Decrement(ref _eventQueueEnqueuePendingCount);
}
internal void CompleteCompilationEventQueue_NoLock()
{
RoslynDebug.Assert(EventQueue != null);
if (Volatile.Read(ref _eventQueueEnqueuePendingCount) != 0)
{
SpinWait.SpinUntil(() => Volatile.Read(ref _eventQueueEnqueuePendingCount) == 0);
}
// Signal the end of compilation.
EventQueue.TryEnqueue(new CompilationCompletedEvent(this));
EventQueue.PromiseNotToEnqueue();
EventQueue.TryComplete();
}
internal abstract CommonMessageProvider MessageProvider { get; }
/// <summary>
/// Filter out warnings based on the compiler options (/nowarn, /warn and /warnaserror) and the pragma warning directives.
/// 'incoming' is freed.
/// </summary>
/// <param name="accumulator">Bag to which filtered diagnostics will be added.</param>
/// <param name="incoming">Diagnostics to be filtered.</param>
/// <returns>True if there are no unsuppressed errors (i.e., no errors which fail compilation).</returns>
internal bool FilterAndAppendAndFreeDiagnostics(DiagnosticBag accumulator, [DisallowNull] ref DiagnosticBag? incoming, CancellationToken cancellationToken)
{
RoslynDebug.Assert(incoming is object);
bool result = FilterAndAppendDiagnostics(accumulator, incoming, cancellationToken);
incoming.Free();
incoming = null;
return result;
}
internal bool FilterAndAppendDiagnostics(DiagnosticBag accumulator, DiagnosticBag incoming, CancellationToken cancellationToken)
{
RoslynDebug.Assert(incoming is object);
bool result = FilterAndAppendDiagnostics(accumulator, incoming.AsEnumerableWithoutResolution(), exclude: null, cancellationToken);
return result;
}
/// <summary>
/// Filter out warnings based on the compiler options (/nowarn, /warn and /warnaserror) and the pragma warning directives.
/// </summary>
/// <returns>True if there are no unsuppressed errors (i.e., no errors which fail compilation).</returns>
internal bool FilterAndAppendDiagnostics(DiagnosticBag accumulator, IEnumerable<Diagnostic> incoming, HashSet<int>? exclude, CancellationToken cancellationToken)
{
bool hasError = false;
bool reportSuppressedDiagnostics = Options.ReportSuppressedDiagnostics;
foreach (Diagnostic d in incoming)
{
if (exclude?.Contains(d.Code) == true)
{
continue;
}
var filtered = Options.FilterDiagnostic(d, cancellationToken);
if (filtered == null ||
(!reportSuppressedDiagnostics && filtered.IsSuppressed))
{
continue;
}
else if (filtered.IsUnsuppressableError())
{
hasError = true;
}
accumulator.Add(filtered);
}
return !hasError;
}
#endregion
#region Resources
/// <summary>
/// Create a stream filled with default win32 resources.
/// </summary>
public Stream CreateDefaultWin32Resources(bool versionResource, bool noManifest, Stream? manifestContents, Stream? iconInIcoFormat)
{
//Win32 resource encodings use a lot of 16bit values. Do all of the math checked with the
//expectation that integer types are well-chosen with size in mind.
checked
{
var result = new MemoryStream(1024);
//start with a null resource just as rc.exe does
AppendNullResource(result);
if (versionResource)
AppendDefaultVersionResource(result);
if (!noManifest)
{
if (this.Options.OutputKind.IsApplication())
{
// Applications use a default manifest if one is not specified.
if (manifestContents == null)
{
manifestContents = typeof(Compilation).GetTypeInfo().Assembly.GetManifestResourceStream("Microsoft.CodeAnalysis.Resources.default.win32manifest");
}
}
else
{
// Modules never have manifests, even if one is specified.
//Debug.Assert(!this.Options.OutputKind.IsNetModule() || manifestContents == null);
}
if (manifestContents != null)
{
Win32ResourceConversions.AppendManifestToResourceStream(result, manifestContents, !this.Options.OutputKind.IsApplication());
}
}
if (iconInIcoFormat != null)
{
Win32ResourceConversions.AppendIconToResourceStream(result, iconInIcoFormat);
}
result.Position = 0;
return result;
}
}
internal static void AppendNullResource(Stream resourceStream)
{
var writer = new BinaryWriter(resourceStream);
writer.Write((UInt32)0);
writer.Write((UInt32)0x20);
writer.Write((UInt16)0xFFFF);
writer.Write((UInt16)0);
writer.Write((UInt16)0xFFFF);
writer.Write((UInt16)0);
writer.Write((UInt32)0); //DataVersion
writer.Write((UInt16)0); //MemoryFlags
writer.Write((UInt16)0); //LanguageId
writer.Write((UInt32)0); //Version
writer.Write((UInt32)0); //Characteristics
}
protected abstract void AppendDefaultVersionResource(Stream resourceStream);
internal enum Win32ResourceForm : byte
{
UNKNOWN,
COFF,
RES
}
internal static Win32ResourceForm DetectWin32ResourceForm(Stream win32Resources)
{
var reader = new BinaryReader(win32Resources, Encoding.Unicode);
var initialPosition = win32Resources.Position;
var initial32Bits = reader.ReadUInt32();
win32Resources.Position = initialPosition;
//RC.EXE output starts with a resource that contains no data.
if (initial32Bits == 0)
return Win32ResourceForm.RES;
else if ((initial32Bits & 0xFFFF0000) != 0 || (initial32Bits & 0x0000FFFF) != 0xFFFF)
// See CLiteWeightStgdbRW::FindObjMetaData in peparse.cpp
return Win32ResourceForm.COFF;
else
return Win32ResourceForm.UNKNOWN;
}
internal Cci.ResourceSection? MakeWin32ResourcesFromCOFF(Stream? win32Resources, DiagnosticBag diagnostics)
{
if (win32Resources == null)
{
return null;
}
Cci.ResourceSection resources;
try
{
resources = COFFResourceReader.ReadWin32ResourcesFromCOFF(win32Resources);
}
catch (BadImageFormatException ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, Location.None, ex.Message));
return null;
}
catch (IOException ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, Location.None, ex.Message));
return null;
}
catch (ResourceException ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, Location.None, ex.Message));
return null;
}
return resources;
}
internal List<Win32Resource>? MakeWin32ResourceList(Stream? win32Resources, DiagnosticBag diagnostics)
{
if (win32Resources == null)
{
return null;
}
List<RESOURCE> resources;
try
{
resources = CvtResFile.ReadResFile(win32Resources);
}
catch (ResourceException ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, Location.None, ex.Message));
return null;
}
if (resources == null)
{
return null;
}
var resourceList = new List<Win32Resource>();
foreach (var r in resources)
{
var result = new Win32Resource(
data: r.data,
codePage: 0,
languageId: r.LanguageId,
//EDMAURER converting to int from ushort.
//Go to short first to avoid sign extension.
id: unchecked((short)r.pstringName!.Ordinal),
name: r.pstringName.theString,
typeId: unchecked((short)r.pstringType!.Ordinal),
typeName: r.pstringType.theString
);
resourceList.Add(result);
}
return resourceList;
}
internal void SetupWin32Resources(CommonPEModuleBuilder moduleBeingBuilt, Stream? win32Resources, bool useRawWin32Resources, DiagnosticBag diagnostics)
{
if (win32Resources == null)
return;
if (useRawWin32Resources)
{
moduleBeingBuilt.RawWin32Resources = win32Resources;
return;
}
Win32ResourceForm resourceForm;
try
{
resourceForm = DetectWin32ResourceForm(win32Resources);
}
catch (EndOfStreamException)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, NoLocation.Singleton, CodeAnalysisResources.UnrecognizedResourceFileFormat));
return;
}
catch (Exception ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, NoLocation.Singleton, ex.Message));
return;
}
switch (resourceForm)
{
case Win32ResourceForm.COFF:
moduleBeingBuilt.Win32ResourceSection = MakeWin32ResourcesFromCOFF(win32Resources, diagnostics);
break;
case Win32ResourceForm.RES:
moduleBeingBuilt.Win32Resources = MakeWin32ResourceList(win32Resources, diagnostics);
break;
default:
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_BadWin32Resource, NoLocation.Singleton, CodeAnalysisResources.UnrecognizedResourceFileFormat));
break;
}
}
internal void ReportManifestResourceDuplicates(
IEnumerable<ResourceDescription>? manifestResources,
IEnumerable<string> addedModuleNames,
IEnumerable<string> addedModuleResourceNames,
DiagnosticBag diagnostics)
{
if (Options.OutputKind == OutputKind.NetModule && !(manifestResources != null && manifestResources.Any()))
{
return;
}
var uniqueResourceNames = new HashSet<string>();
if (manifestResources != null && manifestResources.Any())
{
var uniqueFileNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var resource in manifestResources)
{
if (!uniqueResourceNames.Add(resource.ResourceName))
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_ResourceNotUnique, Location.None, resource.ResourceName));
}
// file name could be null if resource is embedded
var fileName = resource.FileName;
if (fileName != null && !uniqueFileNames.Add(fileName))
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_ResourceFileNameNotUnique, Location.None, fileName));
}
}
foreach (var fileName in addedModuleNames)
{
if (!uniqueFileNames.Add(fileName))
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_ResourceFileNameNotUnique, Location.None, fileName));
}
}
}
if (Options.OutputKind != OutputKind.NetModule)
{
foreach (string name in addedModuleResourceNames)
{
if (!uniqueResourceNames.Add(name))
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_ResourceNotUnique, Location.None, name));
}
}
}
}
#endregion
#region Emit
/// <summary>
/// There are two ways to sign PE files
/// 1. By directly signing the <see cref="PEBuilder"/>
/// 2. Write the unsigned PE to disk and use CLR COM APIs to sign.
/// The preferred method is #1 as it's more efficient and more resilient (no reliance on %TEMP%). But
/// we must continue to support #2 as it's the only way to do the following:
/// - Access private keys stored in a key container
/// - Do proper counter signature verification for AssemblySignatureKey attributes
/// </summary>
internal bool SignUsingBuilder =>
string.IsNullOrEmpty(StrongNameKeys.KeyContainer) &&
!StrongNameKeys.HasCounterSignature &&
!_features.ContainsKey("UseLegacyStrongNameProvider");
/// <summary>
/// Constructs the module serialization properties out of the compilation options of this compilation.
/// </summary>
internal Cci.ModulePropertiesForSerialization ConstructModuleSerializationProperties(
EmitOptions emitOptions,
string? targetRuntimeVersion,
Guid moduleVersionId = default(Guid))
{
CompilationOptions compilationOptions = this.Options;
Platform platform = compilationOptions.Platform;
OutputKind outputKind = compilationOptions.OutputKind;
if (!platform.IsValid())
{
platform = Platform.AnyCpu;
}
if (!outputKind.IsValid())
{
outputKind = OutputKind.DynamicallyLinkedLibrary;
}
bool requires64Bit = platform.Requires64Bit();
bool requires32Bit = platform.Requires32Bit();
ushort fileAlignment;
if (emitOptions.FileAlignment == 0 || !CompilationOptions.IsValidFileAlignment(emitOptions.FileAlignment))
{
fileAlignment = requires64Bit
? Cci.ModulePropertiesForSerialization.DefaultFileAlignment64Bit
: Cci.ModulePropertiesForSerialization.DefaultFileAlignment32Bit;
}
else
{
fileAlignment = (ushort)emitOptions.FileAlignment;
}
ulong baseAddress = unchecked(emitOptions.BaseAddress + 0x8000) & (requires64Bit ? 0xffffffffffff0000 : 0x00000000ffff0000);
// cover values smaller than 0x8000, overflow and default value 0):
if (baseAddress == 0)
{
if (outputKind == OutputKind.ConsoleApplication ||
outputKind == OutputKind.WindowsApplication ||
outputKind == OutputKind.WindowsRuntimeApplication)
{
baseAddress = (requires64Bit) ? Cci.ModulePropertiesForSerialization.DefaultExeBaseAddress64Bit : Cci.ModulePropertiesForSerialization.DefaultExeBaseAddress32Bit;
}
else
{
baseAddress = (requires64Bit) ? Cci.ModulePropertiesForSerialization.DefaultDllBaseAddress64Bit : Cci.ModulePropertiesForSerialization.DefaultDllBaseAddress32Bit;
}
}
ulong sizeOfHeapCommit = requires64Bit
? Cci.ModulePropertiesForSerialization.DefaultSizeOfHeapCommit64Bit
: Cci.ModulePropertiesForSerialization.DefaultSizeOfHeapCommit32Bit;
// Dev10 always uses the default value for 32bit for sizeOfHeapReserve.
// check with link -dump -headers <filename>
const ulong sizeOfHeapReserve = Cci.ModulePropertiesForSerialization.DefaultSizeOfHeapReserve32Bit;
ulong sizeOfStackReserve = requires64Bit
? Cci.ModulePropertiesForSerialization.DefaultSizeOfStackReserve64Bit
: Cci.ModulePropertiesForSerialization.DefaultSizeOfStackReserve32Bit;
ulong sizeOfStackCommit = requires64Bit
? Cci.ModulePropertiesForSerialization.DefaultSizeOfStackCommit64Bit
: Cci.ModulePropertiesForSerialization.DefaultSizeOfStackCommit32Bit;
SubsystemVersion subsystemVersion;
if (emitOptions.SubsystemVersion.Equals(SubsystemVersion.None) || !emitOptions.SubsystemVersion.IsValid)
{
subsystemVersion = SubsystemVersion.Default(outputKind, platform);
}
else
{
subsystemVersion = emitOptions.SubsystemVersion;
}
Machine machine;
switch (platform)
{
case Platform.Arm64:
machine = Machine.Arm64;
break;
case Platform.Arm:
machine = Machine.ArmThumb2;
break;
case Platform.X64:
machine = Machine.Amd64;
break;
case Platform.Itanium:
machine = Machine.IA64;
break;
case Platform.X86:
machine = Machine.I386;
break;
case Platform.AnyCpu:
case Platform.AnyCpu32BitPreferred:
machine = Machine.Unknown;
break;
default:
throw ExceptionUtilities.UnexpectedValue(platform);
}
return new Cci.ModulePropertiesForSerialization(
persistentIdentifier: moduleVersionId,
corFlags: GetCorHeaderFlags(machine, HasStrongName, prefers32Bit: platform == Platform.AnyCpu32BitPreferred),
fileAlignment: fileAlignment,
sectionAlignment: Cci.ModulePropertiesForSerialization.DefaultSectionAlignment,
targetRuntimeVersion: targetRuntimeVersion,
machine: machine,
baseAddress: baseAddress,
sizeOfHeapReserve: sizeOfHeapReserve,
sizeOfHeapCommit: sizeOfHeapCommit,
sizeOfStackReserve: sizeOfStackReserve,
sizeOfStackCommit: sizeOfStackCommit,
dllCharacteristics: GetDllCharacteristics(emitOptions.HighEntropyVirtualAddressSpace, compilationOptions.OutputKind == OutputKind.WindowsRuntimeApplication),
imageCharacteristics: GetCharacteristics(outputKind, requires32Bit),
subsystem: GetSubsystem(outputKind),
majorSubsystemVersion: (ushort)subsystemVersion.Major,
minorSubsystemVersion: (ushort)subsystemVersion.Minor,
linkerMajorVersion: this.LinkerMajorVersion,
linkerMinorVersion: 0);
}
private static CorFlags GetCorHeaderFlags(Machine machine, bool strongNameSigned, bool prefers32Bit)
{
CorFlags result = CorFlags.ILOnly;
if (machine == Machine.I386)
{
result |= CorFlags.Requires32Bit;
}
if (strongNameSigned)
{
result |= CorFlags.StrongNameSigned;
}
if (prefers32Bit)
{
result |= CorFlags.Requires32Bit | CorFlags.Prefers32Bit;
}
return result;
}
internal static DllCharacteristics GetDllCharacteristics(bool enableHighEntropyVA, bool configureToExecuteInAppContainer)
{
var result =
DllCharacteristics.DynamicBase |
DllCharacteristics.NxCompatible |
DllCharacteristics.NoSeh |
DllCharacteristics.TerminalServerAware;
if (enableHighEntropyVA)
{
result |= DllCharacteristics.HighEntropyVirtualAddressSpace;
}
if (configureToExecuteInAppContainer)
{
result |= DllCharacteristics.AppContainer;
}
return result;
}
private static Characteristics GetCharacteristics(OutputKind outputKind, bool requires32Bit)
{
var characteristics = Characteristics.ExecutableImage;
if (requires32Bit)
{
// 32 bit machine (The standard says to always set this, the linker team says otherwise)
// The loader team says that this is not used for anything in the OS.
characteristics |= Characteristics.Bit32Machine;
}
else
{
// Large address aware (the standard says never to set this, the linker team says otherwise).
// The loader team says that this is not overridden for managed binaries and will be respected if set.
characteristics |= Characteristics.LargeAddressAware;
}
switch (outputKind)
{
case OutputKind.WindowsRuntimeMetadata:
case OutputKind.DynamicallyLinkedLibrary:
case OutputKind.NetModule:
characteristics |= Characteristics.Dll;
break;
case OutputKind.ConsoleApplication:
case OutputKind.WindowsRuntimeApplication:
case OutputKind.WindowsApplication:
break;
default:
throw ExceptionUtilities.UnexpectedValue(outputKind);
}
return characteristics;
}
private static Subsystem GetSubsystem(OutputKind outputKind)
{
switch (outputKind)
{
case OutputKind.ConsoleApplication:
case OutputKind.DynamicallyLinkedLibrary:
case OutputKind.NetModule:
case OutputKind.WindowsRuntimeMetadata:
return Subsystem.WindowsCui;
case OutputKind.WindowsRuntimeApplication:
case OutputKind.WindowsApplication:
return Subsystem.WindowsGui;
default:
throw ExceptionUtilities.UnexpectedValue(outputKind);
}
}
/// <summary>
/// The value is not used by Windows loader, but the OS appcompat infrastructure uses it to identify apps.
/// It is useful for us to have a mechanism to identify the compiler that produced the binary.
/// This is the appropriate value to use for that. That is what it was invented for.
/// We don't want to have the high bit set for this in case some users perform a signed comparison to
/// determine if the value is less than some version. The C++ linker is at 0x0B.
/// We'll start our numbering at 0x30 for C#, 0x50 for VB.
/// </summary>
internal abstract byte LinkerMajorVersion { get; }
internal bool HasStrongName
{
get
{
return !IsDelaySigned
&& Options.OutputKind != OutputKind.NetModule
&& StrongNameKeys.CanProvideStrongName;
}
}
internal bool IsRealSigned
{
get
{
// A module cannot be signed. The native compiler allowed one to create a netmodule with an AssemblyKeyFile
// or Container attribute (or specify a key via the cmd line). When the module was linked into an assembly,
// alink would sign the assembly. So rather than give an error we just don't sign when outputting a module.
return !IsDelaySigned
&& !Options.PublicSign
&& Options.OutputKind != OutputKind.NetModule
&& StrongNameKeys.CanSign;
}
}
/// <summary>
/// Return true if the compilation contains any code or types.
/// </summary>
internal abstract bool HasCodeToEmit();
internal abstract bool IsDelaySigned { get; }
internal abstract StrongNameKeys StrongNameKeys { get; }
internal abstract CommonPEModuleBuilder? CreateModuleBuilder(
EmitOptions emitOptions,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
IEnumerable<ResourceDescription>? manifestResources,
CompilationTestData? testData,
DiagnosticBag diagnostics,
CancellationToken cancellationToken);
/// <summary>
/// Report declaration diagnostics and compile and synthesize method bodies.
/// </summary>
/// <returns>True if successful.</returns>
internal abstract bool CompileMethods(
CommonPEModuleBuilder moduleBuilder,
bool emittingPdb,
DiagnosticBag diagnostics,
Predicate<ISymbolInternal>? filterOpt,
CancellationToken cancellationToken);
internal bool CreateDebugDocuments(DebugDocumentsBuilder documentsBuilder, IEnumerable<EmbeddedText> embeddedTexts, DiagnosticBag diagnostics)
{
// Check that all syntax trees are debuggable:
bool allTreesDebuggable = true;
foreach (var tree in CommonSyntaxTrees)
{
if (!string.IsNullOrEmpty(tree.FilePath) && tree.GetText().Encoding == null)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_EncodinglessSyntaxTree, tree.GetRoot().GetLocation()));
allTreesDebuggable = false;
}
}
if (!allTreesDebuggable)
{
return false;
}
// Add debug documents for all embedded text first. This ensures that embedding
// takes priority over the syntax tree pass, which will not embed.
if (!embeddedTexts.IsEmpty())
{
foreach (var text in embeddedTexts)
{
Debug.Assert(!string.IsNullOrEmpty(text.FilePath));
string normalizedPath = documentsBuilder.NormalizeDebugDocumentPath(text.FilePath, basePath: null);
var existingDoc = documentsBuilder.TryGetDebugDocumentForNormalizedPath(normalizedPath);
if (existingDoc == null)
{
var document = new Cci.DebugSourceDocument(
normalizedPath,
DebugSourceDocumentLanguageId,
() => text.GetDebugSourceInfo());
documentsBuilder.AddDebugDocument(document);
}
}
}
// Add debug documents for all trees with distinct paths.
foreach (var tree in CommonSyntaxTrees)
{
if (!string.IsNullOrEmpty(tree.FilePath))
{
// compilation does not guarantee that all trees will have distinct paths.
// Do not attempt adding a document for a particular path if we already added one.
string normalizedPath = documentsBuilder.NormalizeDebugDocumentPath(tree.FilePath, basePath: null);
var existingDoc = documentsBuilder.TryGetDebugDocumentForNormalizedPath(normalizedPath);
if (existingDoc == null)
{
documentsBuilder.AddDebugDocument(new Cci.DebugSourceDocument(
normalizedPath,
DebugSourceDocumentLanguageId,
() => tree.GetDebugSourceInfo()));
}
}
}
// Add debug documents for all pragmas.
// If there are clashes with already processed directives, report warnings.
// If there are clashes with debug documents that came from actual trees, ignore the pragma.
// Therefore we need to add these in a separate pass after documents for syntax trees were added.
foreach (var tree in CommonSyntaxTrees)
{
AddDebugSourceDocumentsForChecksumDirectives(documentsBuilder, tree, diagnostics);
}
return true;
}
internal abstract Guid DebugSourceDocumentLanguageId { get; }
internal abstract void AddDebugSourceDocumentsForChecksumDirectives(DebugDocumentsBuilder documentsBuilder, SyntaxTree tree, DiagnosticBag diagnostics);
/// <summary>
/// Update resources.
/// </summary>
/// <returns>True if successful.</returns>
internal abstract bool GenerateResources(
CommonPEModuleBuilder moduleBuilder,
Stream? win32Resources,
bool useRawWin32Resources,
DiagnosticBag diagnostics,
CancellationToken cancellationToken);
/// <summary>
/// Generate XML documentation comments.
/// </summary>
/// <returns>True if successful.</returns>
internal abstract bool GenerateDocumentationComments(
Stream? xmlDocStream,
string? outputNameOverride,
DiagnosticBag diagnostics,
CancellationToken cancellationToken);
/// <summary>
/// Reports all unused imports/usings so far (and thus it must be called as a last step of Emit)
/// </summary>
internal abstract void ReportUnusedImports(
DiagnosticBag diagnostics,
CancellationToken cancellationToken);
internal static bool ReportUnusedImportsInTree(SyntaxTree tree)
{
return tree.Options.DocumentationMode != DocumentationMode.None;
}
/// <summary>
/// Signals the event queue, if any, that we are done compiling.
/// There should not be more compiling actions after this step.
/// NOTE: once we signal about completion to analyzers they will cancel and thus in some cases we
/// may be effectively cutting off some diagnostics.
/// It is not clear if behavior is desirable.
/// See: https://github.com/dotnet/roslyn/issues/11470
/// </summary>
/// <param name="filterTree">What tree to complete. null means complete all trees. </param>
internal abstract void CompleteTrees(SyntaxTree? filterTree);
internal bool Compile(
CommonPEModuleBuilder moduleBuilder,
bool emittingPdb,
DiagnosticBag diagnostics,
Predicate<ISymbolInternal>? filterOpt,
CancellationToken cancellationToken)
{
try
{
return CompileMethods(
moduleBuilder,
emittingPdb,
diagnostics: diagnostics,
filterOpt: filterOpt,
cancellationToken: cancellationToken);
}
finally
{
moduleBuilder.CompilationFinished();
}
}
internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken)
{
Debug.Assert(IsSubmission);
if (this.GetSubmissionSlotIndex() >= 0 && HasCodeToEmit())
{
if (!this.CommonAnonymousTypeManager.AreTemplatesSealed)
{
var discardedDiagnostics = DiagnosticBag.GetInstance();
var moduleBeingBuilt = this.CreateModuleBuilder(
emitOptions: EmitOptions.Default,
debugEntryPoint: null,
manifestResources: null,
sourceLinkStream: null,
embeddedTexts: null,
testData: null,
diagnostics: discardedDiagnostics,
cancellationToken: cancellationToken);
if (moduleBeingBuilt != null)
{
Compile(
moduleBeingBuilt,
diagnostics: discardedDiagnostics,
emittingPdb: false,
filterOpt: null,
cancellationToken: cancellationToken);
}
discardedDiagnostics.Free();
}
Debug.Assert(this.CommonAnonymousTypeManager.AreTemplatesSealed);
}
else
{
this.ScriptCompilationInfo?.PreviousScriptCompilation?.EnsureAnonymousTypeTemplates(cancellationToken);
}
}
// 1.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
[EditorBrowsable(EditorBrowsableState.Never)]
public EmitResult Emit(
Stream peStream,
Stream? pdbStream,
Stream? xmlDocumentationStream,
Stream? win32Resources,
IEnumerable<ResourceDescription>? manifestResources,
EmitOptions options,
CancellationToken cancellationToken)
{
return Emit(
peStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
debugEntryPoint: null,
sourceLinkStream: null,
embeddedTexts: null,
cancellationToken);
}
// 1.3 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
[EditorBrowsable(EditorBrowsableState.Never)]
public EmitResult Emit(
Stream peStream,
Stream pdbStream,
Stream xmlDocumentationStream,
Stream win32Resources,
IEnumerable<ResourceDescription> manifestResources,
EmitOptions options,
IMethodSymbol debugEntryPoint,
CancellationToken cancellationToken)
{
return Emit(
peStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
debugEntryPoint,
sourceLinkStream: null,
embeddedTexts: null,
cancellationToken);
}
// 2.0 BACKCOMPAT OVERLOAD -- DO NOT TOUCH
public EmitResult Emit(
Stream peStream,
Stream? pdbStream,
Stream? xmlDocumentationStream,
Stream? win32Resources,
IEnumerable<ResourceDescription>? manifestResources,
EmitOptions options,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
CancellationToken cancellationToken)
{
return Emit(
peStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
debugEntryPoint,
sourceLinkStream,
embeddedTexts,
metadataPEStream: null,
cancellationToken: cancellationToken);
}
/// <summary>
/// Emit the IL for the compiled source code into the specified stream.
/// </summary>
/// <param name="peStream">Stream to which the compilation will be written.</param>
/// <param name="metadataPEStream">Stream to which the metadata-only output will be written.</param>
/// <param name="pdbStream">Stream to which the compilation's debug info will be written. Null to forego PDB generation.</param>
/// <param name="xmlDocumentationStream">Stream to which the compilation's XML documentation will be written. Null to forego XML generation.</param>
/// <param name="win32Resources">Stream from which the compilation's Win32 resources will be read (in RES format).
/// Null to indicate that there are none. The RES format begins with a null resource entry.
/// Note that the caller is responsible for disposing this stream, if provided.</param>
/// <param name="manifestResources">List of the compilation's managed resources. Null to indicate that there are none.</param>
/// <param name="options">Emit options.</param>
/// <param name="debugEntryPoint">
/// Debug entry-point of the assembly. The method token is stored in the generated PDB stream.
///
/// When a program launches with a debugger attached the debugger places the first breakpoint to the start of the debug entry-point method.
/// The CLR starts executing the static Main method of <see cref="CompilationOptions.MainTypeName"/> type. When the first breakpoint is hit
/// the debugger steps thru the code statement by statement until user code is reached, skipping methods marked by <see cref="DebuggerHiddenAttribute"/>,
/// and taking other debugging attributes into consideration.
///
/// By default both entry points in an executable program (<see cref="OutputKind.ConsoleApplication"/>, <see cref="OutputKind.WindowsApplication"/>, <see cref="OutputKind.WindowsRuntimeApplication"/>)
/// are the same method (Main). A non-executable program has no entry point. Runtimes that implement a custom loader may specify debug entry-point
/// to force the debugger to skip over complex custom loader logic executing at the beginning of the .exe and thus improve debugging experience.
///
/// Unlike ordinary entry-point which is limited to a non-generic static method of specific signature, there are no restrictions on the <paramref name="debugEntryPoint"/>
/// method other than having a method body (extern, interface, or abstract methods are not allowed).
/// </param>
/// <param name="sourceLinkStream">
/// Stream containing information linking the compilation to a source control.
/// </param>
/// <param name="embeddedTexts">
/// Texts to embed in the PDB.
/// Only supported when emitting Portable PDBs.
/// </param>
/// <param name="cancellationToken">To cancel the emit process.</param>
public EmitResult Emit(
Stream peStream,
Stream? pdbStream = null,
Stream? xmlDocumentationStream = null,
Stream? win32Resources = null,
IEnumerable<ResourceDescription>? manifestResources = null,
EmitOptions? options = null,
IMethodSymbol? debugEntryPoint = null,
Stream? sourceLinkStream = null,
IEnumerable<EmbeddedText>? embeddedTexts = null,
Stream? metadataPEStream = null,
CancellationToken cancellationToken = default(CancellationToken))
{
return Emit(
peStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
debugEntryPoint,
sourceLinkStream,
embeddedTexts,
metadataPEStream,
rebuildData: null,
cancellationToken);
}
internal EmitResult Emit(
Stream peStream,
Stream? pdbStream,
Stream? xmlDocumentationStream,
Stream? win32Resources,
IEnumerable<ResourceDescription>? manifestResources,
EmitOptions? options,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
Stream? metadataPEStream,
RebuildData? rebuildData,
CancellationToken cancellationToken)
{
if (peStream == null)
{
throw new ArgumentNullException(nameof(peStream));
}
if (!peStream.CanWrite)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportWrite, nameof(peStream));
}
if (pdbStream != null)
{
if (options?.DebugInformationFormat == DebugInformationFormat.Embedded)
{
throw new ArgumentException(CodeAnalysisResources.PdbStreamUnexpectedWhenEmbedding, nameof(pdbStream));
}
if (!pdbStream.CanWrite)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportWrite, nameof(pdbStream));
}
if (options?.EmitMetadataOnly == true)
{
throw new ArgumentException(CodeAnalysisResources.PdbStreamUnexpectedWhenEmittingMetadataOnly, nameof(pdbStream));
}
}
if (metadataPEStream != null && options?.EmitMetadataOnly == true)
{
throw new ArgumentException(CodeAnalysisResources.MetadataPeStreamUnexpectedWhenEmittingMetadataOnly, nameof(metadataPEStream));
}
if (metadataPEStream != null && options?.IncludePrivateMembers == true)
{
throw new ArgumentException(CodeAnalysisResources.IncludingPrivateMembersUnexpectedWhenEmittingToMetadataPeStream, nameof(metadataPEStream));
}
if (metadataPEStream == null && options?.EmitMetadataOnly == false)
{
// EmitOptions used to default to IncludePrivateMembers=false, so to preserve binary compatibility we silently correct that unless emitting regular assemblies
options = options.WithIncludePrivateMembers(true);
}
if (options?.DebugInformationFormat == DebugInformationFormat.Embedded &&
options?.EmitMetadataOnly == true)
{
throw new ArgumentException(CodeAnalysisResources.EmbeddingPdbUnexpectedWhenEmittingMetadata, nameof(metadataPEStream));
}
if (this.Options.OutputKind == OutputKind.NetModule)
{
if (metadataPEStream != null)
{
throw new ArgumentException(CodeAnalysisResources.CannotTargetNetModuleWhenEmittingRefAssembly, nameof(metadataPEStream));
}
else if (options?.EmitMetadataOnly == true)
{
throw new ArgumentException(CodeAnalysisResources.CannotTargetNetModuleWhenEmittingRefAssembly, nameof(options.EmitMetadataOnly));
}
}
if (win32Resources != null)
{
if (!win32Resources.CanRead || !win32Resources.CanSeek)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportReadAndSeek, nameof(win32Resources));
}
}
if (sourceLinkStream != null && !sourceLinkStream.CanRead)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportRead, nameof(sourceLinkStream));
}
if (embeddedTexts != null &&
!embeddedTexts.IsEmpty() &&
pdbStream == null &&
options?.DebugInformationFormat != DebugInformationFormat.Embedded)
{
throw new ArgumentException(CodeAnalysisResources.EmbeddedTextsRequirePdb, nameof(embeddedTexts));
}
return Emit(
peStream,
metadataPEStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
debugEntryPoint,
sourceLinkStream,
embeddedTexts,
rebuildData,
testData: null,
cancellationToken: cancellationToken);
}
/// <summary>
/// This overload is only intended to be directly called by tests that want to pass <paramref name="testData"/>.
/// The map is used for storing a list of methods and their associated IL.
/// </summary>
internal EmitResult Emit(
Stream peStream,
Stream? metadataPEStream,
Stream? pdbStream,
Stream? xmlDocumentationStream,
Stream? win32Resources,
IEnumerable<ResourceDescription>? manifestResources,
EmitOptions? options,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
RebuildData? rebuildData,
CompilationTestData? testData,
CancellationToken cancellationToken)
{
options = options ?? EmitOptions.Default.WithIncludePrivateMembers(metadataPEStream == null);
bool embedPdb = options.DebugInformationFormat == DebugInformationFormat.Embedded;
Debug.Assert(!embedPdb || pdbStream == null);
Debug.Assert(metadataPEStream == null || !options.IncludePrivateMembers); // you may not use a secondary stream and include private members together
var diagnostics = DiagnosticBag.GetInstance();
var moduleBeingBuilt = CheckOptionsAndCreateModuleBuilder(
diagnostics,
manifestResources,
options,
debugEntryPoint,
sourceLinkStream,
embeddedTexts,
testData,
cancellationToken);
bool success = false;
if (moduleBeingBuilt != null)
{
try
{
success = CompileMethods(
moduleBeingBuilt,
emittingPdb: pdbStream != null || embedPdb,
diagnostics: diagnostics,
filterOpt: null,
cancellationToken: cancellationToken);
if (!options.EmitMetadataOnly)
{
// NOTE: We generate documentation even in presence of compile errors.
// https://github.com/dotnet/roslyn/issues/37996 tracks revisiting this behavior.
if (!GenerateResources(moduleBeingBuilt, win32Resources, useRawWin32Resources: rebuildData is object, diagnostics, cancellationToken) ||
!GenerateDocumentationComments(xmlDocumentationStream, options.OutputNameOverride, diagnostics, cancellationToken))
{
success = false;
}
if (success)
{
ReportUnusedImports(diagnostics, cancellationToken);
}
}
else if (xmlDocumentationStream != null)
{
// If we're in metadata only, and the caller asks for xml docs, then still proceed and generate those.
success = GenerateDocumentationComments(
xmlDocumentationStream, options.OutputNameOverride, diagnostics, cancellationToken);
}
}
finally
{
moduleBeingBuilt.CompilationFinished();
}
RSAParameters? privateKeyOpt = null;
if (Options.StrongNameProvider != null && SignUsingBuilder && !Options.PublicSign)
{
privateKeyOpt = StrongNameKeys.PrivateKey;
}
if (!options.EmitMetadataOnly && CommonCompiler.HasUnsuppressedErrors(diagnostics))
{
success = false;
}
if (success)
{
success = SerializeToPeStream(
moduleBeingBuilt,
new SimpleEmitStreamProvider(peStream),
(metadataPEStream != null) ? new SimpleEmitStreamProvider(metadataPEStream) : null,
(pdbStream != null) ? new SimpleEmitStreamProvider(pdbStream) : null,
rebuildData,
testData?.SymWriterFactory,
diagnostics,
emitOptions: options,
privateKeyOpt: privateKeyOpt,
cancellationToken: cancellationToken);
}
}
return new EmitResult(success, diagnostics.ToReadOnlyAndFree());
}
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Emit the differences between the compilation and the previous generation
/// for Edit and Continue. The differences are expressed as added and changed
/// symbols, and are emitted as metadata, IL, and PDB deltas. A representation
/// of the current compilation is returned as an EmitBaseline for use in a
/// subsequent Edit and Continue.
/// </summary>
[Obsolete("UpdatedMethods is now part of EmitDifferenceResult, so you should use an overload that doesn't take it.")]
public EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable<SemanticEdit> edits,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ICollection<MethodDefinitionHandle> updatedMethods,
CancellationToken cancellationToken = default(CancellationToken))
{
return EmitDifference(baseline, edits, s => false, metadataStream, ilStream, pdbStream, updatedMethods, cancellationToken);
}
/// <summary>
/// Emit the differences between the compilation and the previous generation
/// for Edit and Continue. The differences are expressed as added and changed
/// symbols, and are emitted as metadata, IL, and PDB deltas. A representation
/// of the current compilation is returned as an EmitBaseline for use in a
/// subsequent Edit and Continue.
/// </summary>
[Obsolete("UpdatedMethods is now part of EmitDifferenceResult, so you should use an overload that doesn't take it.")]
public EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable<SemanticEdit> edits,
Func<ISymbol, bool> isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ICollection<MethodDefinitionHandle> updatedMethods,
CancellationToken cancellationToken = default(CancellationToken))
{
var diff = EmitDifference(baseline, edits, isAddedSymbol, metadataStream, ilStream, pdbStream, cancellationToken);
foreach (var token in diff.UpdatedMethods)
{
updatedMethods.Add(token);
}
return diff;
}
/// <summary>
/// Emit the differences between the compilation and the previous generation
/// for Edit and Continue. The differences are expressed as added and changed
/// symbols, and are emitted as metadata, IL, and PDB deltas. A representation
/// of the current compilation is returned as an EmitBaseline for use in a
/// subsequent Edit and Continue.
/// </summary>
public EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable<SemanticEdit> edits,
Func<ISymbol, bool> isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
CancellationToken cancellationToken = default(CancellationToken))
{
if (baseline == null)
{
throw new ArgumentNullException(nameof(baseline));
}
// TODO: check if baseline is an assembly manifest module/netmodule
// Do we support EnC on netmodules?
if (edits == null)
{
throw new ArgumentNullException(nameof(edits));
}
if (isAddedSymbol == null)
{
throw new ArgumentNullException(nameof(isAddedSymbol));
}
if (metadataStream == null)
{
throw new ArgumentNullException(nameof(metadataStream));
}
if (ilStream == null)
{
throw new ArgumentNullException(nameof(ilStream));
}
if (pdbStream == null)
{
throw new ArgumentNullException(nameof(pdbStream));
}
return this.EmitDifference(baseline, edits, isAddedSymbol, metadataStream, ilStream, pdbStream, testData: null, cancellationToken);
}
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
internal abstract EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable<SemanticEdit> edits,
Func<ISymbol, bool> isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
CompilationTestData? testData,
CancellationToken cancellationToken);
/// <summary>
/// Check compilation options and create <see cref="CommonPEModuleBuilder"/>.
/// </summary>
/// <returns><see cref="CommonPEModuleBuilder"/> if successful.</returns>
internal CommonPEModuleBuilder? CheckOptionsAndCreateModuleBuilder(
DiagnosticBag diagnostics,
IEnumerable<ResourceDescription>? manifestResources,
EmitOptions options,
IMethodSymbol? debugEntryPoint,
Stream? sourceLinkStream,
IEnumerable<EmbeddedText>? embeddedTexts,
CompilationTestData? testData,
CancellationToken cancellationToken)
{
options.ValidateOptions(diagnostics, MessageProvider, Options.Deterministic);
if (debugEntryPoint != null)
{
ValidateDebugEntryPoint(debugEntryPoint, diagnostics);
}
if (Options.OutputKind == OutputKind.NetModule && manifestResources != null)
{
foreach (ResourceDescription res in manifestResources)
{
if (res.FileName != null)
{
// Modules can have only embedded resources, not linked ones.
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_ResourceInModule, Location.None));
}
}
}
if (CommonCompiler.HasUnsuppressableErrors(diagnostics))
{
return null;
}
// Do not waste a slot in the submission chain for submissions that contain no executable code
// (they may only contain #r directives, usings, etc.)
if (IsSubmission && !HasCodeToEmit())
{
// Still report diagnostics since downstream submissions will assume there are no errors.
diagnostics.AddRange(this.GetDiagnostics(cancellationToken));
return null;
}
return this.CreateModuleBuilder(
options,
debugEntryPoint,
sourceLinkStream,
embeddedTexts,
manifestResources,
testData,
diagnostics,
cancellationToken);
}
internal abstract void ValidateDebugEntryPoint(IMethodSymbol debugEntryPoint, DiagnosticBag diagnostics);
internal bool IsEmitDeterministic => this.Options.Deterministic;
internal bool SerializeToPeStream(
CommonPEModuleBuilder moduleBeingBuilt,
EmitStreamProvider peStreamProvider,
EmitStreamProvider? metadataPEStreamProvider,
EmitStreamProvider? pdbStreamProvider,
RebuildData? rebuildData,
Func<ISymWriterMetadataProvider, SymUnmanagedWriter>? testSymWriterFactory,
DiagnosticBag diagnostics,
EmitOptions emitOptions,
RSAParameters? privateKeyOpt,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Cci.PdbWriter? nativePdbWriter = null;
DiagnosticBag? metadataDiagnostics = null;
DiagnosticBag? pdbBag = null;
bool deterministic = IsEmitDeterministic;
// PDB Stream provider should not be given if PDB is to be embedded into the PE file:
Debug.Assert(moduleBeingBuilt.DebugInformationFormat != DebugInformationFormat.Embedded || pdbStreamProvider == null);
string? pePdbFilePath = emitOptions.PdbFilePath;
if (moduleBeingBuilt.DebugInformationFormat == DebugInformationFormat.Embedded || pdbStreamProvider != null)
{
pePdbFilePath = pePdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb");
}
else
{
pePdbFilePath = null;
}
if (moduleBeingBuilt.DebugInformationFormat == DebugInformationFormat.Embedded && !RoslynString.IsNullOrEmpty(pePdbFilePath))
{
pePdbFilePath = PathUtilities.GetFileName(pePdbFilePath);
}
EmitStream? emitPeStream = null;
EmitStream? emitMetadataStream = null;
try
{
var signKind = IsRealSigned
? (SignUsingBuilder ? EmitStreamSignKind.SignedWithBuilder : EmitStreamSignKind.SignedWithFile)
: EmitStreamSignKind.None;
emitPeStream = new EmitStream(peStreamProvider, signKind, StrongNameKeys, Options.StrongNameProvider);
emitMetadataStream = metadataPEStreamProvider == null
? null
: new EmitStream(metadataPEStreamProvider, signKind, StrongNameKeys, Options.StrongNameProvider);
metadataDiagnostics = DiagnosticBag.GetInstance();
if (moduleBeingBuilt.DebugInformationFormat == DebugInformationFormat.Pdb && pdbStreamProvider != null)
{
// The algorithm must be specified for deterministic builds (checked earlier).
Debug.Assert(!deterministic || moduleBeingBuilt.PdbChecksumAlgorithm.Name != null);
// The calls ISymUnmanagedWriter2.GetDebugInfo require a file name in order to succeed. This is
// frequently used during PDB writing. Ensure a name is provided here in the case we were given
// only a Stream value.
nativePdbWriter = new Cci.PdbWriter(pePdbFilePath, testSymWriterFactory, deterministic ? moduleBeingBuilt.PdbChecksumAlgorithm : default);
}
Func<Stream?>? getPortablePdbStream =
moduleBeingBuilt.DebugInformationFormat != DebugInformationFormat.PortablePdb || pdbStreamProvider == null
? null
: (Func<Stream?>)(() => ConditionalGetOrCreateStream(pdbStreamProvider, metadataDiagnostics));
try
{
if (SerializePeToStream(
moduleBeingBuilt,
metadataDiagnostics,
MessageProvider,
emitPeStream.GetCreateStreamFunc(MessageProvider, metadataDiagnostics),
emitMetadataStream?.GetCreateStreamFunc(MessageProvider, metadataDiagnostics),
getPortablePdbStream,
nativePdbWriter,
pePdbFilePath,
rebuildData,
emitOptions.EmitMetadataOnly,
emitOptions.IncludePrivateMembers,
deterministic,
emitOptions.InstrumentationKinds.Contains(InstrumentationKind.TestCoverage),
privateKeyOpt,
cancellationToken))
{
if (nativePdbWriter != null)
{
var nativePdbStream = pdbStreamProvider!.GetOrCreateStream(metadataDiagnostics);
Debug.Assert(nativePdbStream != null || metadataDiagnostics.HasAnyErrors());
if (nativePdbStream != null)
{
nativePdbWriter.WriteTo(nativePdbStream);
}
}
}
}
catch (SymUnmanagedWriterException ex)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PdbWritingFailed, Location.None, ex.Message));
return false;
}
catch (Cci.PeWritingException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PeWritingFailure, Location.None, e.InnerException?.ToString() ?? ""));
return false;
}
catch (ResourceException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_CantReadResource, Location.None, e.Message, e.InnerException?.Message ?? ""));
return false;
}
catch (PermissionSetFileReadException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PermissionSetAttributeFileReadError, Location.None, e.FileName, e.PropertyName, e.Message));
return false;
}
// translate metadata errors.
if (!FilterAndAppendAndFreeDiagnostics(diagnostics, ref metadataDiagnostics, cancellationToken))
{
return false;
}
return
emitPeStream.Complete(MessageProvider, diagnostics) &&
(emitMetadataStream?.Complete(MessageProvider, diagnostics) ?? true);
}
finally
{
nativePdbWriter?.Dispose();
emitPeStream?.Close();
emitMetadataStream?.Close();
pdbBag?.Free();
metadataDiagnostics?.Free();
}
}
private static Stream? ConditionalGetOrCreateStream(EmitStreamProvider metadataPEStreamProvider, DiagnosticBag metadataDiagnostics)
{
if (metadataDiagnostics.HasAnyErrors())
{
return null;
}
var auxStream = metadataPEStreamProvider.GetOrCreateStream(metadataDiagnostics);
Debug.Assert(auxStream != null || metadataDiagnostics.HasAnyErrors());
return auxStream;
}
internal static bool SerializePeToStream(
CommonPEModuleBuilder moduleBeingBuilt,
DiagnosticBag metadataDiagnostics,
CommonMessageProvider messageProvider,
Func<Stream?> getPeStream,
Func<Stream?>? getMetadataPeStreamOpt,
Func<Stream?>? getPortablePdbStreamOpt,
Cci.PdbWriter? nativePdbWriterOpt,
string? pdbPathOpt,
RebuildData? rebuildData,
bool metadataOnly,
bool includePrivateMembers,
bool isDeterministic,
bool emitTestCoverageData,
RSAParameters? privateKeyOpt,
CancellationToken cancellationToken)
{
bool includePrivateMembersOnPrimaryOutput = metadataOnly ? includePrivateMembers : true;
bool deterministicPrimaryOutput = (metadataOnly && !includePrivateMembers) || isDeterministic;
if (!Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBeingBuilt, metadataDiagnostics, metadataOnly, includePrivateMembersOnPrimaryOutput, rebuildData: rebuildData),
messageProvider,
getPeStream,
getPortablePdbStreamOpt,
nativePdbWriterOpt,
pdbPathOpt,
metadataOnly,
deterministicPrimaryOutput,
emitTestCoverageData,
privateKeyOpt,
cancellationToken))
{
return false;
}
// produce the secondary output (ref assembly) if needed
if (getMetadataPeStreamOpt is not null)
{
Debug.Assert(!metadataOnly);
Debug.Assert(!includePrivateMembers);
if (!Cci.PeWriter.WritePeToStream(
new EmitContext(moduleBeingBuilt, syntaxNode: null, metadataDiagnostics, metadataOnly: true, includePrivateMembers: false),
messageProvider,
getMetadataPeStreamOpt,
getPortablePdbStreamOpt: null,
nativePdbWriterOpt: null,
pdbPathOpt: null,
metadataOnly: true,
isDeterministic: true,
emitTestCoverageData: false,
privateKeyOpt: privateKeyOpt,
cancellationToken: cancellationToken))
{
return false;
}
}
return true;
}
private protected abstract EmitBaseline MapToCompilation(CommonPEModuleBuilder moduleBeingBuilt);
internal EmitBaseline? SerializeToDeltaStreams(
CommonPEModuleBuilder moduleBeingBuilt,
DefinitionMap definitionMap,
SymbolChanges changes,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ArrayBuilder<MethodDefinitionHandle> updatedMethods,
ArrayBuilder<TypeDefinitionHandle> changedTypes,
DiagnosticBag diagnostics,
Func<ISymWriterMetadataProvider, SymUnmanagedWriter>? testSymWriterFactory,
string? pdbFilePath,
CancellationToken cancellationToken)
{
var nativePdbWriter = (moduleBeingBuilt.DebugInformationFormat != DebugInformationFormat.Pdb) ? null :
new Cci.PdbWriter(
pdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"),
testSymWriterFactory,
hashAlgorithmNameOpt: default);
using (nativePdbWriter)
{
var context = new EmitContext(moduleBeingBuilt, diagnostics, metadataOnly: false, includePrivateMembers: true);
var deletedMethodDefs = DeltaMetadataWriter.CreateDeletedMethodsDefs(context, changes);
// Map the definitions from the previous compilation to the current compilation.
// This must be done after compiling since synthesized definitions (generated when compiling method bodies)
// may be required. Must also be done after determining deleted method definitions
// since doing so may synthesize HotReloadException symbol.
var baseline = MapToCompilation(moduleBeingBuilt);
var encId = Guid.NewGuid();
try
{
var writer = new DeltaMetadataWriter(
context,
MessageProvider,
baseline,
encId,
definitionMap,
changes,
deletedMethodDefs,
cancellationToken);
moduleBeingBuilt.TestData?.SetMetadataWriter(writer);
writer.WriteMetadataAndIL(
nativePdbWriter,
metadataStream,
ilStream,
(nativePdbWriter == null) ? pdbStream : null,
out MetadataSizes metadataSizes);
writer.GetUpdatedMethodTokens(updatedMethods);
writer.GetChangedTypeTokens(changedTypes);
nativePdbWriter?.WriteTo(pdbStream);
return diagnostics.HasAnyErrors() ? null : writer.GetDelta(this, encId, metadataSizes);
}
catch (SymUnmanagedWriterException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PdbWritingFailed, Location.None, e.Message));
return null;
}
catch (Cci.PeWritingException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PeWritingFailure, Location.None, e.InnerException?.ToString() ?? ""));
return null;
}
catch (PermissionSetFileReadException e)
{
diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PermissionSetAttributeFileReadError, Location.None, e.FileName, e.PropertyName, e.Message));
return null;
}
finally
{
foreach (var (_, builder) in deletedMethodDefs)
{
builder.Free();
}
}
}
}
internal string? Feature(string p)
{
string? v;
return _features.TryGetValue(p, out v) ? v : null;
}
#endregion
#region Features
/// <summary>
/// False when the "debug-analyzers" feature flag is set.
/// When that flag is set, the compiler will not catch exceptions from analyzer execution to allow creating dumps.
/// </summary>
internal bool CatchAnalyzerExceptions => Feature("debug-analyzers") == null;
#endregion
private ConcurrentDictionary<SyntaxTree, SmallConcurrentSetOfInts>? _lazyTreeToUsedImportDirectivesMap;
private static readonly Func<SyntaxTree, SmallConcurrentSetOfInts> s_createSetCallback = t => new SmallConcurrentSetOfInts();
private ConcurrentDictionary<SyntaxTree, SmallConcurrentSetOfInts> TreeToUsedImportDirectivesMap
{
get
{
return RoslynLazyInitializer.EnsureInitialized(ref _lazyTreeToUsedImportDirectivesMap);
}
}
internal void MarkImportDirectiveAsUsed(SyntaxReference node)
{
MarkImportDirectiveAsUsed(node.SyntaxTree, node.Span.Start);
}
internal void MarkImportDirectiveAsUsed(SyntaxTree? syntaxTree, int position)
{
// Optimization: Don't initialize TreeToUsedImportDirectivesMap in submissions.
if (!IsSubmission && syntaxTree != null)
{
var set = TreeToUsedImportDirectivesMap.GetOrAdd(syntaxTree, s_createSetCallback);
set.Add(position);
}
}
internal bool IsImportDirectiveUsed(SyntaxTree syntaxTree, int position)
{
if (IsSubmission)
{
// Since usings apply to subsequent submissions, we have to assume they are used.
return true;
}
SmallConcurrentSetOfInts? usedImports;
return syntaxTree != null &&
TreeToUsedImportDirectivesMap.TryGetValue(syntaxTree, out usedImports) &&
usedImports.Contains(position);
}
/// <summary>
/// The compiler needs to define an ordering among different partial class in different syntax trees
/// in some cases, because emit order for fields in structures, for example, is semantically important.
/// This function defines an ordering among syntax trees in this compilation.
/// </summary>
internal int CompareSyntaxTreeOrdering(SyntaxTree tree1, SyntaxTree tree2)
{
if (tree1 == tree2)
{
return 0;
}
Debug.Assert(this.ContainsSyntaxTree(tree1));
Debug.Assert(this.ContainsSyntaxTree(tree2));
return this.GetSyntaxTreeOrdinal(tree1) - this.GetSyntaxTreeOrdinal(tree2);
}
internal abstract int GetSyntaxTreeOrdinal(SyntaxTree tree);
/// <summary>
/// Compare two source locations, using their containing trees, and then by Span.First within a tree.
/// Can be used to get a total ordering on declarations, for example.
/// </summary>
internal abstract int CompareSourceLocations(Location loc1, Location loc2);
/// <summary>
/// Compare two source locations, using their containing trees, and then by Span.First within a tree.
/// Can be used to get a total ordering on declarations, for example.
/// </summary>
internal abstract int CompareSourceLocations(SyntaxReference loc1, SyntaxReference loc2);
/// <summary>
/// Compare two source locations, using their containing trees, and then by Span.First within a tree.
/// Can be used to get a total ordering on declarations, for example.
/// </summary>
internal abstract int CompareSourceLocations(SyntaxNode loc1, SyntaxNode loc2);
/// <summary>
/// Return the lexically first of two locations.
/// </summary>
internal TLocation FirstSourceLocation<TLocation>(TLocation first, TLocation second)
where TLocation : Location
{
if (CompareSourceLocations(first, second) <= 0)
{
return first;
}
else
{
return second;
}
}
/// <summary>
/// Return the lexically first of multiple locations.
/// </summary>
internal TLocation? FirstSourceLocation<TLocation>(ImmutableArray<TLocation> locations)
where TLocation : Location
{
if (locations.IsEmpty)
{
return null;
}
var result = locations[0];
for (int i = 1; i < locations.Length; i++)
{
result = FirstSourceLocation(result, locations[i]);
}
return result;
}
#region Logging Helpers
// Following helpers are used when logging ETW events. These helpers are invoked only if we are running
// under an ETW listener that has requested 'verbose' logging. In other words, these helpers will never
// be invoked in the 'normal' case (i.e. when the code is running on user's machine and no ETW listener
// is involved).
// Note: Most of the below helpers are unused at the moment - but we would like to keep them around in
// case we decide we need more verbose logging in certain cases for debugging.
internal string GetMessage(CompilationStage stage)
{
return string.Format("{0} ({1})", this.AssemblyName, stage.ToString());
}
internal string GetMessage(ITypeSymbol source, ITypeSymbol destination)
{
if (source == null || destination == null) return this.AssemblyName ?? "";
return string.Format("{0}: {1} {2} -> {3} {4}", this.AssemblyName, source.TypeKind.ToString(), source.Name, destination.TypeKind.ToString(), destination.Name);
}
#endregion
#region Declaration Name Queries
/// <summary>
/// Return true if there is a source declaration symbol name that meets given predicate.
/// </summary>
public abstract bool ContainsSymbolsWithName(Func<string, bool> predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Return source declaration symbols whose name meets given predicate.
/// </summary>
public abstract IEnumerable<ISymbol> GetSymbolsWithName(Func<string, bool> predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken));
#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters
/// <summary>
/// Return true if there is a source declaration symbol name that matches the provided name.
/// This may be faster than <see cref="ContainsSymbolsWithName(Func{string, bool},
/// SymbolFilter, CancellationToken)"/> when predicate is just a simple string check.
/// <paramref name="name"/> is case sensitive or not depending on the target language.
/// </summary>
public abstract bool ContainsSymbolsWithName(string name, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Return source declaration symbols whose name matches the provided name. This may be
/// faster than <see cref="GetSymbolsWithName(Func{string, bool}, SymbolFilter,
/// CancellationToken)"/> when predicate is just a simple string check. <paramref
/// name="name"/> is case sensitive or not depending on the target language.
/// </summary>
public abstract IEnumerable<ISymbol> GetSymbolsWithName(string name, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken));
#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters
#endregion
internal void MakeMemberMissing(WellKnownMember member)
{
MakeMemberMissing((int)member);
}
internal void MakeMemberMissing(SpecialMember member)
{
MakeMemberMissing(-(int)member - 1);
}
internal bool IsMemberMissing(WellKnownMember member)
{
return IsMemberMissing((int)member);
}
internal bool IsMemberMissing(SpecialMember member)
{
return IsMemberMissing(-(int)member - 1);
}
private void MakeMemberMissing(int member)
{
if (_lazyMakeMemberMissingMap == null)
{
_lazyMakeMemberMissingMap = new SmallDictionary<int, bool>();
}
_lazyMakeMemberMissingMap[member] = true;
}
private bool IsMemberMissing(int member)
{
return _lazyMakeMemberMissingMap != null && _lazyMakeMemberMissingMap.ContainsKey(member);
}
internal void MakeTypeMissing(ExtendedSpecialType type)
{
MakeTypeMissing((int)type);
}
internal void MakeTypeMissing(WellKnownType type)
{
MakeTypeMissing((int)type);
}
private void MakeTypeMissing(int type)
{
if (_lazyMakeWellKnownTypeMissingMap == null)
{
_lazyMakeWellKnownTypeMissingMap = new SmallDictionary<int, bool>();
}
_lazyMakeWellKnownTypeMissingMap[(int)type] = true;
}
internal bool IsTypeMissing(ExtendedSpecialType type)
{
return IsTypeMissing((int)type);
}
internal bool IsTypeMissing(WellKnownType type)
{
return IsTypeMissing((int)type);
}
private bool IsTypeMissing(int type)
{
return _lazyMakeWellKnownTypeMissingMap != null && _lazyMakeWellKnownTypeMissingMap.ContainsKey((int)type);
}
/// <summary>
/// Given a <see cref="Diagnostic"/> reporting unreferenced <see cref="AssemblyIdentity"/>s, returns
/// the actual <see cref="AssemblyIdentity"/> instances that were not referenced.
/// </summary>
public ImmutableArray<AssemblyIdentity> GetUnreferencedAssemblyIdentities(Diagnostic diagnostic)
{
if (diagnostic == null)
{
throw new ArgumentNullException(nameof(diagnostic));
}
if (!IsUnreferencedAssemblyIdentityDiagnosticCode(diagnostic.Code))
{
return ImmutableArray<AssemblyIdentity>.Empty;
}
using var builder = TemporaryArray<AssemblyIdentity>.Empty;
foreach (var argument in diagnostic.Arguments)
{
if (argument is AssemblyIdentity id)
{
builder.Add(id);
}
}
return builder.ToImmutableAndClear();
}
internal abstract bool IsUnreferencedAssemblyIdentityDiagnosticCode(int code);
/// <summary>
/// Returns the required language version found in a <see cref="Diagnostic"/>, if any is found.
/// Returns null if none is found.
/// </summary>
public static string? GetRequiredLanguageVersion(Diagnostic diagnostic)
{
if (diagnostic == null)
{
throw new ArgumentNullException(nameof(diagnostic));
}
bool found = false;
string? foundVersion = null;
if (diagnostic.Arguments != null)
{
foreach (var argument in diagnostic.Arguments)
{
if (argument is RequiredLanguageVersion versionDiagnostic)
{
Debug.Assert(!found); // only one required language version in a given diagnostic
found = true;
foundVersion = versionDiagnostic.ToString();
}
}
}
return foundVersion;
}
/// <summary>
/// Determines whether the runtime this <see cref="Compilation"/> is targeting supports a particular capability.
/// </summary>
/// <remarks>Returns <see langword="false"/> if an unknown capability is passed in.</remarks>
public bool SupportsRuntimeCapability(RuntimeCapability capability)
=> SupportsRuntimeCapabilityCore(capability);
private protected abstract bool SupportsRuntimeCapabilityCore(RuntimeCapability capability);
}
}
|