// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
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.Symbols;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis
{
///
/// 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.
///
public abstract partial class Compilation
{
// Inverse of syntaxTrees array (i.e. maps tree to index)
internal readonly ImmutableDictionary syntaxTreeOrdinalMap;
///
/// 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).
///
public abstract bool IsCaseSensitive { get; }
///
/// Used for test purposes only to emulate missing members.
///
private SmallDictionary _lazyMakeWellKnownTypeMissingMap;
///
/// Used for test purposes only to emulate missing members.
///
private SmallDictionary _lazyMakeMemberMissingMap;
private Dictionary _lazyFeatures;
internal Compilation(
string name,
ImmutableArray references,
Type submissionReturnType,
Type hostObjectType,
bool isSubmission,
ImmutableDictionary syntaxTreeOrdinalMap,
AsyncQueue eventQueue)
{
Debug.Assert(!references.IsDefault);
this.AssemblyName = name;
this.ExternalReferences = references;
this.syntaxTreeOrdinalMap = syntaxTreeOrdinalMap;
this.EventQueue = eventQueue;
if (isSubmission)
{
_lazySubmissionSlotIndex = SubmissionSlotIndexToBeAllocated;
this.SubmissionReturnType = submissionReturnType ?? typeof(object);
this.HostObjectType = hostObjectType;
}
else
{
_lazySubmissionSlotIndex = SubmissionSlotIndexNotApplicable;
}
}
internal abstract AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager, CancellationToken cancellationToken);
///
/// Gets the source language ("C#" or "Visual Basic").
///
public abstract string Language { get; }
internal static void ValidateSubmissionParameters(Compilation previousSubmission, Type returnType, ref Type hostObjectType)
{
if (hostObjectType != null && !IsValidHostObjectType(hostObjectType))
{
throw new ArgumentException(CodeAnalysisResources.ReturnTypeCannotBeValuePointerbyRefOrOpen, nameof(hostObjectType));
}
if (returnType != null && !IsValidSubmissionReturnType(returnType))
{
throw new ArgumentException(CodeAnalysisResources.ReturnTypeCannotBeVoidByRefOrOpen, nameof(returnType));
}
if (previousSubmission != null)
{
if (hostObjectType == null)
{
hostObjectType = previousSubmission.HostObjectType;
}
else if (hostObjectType != previousSubmission.HostObjectType)
{
throw new ArgumentException(CodeAnalysisResources.TypeMustBeSameAsHostObjectTypeOfPreviousSubmission, nameof(hostObjectType));
}
// Force the previous submission to be analyzed. This is required for anonymous types unification.
if (previousSubmission.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
{
throw new InvalidOperationException(CodeAnalysisResources.PreviousSubmissionHasErrors);
}
}
}
///
/// Checks options passed to submission compilation constructor.
/// Throws an exception if the options are not applicable to submissions.
///
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)
{
throw new ArgumentException(CodeAnalysisResources.InvalidCompilationOptions, nameof(options));
}
}
///
/// Creates a new compilation equivalent to this one with different symbol instances.
///
public Compilation Clone()
{
return CommonClone();
}
protected abstract Compilation CommonClone();
///
/// Returns a new compilation with a given event queue.
///
internal abstract Compilation WithEventQueue(AsyncQueue eventQueue);
///
/// Gets a new for the specified syntax tree.
///
/// The specificed syntax tree.
///
/// True if the SemanticModel should ignore accessibility rules when answering semantic questions.
///
public SemanticModel GetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility = false)
{
return CommonGetSemanticModel(syntaxTree, ignoreAccessibility);
}
protected abstract SemanticModel CommonGetSemanticModel(SyntaxTree syntaxTree, bool ignoreAccessibility);
///
/// Returns a new INamedTypeSymbol representing an error type with the given name and arity
/// in the given optional container.
///
public abstract INamedTypeSymbol CreateErrorTypeSymbol(INamespaceOrTypeSymbol container, string name, int arity);
#region Name
internal const string UnspecifiedModuleAssemblyName = "?";
///
/// Simple assembly name, or null if not specified.
///
///
/// The name is used for determining internals-visible-to relationship with referenced assemblies.
///
/// If the compilation represents an assembly the value of is its simple name.
///
/// Unless specifies otherwise the module name
/// written to metadata is with an extension based upon .
///
public string AssemblyName { get; }
internal static void CheckAssemblyName(string assemblyName)
{
// We could only allow name == null if OutputKind is Module.
// However we couldn't check such condition here since one wouldn't be able to call WithName(...).WithOptions(...).
// It does no harm that we allow name == null for assemblies as well, so we don't enforce it.
if (assemblyName != null)
{
MetadataHelpers.ValidateAssemblyOrModuleName(assemblyName, "assemblyName");
}
}
internal string MakeSourceAssemblySimpleName()
{
return AssemblyName ?? UnspecifiedModuleAssemblyName;
}
internal string MakeSourceModuleName()
{
return Options.ModuleName ??
(AssemblyName != null ? AssemblyName + Options.OutputKind.GetDefaultExtension() : UnspecifiedModuleAssemblyName);
}
///
/// Creates a compilation with the specified assembly name.
///
/// The new assembly name.
/// A new compilation.
public Compilation WithAssemblyName(string assemblyName)
{
return CommonWithAssemblyName(assemblyName);
}
protected abstract Compilation CommonWithAssemblyName(string outputName);
#endregion
#region Options
///
/// Gets the options the compilation was created with.
///
public CompilationOptions Options { get { return CommonOptions; } }
protected abstract CompilationOptions CommonOptions { get; }
///
/// Creates a new compilation with the specified compilation options.
///
/// The new options.
/// A new compilation.
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;
///
/// True if the compilation represents an interactive submission.
///
internal bool IsSubmission
{
get
{
return _lazySubmissionSlotIndex != SubmissionSlotIndexNotApplicable;
}
}
///
/// Gets or allocates a runtime submission slot index for this compilation.
///
/// Non-negative integer if this is a submission and it or a previous submission contains code, negative integer otherwise.
internal int GetSubmissionSlotIndex()
{
if (_lazySubmissionSlotIndex == SubmissionSlotIndexToBeAllocated)
{
// TODO (tomat): remove recursion
int lastSlotIndex = PreviousSubmission?.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.
///
/// The type object that represents the type of submission result the host requested.
///
internal Type SubmissionReturnType { get; }
internal static bool IsValidSubmissionReturnType(Type type)
{
return !(type == typeof(void) || type.IsByRef || type.GetTypeInfo().ContainsGenericParameters);
}
///
/// The type of the host object or null if not specified for this compilation.
///
internal Type HostObjectType { get; }
internal static bool IsValidHostObjectType(Type type)
{
var info = type.GetTypeInfo();
return !(info.IsValueType || info.IsPointer || info.IsByRef || info.ContainsGenericParameters);
}
///
/// Returns the type of the submission return value.
///
///
/// True if the submission has a return value, i.e. if the submission
/// ends with an expression statement.
///
///
/// The compilation doesn't represent a submission
/// ( return false).
///
///
/// Null if the type of the last expression is unknown,
/// if the type of the last expression statement is
/// void or if the submission is not an expression statement, or
/// otherwise the type of the last expression.
///
///
/// Note that the return type is if the last
/// statement is a non-expression statement e.g.,
/// System.Console.WriteLine();
/// and if the statement is an expression statement of type void e.g,
/// System.Console.WriteLine(). However,
/// is false in the former case and true
/// in the latter.
///
public ITypeSymbol GetSubmissionResultType(out bool hasValue)
{
return CommonGetSubmissionResultType(out hasValue);
}
protected abstract ITypeSymbol CommonGetSubmissionResultType(out bool hasValue);
///
/// The previous submission compilation, or null if either this
/// compilation doesn't represent a submission or the submission is the
/// first submission in a submission chain.
///
public Compilation PreviousSubmission { get { return CommonPreviousSubmission; } }
protected abstract Compilation CommonPreviousSubmission { get; }
///
/// Returns a new compilation with the given compilation set as the
/// previous submission.
///
public Compilation WithPreviousSubmission(Compilation newPreviousSubmission)
{
return CommonWithPreviousSubmission(newPreviousSubmission);
}
protected abstract Compilation CommonWithPreviousSubmission(Compilation newPreviousSubmission);
#endregion
#region Syntax Trees
///
/// Gets the syntax trees (parsed from source code) that this compilation was created with.
///
public IEnumerable SyntaxTrees { get { return CommonSyntaxTrees; } }
protected abstract IEnumerable CommonSyntaxTrees { get; }
///
/// Creates a new compilation with additional syntax trees.
///
/// The new syntax trees.
/// A new compilation.
public Compilation AddSyntaxTrees(params SyntaxTree[] trees)
{
return CommonAddSyntaxTrees(trees);
}
///
/// Creates a new compilation with additional syntax trees.
///
/// The new syntax trees.
/// A new compilation.
public Compilation AddSyntaxTrees(IEnumerable trees)
{
return CommonAddSyntaxTrees(trees);
}
protected abstract Compilation CommonAddSyntaxTrees(IEnumerable trees);
///
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
///
/// The new syntax trees.
/// A new compilation.
public Compilation RemoveSyntaxTrees(params SyntaxTree[] trees)
{
return CommonRemoveSyntaxTrees(trees);
}
///
/// Creates a new compilation without the specified syntax trees. Preserves metadata info for use with trees
/// added later.
///
/// The new syntax trees.
/// A new compilation.
public Compilation RemoveSyntaxTrees(IEnumerable trees)
{
return CommonRemoveSyntaxTrees(trees);
}
protected abstract Compilation CommonRemoveSyntaxTrees(IEnumerable trees);
///
/// Creates a new compilation without any syntax trees. Preserves metadata info for use with
/// trees added later.
///
public Compilation RemoveAllSyntaxTrees()
{
return CommonRemoveAllSyntaxTrees();
}
protected abstract Compilation CommonRemoveAllSyntaxTrees();
///
/// Creates a new compilation with an old syntax tree replaced with a new syntax tree.
/// Reuses metadata from old compilation object.
///
/// The new tree.
/// The old tree.
/// A new compilation.
public Compilation ReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree)
{
return CommonReplaceSyntaxTree(oldTree, newTree);
}
protected abstract Compilation CommonReplaceSyntaxTree(SyntaxTree oldTree, SyntaxTree newTree);
///
/// Returns true if this compilation contains the specified tree. False otherwise.
///
/// A syntax tree.
public bool ContainsSyntaxTree(SyntaxTree syntaxTree)
{
return CommonContainsSyntaxTree(syntaxTree);
}
protected abstract bool CommonContainsSyntaxTree(SyntaxTree syntaxTree);
///
/// The event queue that this compilation was created with.
///
internal readonly AsyncQueue EventQueue;
#endregion
#region References
internal static ImmutableArray ValidateReferences(IEnumerable 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("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("Reference of type '{0}' is not valid for this compilation.", reference.GetType()), "references[" + i + "]");
}
}
return result;
}
internal CommonReferenceManager GetBoundReferenceManager()
{
return CommonGetBoundReferenceManager();
}
internal abstract CommonReferenceManager CommonGetBoundReferenceManager();
///
/// Metadata references passed to the compilation constructor.
///
public ImmutableArray ExternalReferences { get; }
///
/// Unique metadata references specified via #r directive in the source code of this compilation.
///
public abstract ImmutableArray DirectiveReferences { get; }
///
/// All reference directives used in this compilation.
///
internal abstract IEnumerable ReferenceDirectives { get; }
///
/// Maps values of #r references to resolved metadata references.
///
internal abstract IDictionary ReferenceDirectiveMap { get; }
///
/// All metadata references -- references passed to the compilation
/// constructor as well as references specified via #r directives.
///
public IEnumerable References
{
get
{
foreach (var reference in ExternalReferences)
{
yield return reference;
}
foreach (var reference in DirectiveReferences)
{
yield return reference;
}
}
}
///
/// Creates a metadata reference for this compilation.
///
///
/// Optional aliases that can be used to refer to the compilation root namespace via extern alias directive.
///
///
/// Embed the COM types from the reference so that the compiled
/// application no longer requires a primary interop assembly (PIA).
///
public abstract CompilationReference ToMetadataReference(ImmutableArray aliases = default(ImmutableArray), bool embedInteropTypes = false);
///
/// Creates a new compilation with the specified references.
///
///
/// The new references.
///
/// A new compilation.
public Compilation WithReferences(IEnumerable newReferences)
{
return this.CommonWithReferences(newReferences);
}
///
/// Creates a new compilation with the specified references.
///
/// The new references.
/// A new compilation.
public Compilation WithReferences(params MetadataReference[] newReferences)
{
return this.WithReferences((IEnumerable)newReferences);
}
///
/// Creates a new compilation with the specified references.
///
protected abstract Compilation CommonWithReferences(IEnumerable newReferences);
///
/// Creates a new compilation with additional metadata references.
///
/// The new references.
/// A new compilation.
public Compilation AddReferences(params MetadataReference[] references)
{
return AddReferences((IEnumerable)references);
}
///
/// Creates a new compilation with additional metadata references.
///
/// The new references.
/// A new compilation.
public Compilation AddReferences(IEnumerable references)
{
if (references == null)
{
throw new ArgumentNullException(nameof(references));
}
if (references.IsEmpty())
{
return this;
}
return CommonWithReferences(this.ExternalReferences.Union(references));
}
///
/// Creates a new compilation without the specified metadata references.
///
/// The new references.
/// A new compilation.
public Compilation RemoveReferences(params MetadataReference[] references)
{
return RemoveReferences((IEnumerable)references);
}
///
/// Creates a new compilation without the specified metadata references.
///
/// The new references.
/// A new compilation.
public Compilation RemoveReferences(IEnumerable references)
{
if (references == null)
{
throw new ArgumentNullException(nameof(references));
}
if (references.IsEmpty())
{
return this;
}
var refSet = new HashSet(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($"MetadataReference '{r}' not found to remove", nameof(references));
}
}
return CommonWithReferences(refSet);
}
///
/// Creates a new compilation without any metadata references.
///
public Compilation RemoveAllReferences()
{
return CommonWithReferences(SpecializedCollections.EmptyEnumerable());
}
///
/// Creates a new compilation with an old metadata reference replaced with a new metadata
/// reference.
///
/// The new reference.
/// The old reference.
/// A new compilation.
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);
}
///
/// Gets the or for a metadata reference used to create this
/// compilation.
///
/// The target reference.
///
/// Assembly or module symbol corresponding to the given reference or null if there is none.
///
public ISymbol GetAssemblyOrModuleSymbol(MetadataReference reference)
{
return CommonGetAssemblyOrModuleSymbol(reference);
}
protected abstract ISymbol CommonGetAssemblyOrModuleSymbol(MetadataReference reference);
///
/// Gets the that corresponds to the assembly symbol.
///
/// The target symbol.
public MetadataReference GetMetadataReference(IAssemblySymbol assemblySymbol)
{
return CommonGetMetadataReference(assemblySymbol);
}
protected abstract MetadataReference CommonGetMetadataReference(IAssemblySymbol assemblySymbol);
///
/// Assembly identities of all assemblies directly referenced by this compilation.
///
///
/// Includes identities of references passed in the compilation constructor
/// as well as those specified via directives in source code.
///
public abstract IEnumerable ReferencedAssemblyNames { get; }
#endregion
#region Symbols
///
/// The that represents the assembly being created.
///
public IAssemblySymbol Assembly { get { return CommonAssembly; } }
protected abstract IAssemblySymbol CommonAssembly { get; }
///
/// Gets the for the module being created by compiling all of
/// the source code.
///
public IModuleSymbol SourceModule { get { return CommonSourceModule; } }
protected abstract IModuleSymbol CommonSourceModule { get; }
///
/// The root namespace that contains all namespaces and types defined in source code or in
/// referenced metadata, merged into a single namespace hierarchy.
///
public INamespaceSymbol GlobalNamespace { get { return CommonGlobalNamespace; } }
protected abstract INamespaceSymbol CommonGlobalNamespace { get; }
///
/// Gets the corresponding compilation namespace for the specified module or assembly namespace.
///
public INamespaceSymbol GetCompilationNamespace(INamespaceSymbol namespaceSymbol)
{
return CommonGetCompilationNamespace(namespaceSymbol);
}
protected abstract INamespaceSymbol CommonGetCompilationNamespace(INamespaceSymbol namespaceSymbol);
internal abstract CommonAnonymousTypeManager CommonAnonymousTypeManager { get; }
///
/// Returns the Main method that will serves as the entry point of the assembly, if it is
/// executable (and not a script).
///
public IMethodSymbol GetEntryPoint(CancellationToken cancellationToken)
{
return CommonGetEntryPoint(cancellationToken);
}
protected abstract IMethodSymbol CommonGetEntryPoint(CancellationToken cancellationToken);
///
/// Get the symbol for the predefined type from the Cor Library referenced by this
/// compilation.
///
public INamedTypeSymbol GetSpecialType(SpecialType specialType)
{
return CommonGetSpecialType(specialType);
}
///
/// Returns true if the type is System.Type.
///
internal abstract bool IsSystemTypeReference(ITypeSymbol type);
protected abstract INamedTypeSymbol CommonGetSpecialType(SpecialType specialType);
internal abstract ISymbol CommonGetWellKnownTypeMember(WellKnownMember member);
///
/// Returns true if the specified type is equal to or derives from System.Attribute well-known type.
///
internal abstract bool IsAttributeType(ITypeSymbol type);
///
/// The INamedTypeSymbol for the .NET System.Object type, which could have a TypeKind of
/// Error if there was no COR Library in this Compilation.
///
public INamedTypeSymbol ObjectType { get { return CommonObjectType; } }
protected abstract INamedTypeSymbol CommonObjectType { get; }
///
/// The TypeSymbol for the type 'dynamic' in this Compilation.
///
public ITypeSymbol DynamicType { get { return CommonDynamicType; } }
protected abstract ITypeSymbol CommonDynamicType { get; }
///
/// A symbol representing the implicit Script class. This is null if the class is not
/// defined in the compilation.
///
public INamedTypeSymbol ScriptClass { get { return CommonScriptClass; } }
protected abstract INamedTypeSymbol CommonScriptClass { get; }
///
/// Returns a new ArrayTypeSymbol representing an array type tied to the base types of the
/// COR Library in this Compilation.
///
public IArrayTypeSymbol CreateArrayTypeSymbol(ITypeSymbol elementType, int rank = 1)
{
return CommonCreateArrayTypeSymbol(elementType, rank);
}
protected abstract IArrayTypeSymbol CommonCreateArrayTypeSymbol(ITypeSymbol elementType, int rank);
///
/// Returns a new PointerTypeSymbol representing a pointer type tied to a type in this
/// Compilation.
///
public IPointerTypeSymbol CreatePointerTypeSymbol(ITypeSymbol pointedAtType)
{
return CommonCreatePointerTypeSymbol(pointedAtType);
}
protected abstract IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol elementType);
///
/// 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.
///
/// Null if the type can't be found.
///
/// Since VB does not have the concept of extern aliases, it considers all referenced assemblies.
///
public INamedTypeSymbol GetTypeByMetadataName(string fullyQualifiedMetadataName)
{
return CommonGetTypeByMetadataName(fullyQualifiedMetadataName);
}
protected abstract INamedTypeSymbol CommonGetTypeByMetadataName(string metadataName);
#endregion
#region Diagnostics
internal static readonly CompilationStage DefaultDiagnosticsStage = CompilationStage.Compile;
///
/// Gets the diagnostics produced during the parsing stage.
///
public abstract ImmutableArray GetParseDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
///
/// Gets the diagnostics produced during symbol declaration.
///
public abstract ImmutableArray GetDeclarationDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
///
/// Gets the diagnostics produced during the analysis of method bodies and field initializers.
///
public abstract ImmutableArray GetMethodBodyDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
///
/// Gets all the diagnostics for the compilation, including syntax, declaration, and
/// binding. Does not include any diagnostics that might be produced during emit, see
/// .
///
public abstract ImmutableArray GetDiagnostics(CancellationToken cancellationToken = default(CancellationToken));
internal abstract CommonMessageProvider MessageProvider { get; }
/// Bag to which filtered diagnostics will be added.
/// Diagnostics to be filtered.
/// True if there were no errors or warnings-as-errors.
internal abstract bool FilterAndAppendAndFreeDiagnostics(DiagnosticBag accumulator, ref DiagnosticBag incoming);
///
/// Modifies the incoming diagnostic, for example escalating its severity, or discarding it (returning null).
///
///
/// The modified diagnostic, or null
internal abstract Diagnostic FilterDiagnostic(Diagnostic diagnostic);
#endregion
#region Resources
///
/// Create a stream filled with default win32 resources.
///
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 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 MakeWin32ResourceList(Stream win32Resources, DiagnosticBag diagnostics)
{
if (win32Resources == null)
{
return null;
}
List 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();
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 ReportManifestResourceDuplicates(
IEnumerable manifestResources,
IEnumerable addedModuleNames,
IEnumerable addedModuleResourceNames,
DiagnosticBag diagnostics)
{
if (Options.OutputKind == OutputKind.NetModule && !(manifestResources != null && manifestResources.Any()))
{
return;
}
var uniqueResourceNames = new HashSet();
if (manifestResources != null && manifestResources.Any())
{
var uniqueFileNames = new HashSet(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
///
/// Constructs the module serialization properties out of the compilation options of this compilation.
///
internal ModulePropertiesForSerialization ConstructModuleSerializationProperties(
EmitOptions emitOptions,
string targetRuntimeVersion,
Guid moduleVersionId = default(Guid))
{
CompilationOptions compilationOptions = this.Options;
Platform platform = compilationOptions.Platform;
if (!platform.IsValid())
{
platform = Platform.AnyCpu;
}
bool requires64bits = platform.Requires64Bit();
ushort fileAlignment;
if (emitOptions.FileAlignment == 0 || !CompilationOptions.IsValidFileAlignment(emitOptions.FileAlignment))
{
fileAlignment = requires64bits
? ModulePropertiesForSerialization.DefaultFileAlignment64Bit
: ModulePropertiesForSerialization.DefaultFileAlignment32Bit;
}
else
{
fileAlignment = (ushort)emitOptions.FileAlignment;
}
ulong baseAddress = unchecked(emitOptions.BaseAddress + 0x8000) & (requires64bits ? 0xffffffffffff0000 : 0x00000000ffff0000);
// cover values smaller than 0x8000, overflow and default value 0):
if (baseAddress == 0)
{
OutputKind outputKind = compilationOptions.OutputKind;
if (outputKind == OutputKind.ConsoleApplication ||
outputKind == OutputKind.WindowsApplication ||
outputKind == OutputKind.WindowsRuntimeApplication)
{
baseAddress = (requires64bits) ? ModulePropertiesForSerialization.DefaultExeBaseAddress64Bit : ModulePropertiesForSerialization.DefaultExeBaseAddress32Bit;
}
else
{
baseAddress = (requires64bits) ? ModulePropertiesForSerialization.DefaultDllBaseAddress64Bit : ModulePropertiesForSerialization.DefaultDllBaseAddress32Bit;
}
}
ulong sizeOfHeapCommit = requires64bits
? ModulePropertiesForSerialization.DefaultSizeOfHeapCommit64Bit
: ModulePropertiesForSerialization.DefaultSizeOfHeapCommit32Bit;
// Dev10 always uses the default value for 32bit for sizeOfHeapReserve.
// check with link -dump -headers
const ulong sizeOfHeapReserve = ModulePropertiesForSerialization.DefaultSizeOfHeapReserve32Bit;
ulong sizeOfStackReserve = requires64bits
? ModulePropertiesForSerialization.DefaultSizeOfStackReserve64Bit
: ModulePropertiesForSerialization.DefaultSizeOfStackReserve32Bit;
ulong sizeOfStackCommit = requires64bits
? ModulePropertiesForSerialization.DefaultSizeOfStackCommit64Bit
: ModulePropertiesForSerialization.DefaultSizeOfStackCommit32Bit;
SubsystemVersion subsystemVer = (emitOptions.SubsystemVersion.Equals(SubsystemVersion.None) || !emitOptions.SubsystemVersion.IsValid)
? SubsystemVersion.Default(compilationOptions.OutputKind.IsValid() ? compilationOptions.OutputKind : OutputKind.DynamicallyLinkedLibrary, platform)
: emitOptions.SubsystemVersion;
return new ModulePropertiesForSerialization(
persistentIdentifier: moduleVersionId,
fileAlignment: fileAlignment,
targetRuntimeVersion: targetRuntimeVersion,
platform: platform,
trackDebugData: false,
baseAddress: baseAddress,
sizeOfHeapReserve: sizeOfHeapReserve,
sizeOfHeapCommit: sizeOfHeapCommit,
sizeOfStackReserve: sizeOfStackReserve,
sizeOfStackCommit: sizeOfStackCommit,
enableHighEntropyVA: emitOptions.HighEntropyVirtualAddressSpace,
strongNameSigned: HasStrongName,
configureToExecuteInAppContainer: compilationOptions.OutputKind == OutputKind.WindowsRuntimeApplication,
subsystemVersion: subsystemVer);
}
#region Emit
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.OutputKind != OutputKind.NetModule
&& StrongNameKeys.CanSign;
}
}
///
/// Return true if the compilation contains any code or types.
///
internal abstract bool HasCodeToEmit();
internal abstract bool IsDelaySigned { get; }
internal abstract StrongNameKeys StrongNameKeys { get; }
internal abstract CommonPEModuleBuilder CreateModuleBuilder(
EmitOptions emitOptions,
IEnumerable manifestResources,
Func assemblySymbolMapper,
CompilationTestData testData,
DiagnosticBag diagnostics,
CancellationToken cancellationToken);
// TODO: private protected
internal abstract bool CompileImpl(
CommonPEModuleBuilder moduleBuilder,
Stream win32Resources,
Stream xmlDocStream,
bool generateDebugInfo,
DiagnosticBag diagnostics,
Predicate filterOpt,
CancellationToken cancellationToken);
internal bool Compile(
CommonPEModuleBuilder moduleBuilder,
Stream win32Resources,
Stream xmlDocStream,
bool generateDebugInfo,
DiagnosticBag diagnostics,
Predicate filterOpt,
CancellationToken cancellationToken)
{
try
{
return CompileImpl(
moduleBuilder,
win32Resources,
xmlDocStream,
generateDebugInfo,
diagnostics,
filterOpt,
cancellationToken);
}
finally
{
moduleBuilder.CompilationFinished();
}
}
internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken)
{
if (this.GetSubmissionSlotIndex() >= 0 && HasCodeToEmit())
{
if (!this.CommonAnonymousTypeManager.AreTemplatesSealed)
{
var discardedDiagnostics = DiagnosticBag.GetInstance();
var moduleBeingBuilt = this.CreateModuleBuilder(
emitOptions: EmitOptions.Default,
manifestResources: null,
assemblySymbolMapper: null,
testData: null,
diagnostics: discardedDiagnostics,
cancellationToken: cancellationToken);
if (moduleBeingBuilt != null)
{
Compile(
moduleBeingBuilt,
win32Resources: null,
xmlDocStream: null,
generateDebugInfo: false,
diagnostics: discardedDiagnostics,
filterOpt: null,
cancellationToken: cancellationToken);
}
discardedDiagnostics.Free();
}
Debug.Assert(this.CommonAnonymousTypeManager.AreTemplatesSealed);
}
else
{
this.PreviousSubmission?.EnsureAnonymousTypeTemplates(cancellationToken);
}
}
///
/// Emit the IL for the compiled source code into the specified stream.
///
/// Stream to which the compilation will be written.
/// Stream to which the compilation's debug info will be written. Null to forego PDB generation.
/// Stream to which the compilation's XML documentation will be written. Null to forego XML generation.
/// 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.
/// List of the compilation's managed resources. Null to indicate that there are none.
/// Emit options.
/// To cancel the emit process.
public EmitResult Emit(
Stream peStream,
Stream pdbStream = null,
Stream xmlDocumentationStream = null,
Stream win32Resources = null,
IEnumerable manifestResources = null,
EmitOptions options = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (peStream == null)
{
throw new ArgumentNullException(nameof(peStream));
}
if (!peStream.CanWrite)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportWrite, nameof(peStream));
}
if (pdbStream != null && !pdbStream.CanWrite)
{
throw new ArgumentException(CodeAnalysisResources.StreamMustSupportWrite, nameof(pdbStream));
}
return Emit(
peStream,
pdbStream,
xmlDocumentationStream,
win32Resources,
manifestResources,
options,
testData: null,
getHostDiagnostics: null,
cancellationToken: cancellationToken);
}
///
/// Emit the IL for the compiled source code into the specified stream.
///
/// Provides the PE stream the compiler will write to.
/// Provides the PDB stream the compiler will write to.
/// Stream to which the compilation's XML documentation will be written. Null to forego XML generation.
/// 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.
/// List of the compilation's managed resources. Null to indicate that there are none.
/// Emit options.
/// Returns any extra diagnostics produced by the host of the compiler.
/// To cancel the emit process.
internal EmitResult Emit(
EmitStreamProvider peStreamProvider,
EmitStreamProvider pdbStreamProvider,
EmitStreamProvider xmlDocumentationStreamProvider,
EmitStreamProvider win32ResourcesProvider,
IEnumerable manifestResources,
EmitOptions options,
Func> getHostDiagnostics,
CancellationToken cancellationToken)
{
return Emit(
peStreamProvider,
pdbStreamProvider,
xmlDocumentationStreamProvider,
win32ResourcesProvider,
manifestResources,
options,
testData: null,
getHostDiagnostics: getHostDiagnostics,
cancellationToken: cancellationToken);
}
///
/// This overload is only intended to be directly called by tests that want to pass .
/// The map is used for storing a list of methods and their associated IL.
///
/// True if emit succeeded.
internal EmitResult Emit(
Stream peStream,
Stream pdbStream,
Stream xmlDocumentationStream,
Stream win32Resources,
IEnumerable manifestResources,
EmitOptions options,
CompilationTestData testData,
Func> getHostDiagnostics,
CancellationToken cancellationToken)
{
return Emit(
new SimpleEmitStreamProvider(peStream),
(pdbStream != null) ? new SimpleEmitStreamProvider(pdbStream) : null,
(xmlDocumentationStream != null) ? new SimpleEmitStreamProvider(xmlDocumentationStream) : null,
(win32Resources != null) ? new SimpleEmitStreamProvider(win32Resources) : null,
manifestResources,
options,
testData,
getHostDiagnostics,
cancellationToken);
}
///
/// 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.
///
public EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable edits,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ICollection updatedMethods,
CancellationToken cancellationToken = default(CancellationToken))
{
return EmitDifference(baseline, edits, s => false, metadataStream, ilStream, pdbStream, updatedMethods, cancellationToken);
}
///
/// 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.
///
public EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable edits,
Func isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ICollection updatedMethods,
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, updatedMethods, null, cancellationToken);
}
internal abstract EmitDifferenceResult EmitDifference(
EmitBaseline baseline,
IEnumerable edits,
Func isAddedSymbol,
Stream metadataStream,
Stream ilStream,
Stream pdbStream,
ICollection updatedMethodHandles,
CompilationTestData testData,
CancellationToken cancellationToken);
///
/// This overload is only intended to be directly called by tests that want to pass .
/// The map is used for storing a list of methods and their associated IL.
///
/// True if emit succeeded.
internal EmitResult Emit(
EmitStreamProvider peStreamProvider,
EmitStreamProvider pdbStreamProvider,
EmitStreamProvider xmlDocumentationStreamProvider,
EmitStreamProvider win32ResourcesStreamProvider,
IEnumerable manifestResources,
EmitOptions options,
CompilationTestData testData,
Func> getHostDiagnostics,
CancellationToken cancellationToken)
{
Debug.Assert(peStreamProvider != null);
DiagnosticBag diagnostics = DiagnosticBag.GetInstance();
if (options != null)
{
options.ValidateOptions(diagnostics, this.MessageProvider);
}
else
{
options = EmitOptions.Default;
}
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 (diagnostics.HasAnyErrors())
{
return ToEmitResultAndFree(diagnostics, success: false);
}
var moduleBeingBuilt = this.CreateModuleBuilder(
options,
manifestResources,
null,
testData,
diagnostics,
cancellationToken);
if (moduleBeingBuilt == null)
{
return ToEmitResultAndFree(diagnostics, success: false);
}
var win32Resources = win32ResourcesStreamProvider?.GetStream(diagnostics);
var xmlDocumentationStream = xmlDocumentationStreamProvider?.GetStream(diagnostics);
if (!this.Compile(
moduleBeingBuilt,
win32Resources,
xmlDocumentationStream,
generateDebugInfo: pdbStreamProvider != null,
diagnostics: diagnostics,
filterOpt: null,
cancellationToken: cancellationToken))
{
return ToEmitResultAndFree(diagnostics, success: false);
}
var hostDiagnostics = getHostDiagnostics?.Invoke() ?? ImmutableArray.Empty;
diagnostics.AddRange(hostDiagnostics);
if (hostDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error))
{
return ToEmitResultAndFree(diagnostics, success: false);
}
bool success = SerializeToPeStream(
moduleBeingBuilt,
peStreamProvider,
pdbStreamProvider,
testData?.SymWriterFactory,
diagnostics,
metadataOnly: options.EmitMetadataOnly,
cancellationToken: cancellationToken);
return ToEmitResultAndFree(diagnostics, success);
}
private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool success)
{
return new EmitResult(success, diagnostics.ToReadOnlyAndFree());
}
internal bool SerializeToPeStream(
CommonPEModuleBuilder moduleBeingBuilt,
EmitStreamProvider peStreamProvider,
EmitStreamProvider pdbStreamProvider,
Func