// 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.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; 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 { /// /// 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 readonly IReadOnlyDictionary _features; internal Compilation( string name, ImmutableArray references, IReadOnlyDictionary features, Type submissionReturnType, Type hostObjectType, bool isSubmission, AsyncQueue eventQueue) { Debug.Assert(!references.IsDefault); Debug.Assert(features != null); this.AssemblyName = name; this.ExternalReferences = references; this.EventQueue = eventQueue; if (isSubmission) { _lazySubmissionSlotIndex = SubmissionSlotIndexToBeAllocated; this.SubmissionReturnType = submissionReturnType ?? typeof(object); this.HostObjectType = hostObjectType; } else { _lazySubmissionSlotIndex = SubmissionSlotIndexNotApplicable; } _features = features; } protected static IReadOnlyDictionary SyntaxTreeCommonFeatures(IEnumerable trees) { IReadOnlyDictionary 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("inconsistent syntax tree features", nameof(trees)); } } } if (set == null) { // Edge case where there are no syntax trees set = ImmutableDictionary.Empty; } return set; } internal abstract AnalyzerDriver AnalyzerForLanguage(ImmutableArray analyzers, AnalyzerManager analyzerManager); /// /// 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 specified 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 GetBoundReferenceManager().GetMetadataReference(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); #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 #region Emit /// /// Constructs the module serialization properties out of the compilation options of this compilation. /// 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 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.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, fileAlignment: fileAlignment, sectionAlignment: Cci.ModulePropertiesForSerialization.DefaultSectionAlignment, targetRuntimeVersion: targetRuntimeVersion, machine: machine, prefer32Bit: platform == Platform.AnyCpu32BitPreferred, trackDebugData: false, baseAddress: baseAddress, sizeOfHeapReserve: sizeOfHeapReserve, sizeOfHeapCommit: sizeOfHeapCommit, sizeOfStackReserve: sizeOfStackReserve, sizeOfStackCommit: sizeOfStackCommit, enableHighEntropyVA: emitOptions.HighEntropyVirtualAddressSpace, strongNameSigned: HasStrongName, imageCharacteristics: GetCharacteristics(outputKind, requires32Bit), configureToExecuteInAppContainer: compilationOptions.OutputKind == OutputKind.WindowsRuntimeApplication, subsystem: GetSubsystem(outputKind), majorSubsystemVersion: (ushort)subsystemVersion.Major, minorSubsystemVersion: (ushort)subsystemVersion.Minor, linkerMajorVersion: this.LinkerMajorVersion, linkerMinorVersion: 0); } 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); } } /// /// 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. /// 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.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, IMethodSymbol debugEntryPoint, IEnumerable manifestResources, CompilationTestData testData, DiagnosticBag diagnostics, CancellationToken cancellationToken); // TODO: private protected internal abstract bool CompileImpl( CommonPEModuleBuilder moduleBuilder, Stream win32Resources, Stream xmlDocStream, bool emittingPdb, DiagnosticBag diagnostics, Predicate filterOpt, CancellationToken cancellationToken); internal bool Compile( CommonPEModuleBuilder moduleBuilder, Stream win32Resources, Stream xmlDocStream, bool emittingPdb, DiagnosticBag diagnostics, Predicate filterOpt, CancellationToken cancellationToken) { try { return CompileImpl( moduleBuilder, win32Resources, xmlDocStream, emittingPdb, 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, debugEntryPoint: null, manifestResources: null, testData: null, diagnostics: discardedDiagnostics, cancellationToken: cancellationToken); if (moduleBeingBuilt != null) { Compile( moduleBeingBuilt, win32Resources: null, xmlDocStream: null, emittingPdb: 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. [EditorBrowsable(EditorBrowsableState.Never)] public EmitResult Emit( Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable manifestResources, EmitOptions options, CancellationToken cancellationToken) { return Emit( peStream, pdbStream, xmlDocumentationStream, win32Resources, manifestResources, options, null, 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. /// /// 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 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 , /// and taking other debugging attributes into consideration. /// /// By default both entry points in an executable program (, , ) /// 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 whcih is limited to a non-generic static method of specific signature, there are no restrictions on the /// method other than having a method body (extern, interface, or abstract methods are not allowed). /// /// 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, IMethodSymbol debugEntryPoint = 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, debugEntryPoint, testData: null, getHostDiagnostics: null, cancellationToken: cancellationToken); } internal EmitResult Emit( EmitStreamProvider peStreamProvider, EmitStreamProvider pdbStreamProvider, EmitStreamProvider xmlDocumentationStreamProvider, EmitStreamProvider win32ResourcesProvider, IEnumerable manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Func> getHostDiagnostics, CancellationToken cancellationToken) { return Emit( peStreamProvider, pdbStreamProvider, xmlDocumentationStreamProvider, win32ResourcesProvider, manifestResources, options, debugEntryPoint, 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, IMethodSymbol debugEntryPoint, 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, debugEntryPoint, 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, IMethodSymbol debugEntryPoint, 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 (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 (diagnostics.HasAnyErrors()) { return ToEmitResultAndFree(diagnostics, success: false); } // 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()); return ToEmitResultAndFree(diagnostics, success: false); } var moduleBeingBuilt = this.CreateModuleBuilder( options, debugEntryPoint, manifestResources, testData, diagnostics, cancellationToken); if (moduleBeingBuilt == null) { return ToEmitResultAndFree(diagnostics, success: false); } var win32Resources = win32ResourcesStreamProvider?.GetOrCreateStream(diagnostics); var xmlDocumentationStream = xmlDocumentationStreamProvider?.GetOrCreateStream(diagnostics); if (!this.Compile( moduleBeingBuilt, win32Resources, xmlDocumentationStream, emittingPdb: 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); } internal abstract void ValidateDebugEntryPoint(IMethodSymbol debugEntryPoint, DiagnosticBag diagnostics); private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool success) { return new EmitResult(success, diagnostics.ToReadOnlyAndFree()); } internal bool IsEmitDeterministic => this.Feature("deterministic")?.Equals("true", StringComparison.OrdinalIgnoreCase) ?? false; internal bool SerializeToPeStream( CommonPEModuleBuilder moduleBeingBuilt, EmitStreamProvider peStreamProvider, EmitStreamProvider pdbStreamProvider, Func testSymWriterFactory, DiagnosticBag diagnostics, bool metadataOnly, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); Cci.PdbWriter nativePdbWriter = null; Stream signingInputStream = null; DiagnosticBag metadataDiagnostics = null; DiagnosticBag pdbBag = null; Stream peStream = null; Stream portablePdbStream = null; Stream portablePdbTempStream = null; Stream peTempStream = null; bool deterministic = IsEmitDeterministic; bool emitPortablePdb = moduleBeingBuilt.EmitOptions.DebugInformationFormat == DebugInformationFormat.PortablePdb; string pdbPath = (pdbStreamProvider != null) ? (moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb")) : null; try { metadataDiagnostics = DiagnosticBag.GetInstance(); if (!emitPortablePdb && pdbStreamProvider != 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(pdbPath, testSymWriterFactory, deterministic); } Func getPortablePdbStream; if (emitPortablePdb && pdbStreamProvider != null) { getPortablePdbStream = () => { if (metadataDiagnostics.HasAnyErrors()) { return null; } portablePdbStream = pdbStreamProvider.GetOrCreateStream(metadataDiagnostics); if (portablePdbStream == null) { Debug.Assert(metadataDiagnostics.HasAnyErrors()); return null; } // When in deterministic mode, we need to seek and read the stream to compute a deterministic PDB ID. // If the underlying stream isn't readable and seekable, we need to use a temp stream. var retStream = portablePdbStream; if (!retStream.CanSeek || deterministic && !retStream.CanRead) { retStream = portablePdbTempStream = new MemoryStream(); } return retStream; }; } else { getPortablePdbStream = null; } Func getPeStream = () => { if (metadataDiagnostics.HasAnyErrors()) { return null; } peStream = peStreamProvider.GetOrCreateStream(metadataDiagnostics); if (peStream == null) { Debug.Assert(metadataDiagnostics.HasAnyErrors()); return null; } // Signing can only be done to on-disk files. This is a limitation of the CLR APIs which we use // to perform strong naming. If this binary is configured to be signed, create a temp file, output to that // then stream that to the stream that this method was called with. Otherwise output to the // stream that this method was called with. Stream retStream; if (!metadataOnly && IsRealSigned) { Debug.Assert(Options.StrongNameProvider != null); // Targeted try-catch for errors during CreateInputStream as found in TFS 1140649 // TODO: Put this wrapping in PeWriter to catch all potential PE writing exceptions try { signingInputStream = Options.StrongNameProvider.CreateInputStream(); retStream = signingInputStream; } catch (Exception e) { throw new Cci.PeWritingException(e); } } else { signingInputStream = null; retStream = peStream; } // When in deterministic mode, we need to seek and read the stream to compute a deterministic MVID. // If the underlying stream isn't readable and seekable, we need to use a temp stream. if (!retStream.CanSeek || deterministic && !retStream.CanRead) { peTempStream = new MemoryStream(); return peTempStream; } return retStream; }; try { if (Cci.PeWriter.WritePeToStream( new EmitContext((Cci.IModule)moduleBeingBuilt, null, metadataDiagnostics), this.MessageProvider, getPeStream, getPortablePdbStream, nativePdbWriter, pdbPath, metadataOnly, deterministic, cancellationToken)) { if (peTempStream != null) { peTempStream.Position = 0; peTempStream.CopyTo(peStream); } if (portablePdbTempStream != null) { portablePdbTempStream.Position = 0; portablePdbTempStream.CopyTo(portablePdbStream); } if (nativePdbWriter != null) { var nativePdbStream = pdbStreamProvider.GetOrCreateStream(metadataDiagnostics); Debug.Assert(nativePdbStream != null || metadataDiagnostics.HasAnyErrors()); if (nativePdbStream != null) { nativePdbWriter.WriteTo(nativePdbStream); } } } } catch (Cci.PdbWritingException ex) { diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PdbWritingFailed, Location.None, ex.Message)); return false; } catch (Cci.PeWritingException e) { // Targeted fix for TFS 1140649 // TODO: Add resource and better error message for a variety of PE exceptions diagnostics.Add(StrongNameKeys.GetError(StrongNameKeys.KeyFilePath, StrongNameKeys.KeyContainer, e.Message, MessageProvider)); } 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)) { return false; } if (signingInputStream != null && peStream != null) { Debug.Assert(Options.StrongNameProvider != null); try { Options.StrongNameProvider.SignAssembly(StrongNameKeys, signingInputStream, peStream); } catch (IOException ex) { diagnostics.Add(StrongNameKeys.GetError(StrongNameKeys.KeyFilePath, StrongNameKeys.KeyContainer, ex.Message, MessageProvider)); return false; } } } finally { nativePdbWriter?.Dispose(); peTempStream?.Dispose(); portablePdbTempStream?.Dispose(); signingInputStream?.Dispose(); pdbBag?.Free(); metadataDiagnostics?.Free(); } return true; } internal EmitBaseline SerializeToDeltaStreams( CommonPEModuleBuilder moduleBeingBuilt, EmitBaseline baseline, DefinitionMap definitionMap, SymbolChanges changes, Stream metadataStream, Stream ilStream, Stream pdbStream, ICollection updatedMethods, DiagnosticBag diagnostics, Func testSymWriterFactory, CancellationToken cancellationToken) { using (var pdbWriter = new Cci.PdbWriter( moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"), testSymWriterFactory, deterministic: false)) { var context = new EmitContext((Cci.IModule)moduleBeingBuilt, null, diagnostics); var encId = Guid.NewGuid(); try { var writer = new DeltaMetadataWriter( context, MessageProvider, baseline, encId, definitionMap, changes, cancellationToken); Cci.MetadataSizes metadataSizes; writer.WriteMetadataAndIL(pdbWriter, metadataStream, ilStream, out metadataSizes); writer.GetMethodTokens(updatedMethods); pdbWriter.WriteTo(pdbStream); return diagnostics.HasAnyErrors() ? null : writer.GetDelta(baseline, this, encId, metadataSizes); } catch (Cci.PdbWritingException e) { diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PdbWritingFailed, Location.None, e.Message)); return null; } catch (PermissionSetFileReadException e) { diagnostics.Add(MessageProvider.CreateDiagnostic(MessageProvider.ERR_PermissionSetAttributeFileReadError, Location.None, e.FileName, e.PropertyName, e.Message)); return null; } } } internal string Feature(string p) { string v; return _features.TryGetValue(p, out v) ? v : null; } #endregion private ConcurrentDictionary _lazyTreeToUsedImportDirectivesMap; private static readonly Func s_createSetCallback = t => new SmallConcurrentSetOfInts(); private ConcurrentDictionary TreeToUsedImportDirectivesMap { get { return LazyInitializer.EnsureInitialized(ref _lazyTreeToUsedImportDirectivesMap); } } internal void MarkImportDirectiveAsUsed(SyntaxNode 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); } /// /// 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. /// 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); /// /// 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. /// internal abstract int CompareSourceLocations(Location loc1, Location loc2); /// /// Return the lexically first of two locations. /// internal TLocation FirstSourceLocation(TLocation first, TLocation second) where TLocation : Location { if (CompareSourceLocations(first, second) <= 0) { return first; } else { return second; } } /// /// Return the lexically first of multiple locations. /// internal TLocation FirstSourceLocation(ImmutableArray 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 /// /// Return true if there is a source declaration symbol name that meets given predicate. /// public abstract bool ContainsSymbolsWithName(Func predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken)); /// /// Return source declaration symbols whose name meets given predicate. /// public abstract IEnumerable GetSymbolsWithName(Func predicate, SymbolFilter filter = SymbolFilter.TypeAndMember, CancellationToken cancellationToken = default(CancellationToken)); #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(); } _lazyMakeMemberMissingMap[member] = true; } private bool IsMemberMissing(int member) { return _lazyMakeMemberMissingMap != null && _lazyMakeMemberMissingMap.ContainsKey(member); } internal void MakeTypeMissing(WellKnownType type) { if (_lazyMakeWellKnownTypeMissingMap == null) { _lazyMakeWellKnownTypeMissingMap = new SmallDictionary(); } _lazyMakeWellKnownTypeMissingMap[(int)type] = true; } internal bool IsTypeMissing(WellKnownType type) { return _lazyMakeWellKnownTypeMissingMap != null && _lazyMakeWellKnownTypeMissingMap.ContainsKey((int)type); } } }