diff --git a/docs/compilers/CSharp/CommandLine.md b/docs/compilers/CSharp/CommandLine.md index 8244ce7bfc71e..5f78b84f8a6ae 100644 --- a/docs/compilers/CSharp/CommandLine.md +++ b/docs/compilers/CSharp/CommandLine.md @@ -4,6 +4,7 @@ | ---- | ---- | | **OUTPUT FILES** | | `/out:`*file* | Specify output file name (default: base name of file with main class or first file) +| `/refout:`*file* | Specify the reference assembly's output file name | `/target:exe` | Build a console executable (default) (Short form: `/t:exe`) | `/target:winexe` | Build a Windows executable (Short form: `/t:winexe` ) | `/target:library` | Build a library (Short form: `/t:library`) @@ -32,6 +33,7 @@ | `/debug`:{`full`|`pdbonly`|`portable`} | Specify debugging type (`full` is default, and enables attaching a debugger to a running program. `portable` is a cross-platform format) | `/optimize`{`+`|`-`} | Enable optimizations (Short form: `/o`) | `/deterministic` | Produce a deterministic assembly (including module version GUID and timestamp) +| `/refonly | Produce a reference assembly, instead of a full assembly, as the primary output | **ERRORS AND WARNINGS** | `/warnaserror`{`+`|`-`} | Report all warnings as errors | `/warnaserror`{`+`|`-`}`:`*warn list* | Report specific warnings as errors diff --git a/docs/compilers/Visual Basic/CommandLine.md b/docs/compilers/Visual Basic/CommandLine.md index 0a7afcefd67da..e7c44304aa4fa 100644 --- a/docs/compilers/Visual Basic/CommandLine.md +++ b/docs/compilers/Visual Basic/CommandLine.md @@ -4,6 +4,7 @@ | ---- | ---- | | **OUTPUT FILE** | `/out:`*file* | Specifies the output file name. +| `/refout:`*file* | Specify the reference assembly's output file name | `/target:exe` | Create a console application (default). (Short form: `/t`) | `/target:winexe` | Create a Windows application. | `/target:library` | Create a library assembly. @@ -34,6 +35,7 @@ | `/debug:portable` | Emit debugging information in the portable format. | `/debug:pdbonly` | Emit PDB file only. | `/deterministic` | Produce a deterministic assembly (including module version GUID and timestamp) +| `/refonly | Produce a reference assembly, instead of a full assembly, as the primary output | **ERRORS AND WARNINGS** | `/nowarn` | Disable all warnings. | `/nowarn:`*number_list* | Disable a list of individual warnings. diff --git a/docs/features/refout.md b/docs/features/refout.md index 9e533c52c72f7..9ab450afc9ffc 100644 --- a/docs/features/refout.md +++ b/docs/features/refout.md @@ -32,11 +32,12 @@ Two mutually exclusive command-line parameters will be added to `csc.exe` and `v - `/refout` - `/refonly` -The `/refout` parameter specifies a file path where the ref assembly should be output. This translates to `metadataPeStream` in the `Emit` API (see details below). +The `/refout` parameter specifies a file path where the ref assembly should be output. This translates to `metadataPeStream` in the `Emit` API (see details below). The filename for the ref assembly should generally match that of the primary assembly, but it can be in a different folder. The `/refonly` parameter is a flag that indicates that a ref assembly should be output instead of an implementation assembly. The `/refonly` parameter is not allowed together with the `/refout` parameter, as it doesn't make sense to have both the primary and secondary outputs be ref assemblies. Also, the `/refonly` parameter silently disables outputting PDBs, as ref assemblies cannot be executed. The `/refonly` parameter translates to `EmitMetadataOnly` being `true`, and `IncludePrivateMembers` being `false` in the `Emit` API (see details below). +Neither `/refonly` nor `/refout` are permitted with `/target:module` or `/addmodule` options. When the compiler produces documentation, the contents produced will match the APIs that go into the primary output. In other words, the documentation will be filtered down when using the `/refonly` parameter. @@ -47,13 +48,14 @@ The `CoreCompile` target will support a new output, called `IntermediateRefAssem The `Csc` task will support a new output, called `OutputRefAssembly`, which parallels the existing `OutputAssembly`. Both of those basically map to the `/refout` command-line parameter. -An additional task, called `CopyRefAssembly`, will be provided along with the existing `Csc` task. It takes a `SourcePath` and a `DestinationPath` and generally copies the file from the source over to the destination. But if it can determine that the contents of those two files match, then the destination file is left untouched. +An additional task, called `CopyRefAssembly`, will be provided along with the existing `Csc` task. It takes a `SourcePath` and a `DestinationPath` and generally copies the file from the source over to the destination. But if it can determine that the contents of those two files match (by comparing their MVIDs, see details below), then the destination file is left untouched. ### CodeAnalysis APIs It is already possible to produce metadata-only assemblies by using `EmitOptions.EmitMetadataOnly`, which is used in IDE scenarios with cross-language dependencies. The compiler will be updated to honour the `EmitOptions.IncludePrivateMembers` flag as well. When combined with `EmitMetadataOnly` or a `metadataPeStream` in `Emit`, a ref assembly will be produced. -The diagnostic check for emitting methods lacking a body (`void M();`) will be moved from declaration diagnostics to regular diagnostics, so that code will successfully emit with `EmitMetadataOnly`. +The diagnostic check for emitting methods lacking a body (`void M();`) will be filtered from declaration diagnostics, so that code will successfully emit with `EmitMetadataOnly`. Later on, the `EmitOptions.TolerateErrors` flag will allow emitting error types as well. +`Emit` is also modified to produce a new PE section called ".mvid" containing a copy of the MVID, when producing ref assemblies. This makes it easy for `CopyRefAssembly` to extract and compare MVIDs from ref assemblies. Going back to the 4 driving scenarios: 1. For a regular compilation, `EmitMetadataOnly` is left to `false` and no `metadataPeStream` is passed into `Emit`. @@ -70,7 +72,6 @@ As mentioned above, there may be further refinements after C# 7.1: - should explicit method implementations be included in ref assemblies? - Non-public attributes on public APIs (emit attribute based on accessibility rule) - ref assemblies and NoPia -- `/refout` and `/addmodule`, should we disallow this combination? ## Related issues - Produce ref assemblies from command-line and msbuild (https://github.com/dotnet/roslyn/issues/2184) diff --git a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj index 2b69608a19d3d..89673c5a909f2 100644 --- a/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj +++ b/src/Compilers/CSharp/Test/Emit/CSharpCompilerEmitTest.csproj @@ -55,6 +55,9 @@ + + Emit\MvidReader.cs + diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index abc39c4f664aa..77ed494cf7dc7 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; @@ -49,7 +50,6 @@ public void Main() Diagnostic(ErrorCode.ERR_AssgLvalueExpected, "x")); } - [Fact] public void CompilationEmitWithQuotedMainType() { @@ -262,9 +262,11 @@ internal static void Main() VerifyEntryPoint(output, expectZero: false); VerifyMethods(output, new[] { "void C.Main()", "C..ctor()" }); + VerifyMvid(output, hasMvidSection: false); VerifyEntryPoint(metadataOutput, expectZero: true); VerifyMethods(metadataOutput, new[] { "C..ctor()" }); + VerifyMvid(metadataOutput, hasMvidSection: true); } void VerifyEntryPoint(MemoryStream stream, bool expectZero) @@ -275,6 +277,110 @@ void VerifyEntryPoint(MemoryStream stream, bool expectZero) } } + private class TestResourceSectionBuilder : ResourceSectionBuilder + { + public TestResourceSectionBuilder() + { + } + + protected override void Serialize(BlobBuilder builder, SectionLocation location) + { + builder.WriteInt32(0x12345678); + builder.WriteInt32(location.PointerToRawData); + builder.WriteInt32(location.RelativeVirtualAddress); + } + } + + private class TestPEBuilder : ManagedPEBuilder + { + public static readonly Guid s_mvid = Guid.Parse("a78fa2c3-854e-42bf-8b8d-75a450a6dc18"); + + public TestPEBuilder(PEHeaderBuilder header, + MetadataRootBuilder metadataRootBuilder, + BlobBuilder ilStream, + ResourceSectionBuilder nativeResources) + : base(header, metadataRootBuilder, ilStream, nativeResources: nativeResources) + { + } + + protected override ImmutableArray
CreateSections() + { + return base.CreateSections().Add( + new Section(".mvid", SectionCharacteristics.MemRead | + SectionCharacteristics.ContainsInitializedData | + SectionCharacteristics.MemDiscardable)); + } + + protected override BlobBuilder SerializeSection(string name, SectionLocation location) + { + if (name.Equals(".mvid", StringComparison.Ordinal)) + { + var sectionBuilder = new BlobBuilder(); + sectionBuilder.WriteGuid(s_mvid); + return sectionBuilder; + } + + return base.SerializeSection(name, location); + } + } + + [Fact] + public void MvidSectionNotFirst() + { + var ilBuilder = new BlobBuilder(); + var metadataBuilder = new MetadataBuilder(); + + var peBuilder = new TestPEBuilder( + PEHeaderBuilder.CreateLibraryHeader(), + new MetadataRootBuilder(metadataBuilder), + ilBuilder, + nativeResources: new TestResourceSectionBuilder()); + + var peBlob = new BlobBuilder(); + peBuilder.Serialize(peBlob); + + var peStream = new MemoryStream(); + peBlob.WriteContentTo(peStream); + + peStream.Position = 0; + using (var peReader = new PEReader(peStream)) + { + AssertEx.Equal(new[] { ".text", ".rsrc", ".reloc", ".mvid" }, + peReader.PEHeaders.SectionHeaders.Select(h => h.Name)); + + peStream.Position = 0; + var mvid = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(peStream); + Assert.Equal(TestPEBuilder.s_mvid, mvid); + } + } + + /// + /// Extract the MVID using two different methods (PEReader and MvidReader) and compare them. + /// We only expect an .mvid section in ref assemblies. + /// + private void VerifyMvid(MemoryStream stream, bool hasMvidSection) + { + stream.Position = 0; + using (var reader = new PEReader(stream)) + { + var metadataReader = reader.GetMetadataReader(); + Guid mvidFromModuleDefinition = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid); + + stream.Position = 0; + var mvidFromMvidReader = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(stream); + + Assert.NotEqual(Guid.Empty, mvidFromModuleDefinition); + if (hasMvidSection) + { + Assert.Equal(mvidFromModuleDefinition, mvidFromMvidReader); + } + else + { + Assert.Equal(Guid.Empty, mvidFromMvidReader); + } + } + } + [Fact] public void EmitRefAssembly_PrivatePropertySetter() { @@ -296,6 +402,8 @@ public class C VerifyMethods(output, new[] { "System.Int32 C.k__BackingField", "System.Int32 C.PrivateSetter.get", "void C.PrivateSetter.set", "C..ctor()", "System.Int32 C.PrivateSetter { get; private set; }" }); VerifyMethods(metadataOutput, new[] { "System.Int32 C.PrivateSetter.get", "C..ctor()", "System.Int32 C.PrivateSetter { get; }" }); + VerifyMvid(output, hasMvidSection: false); + VerifyMvid(metadataOutput, hasMvidSection: true); } } @@ -559,6 +667,20 @@ private void CompareAssemblies(string sourceTemplate, string change1, string cha { AssertEx.NotEqual(image1, image2, message: $"Expecting difference for includePrivateMembers={includePrivateMembers} case, but they matched."); } + + var mvid1 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image1.DangerousGetUnderlyingArray())); + var mvid2 = BuildTasks.MvidReader.ReadAssemblyMvidOrEmpty(new MemoryStream(image2.DangerousGetUnderlyingArray())); + + if (!includePrivateMembers) + { + Assert.NotEqual(Guid.Empty, mvid1); + Assert.Equal(expectMatch, mvid1 == mvid2); + } + else + { + Assert.Equal(Guid.Empty, mvid1); + Assert.Equal(Guid.Empty, mvid2); + } } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs index 2d98c539a82f6..a3a6be5cbb7b4 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs @@ -18,14 +18,19 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Emit public class DeterministicTests : EmitMetadataTestBase { private Guid CompiledGuid(string source, string assemblyName, bool debug) + { + return CompiledGuid(source, assemblyName, options: debug ? TestOptions.DebugExe : TestOptions.ReleaseExe); + } + + private Guid CompiledGuid(string source, string assemblyName, CSharpCompilationOptions options, EmitOptions emitOptions = null) { var compilation = CreateCompilation(source, assemblyName: assemblyName, references: new[] { MscorlibRef }, - options: (debug ? TestOptions.DebugExe : TestOptions.ReleaseExe).WithDeterministic(true)); + options: options.WithDeterministic(true)); Guid result = default(Guid); - base.CompileAndVerify(compilation, validator: a => + base.CompileAndVerify(compilation, emitOptions: emitOptions, validator: a => { var module = a.Modules[0]; result = module.GetModuleVersionIdOrThrow(); @@ -104,6 +109,22 @@ public static void Main(string[] args) {} Assert.NotEqual(mvid3, mvid7); } + [Fact] + public void RefAssembly() + { + var source = +@"class Program +{ + public static void Main(string[] args) {} + CHANGE +}"; + var emitRefAssembly = EmitOptions.Default.WithEmitMetadataOnly(true).WithIncludePrivateMembers(false); + + var mvid1 = CompiledGuid(source.Replace("CHANGE", ""), "X1", TestOptions.DebugDll, emitRefAssembly); + var mvid2 = CompiledGuid(source.Replace("CHANGE", "private void M() { }"), "X1", TestOptions.DebugDll, emitRefAssembly); + Assert.Equal(mvid1, mvid2); + } + const string CompareAllBytesEmitted_Source = @" using System; using System.Linq; diff --git a/src/Compilers/Core/MSBuildTask/CopyRefAssembly.cs b/src/Compilers/Core/MSBuildTask/CopyRefAssembly.cs index 1c2f34c70646a..5b12dc838c517 100644 --- a/src/Compilers/Core/MSBuildTask/CopyRefAssembly.cs +++ b/src/Compilers/Core/MSBuildTask/CopyRefAssembly.cs @@ -2,8 +2,6 @@ using System; using System.IO; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -85,10 +83,8 @@ private bool Copy() private Guid ExtractMvid(string path) { using (FileStream source = File.OpenRead(path)) - using (var reader = new PEReader(source)) { - var metadataReader = reader.GetMetadataReader(); - return metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid); + return MvidReader.ReadAssemblyMvidOrEmpty(source); } } } diff --git a/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj b/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj index cbced79b1b505..9c161e6aa519b 100644 --- a/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj +++ b/src/Compilers/Core/MSBuildTask/MSBuildTask.csproj @@ -55,6 +55,7 @@ + diff --git a/src/Compilers/Core/MSBuildTask/MvidReader.cs b/src/Compilers/Core/MSBuildTask/MvidReader.cs new file mode 100755 index 0000000000000..745fdf24fbb11 --- /dev/null +++ b/src/Compilers/Core/MSBuildTask/MvidReader.cs @@ -0,0 +1,210 @@ +// 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.Diagnostics; +using System.IO; + +namespace Microsoft.CodeAnalysis.BuildTasks +{ + public static class MvidReader + { + private static readonly Guid s_empty = Guid.Empty; + + public static Guid ReadAssemblyMvidOrEmpty(Stream stream) + { + return ReadAssemblyMvidOrEmpty(new BinaryReader(stream)); + } + + private static Guid ReadAssemblyMvidOrEmpty(BinaryReader reader) + { + // DOS Header: Magic number (2) + if (!ReadUInt16(reader, out ushort magicNumber) || magicNumber != 0x5a4d) // "MZ" + { + return s_empty; + } + + // DOS Header: Address of PE Signature (at 0x3C) + if (!MoveTo(0x3C, reader)) + { + return s_empty; + } + if (!ReadUInt32(reader, out uint pointerToPeSignature)) + { + return s_empty; + } + + // jump over the MS DOS Stub to the PE Signature + if (!MoveTo(pointerToPeSignature, reader)) + { + return s_empty; + } + + // PE Signature ('P' 'E' null null) + if (!ReadUInt32(reader, out uint peSig) || peSig != 0x00004550) + { + return s_empty; + } + + // COFF Header: Machine (2) + if (!Skip(2, reader)) + { + return s_empty; + } + + // COFF Header: NumberOfSections (2) + if (!ReadUInt16(reader, out ushort sections)) + { + return s_empty; + } + + // COFF Header: TimeDateStamp (4), PointerToSymbolTable (4), NumberOfSymbols (4) + if (!Skip(12, reader)) + { + return s_empty; + } + + // COFF Header: OptionalHeaderSize (2) + if (!ReadUInt16(reader, out ushort optionalHeaderSize)) + { + return s_empty; + } + + // COFF Header: Characteristics (2) + if (!Skip(2, reader)) + { + return s_empty; + } + + // Optional header + if (!Skip(optionalHeaderSize, reader)) + { + return s_empty; + } + + // Section headers + return FindMvidInSections(sections, reader); + } + + private static Guid FindMvidInSections(ushort count, BinaryReader reader) + { + for (int i = 0; i < count; i++) + { + // Section: Name (8) + if (!ReadBytes(reader, 8, out byte[] name)) + { + return s_empty; + } + + if (name.Length == 8 && name[0] == '.' && + name[1] == 'm' && name[2] == 'v' && name[3] == 'i' && name[4] == 'd' && name[5] == '\0') + { + // Section: VirtualSize (4) + if (!ReadUInt32(reader, out uint virtualSize) || virtualSize != 16) + { + // The .mvid section only stores a Guid + return s_empty; + } + + // Section: VirtualAddress (4), SizeOfRawData (4) + if (!Skip(8, reader)) + { + return s_empty; + } + + // Section: PointerToRawData (4) + if (!ReadUInt32(reader, out uint pointerToRawData)) + { + return s_empty; + } + + return ReadMvidSection(reader, pointerToRawData); + } + else + { + // Section: VirtualSize (4), VirtualAddress (4), SizeOfRawData (4), + // PointerToRawData (4), PointerToRelocations (4), PointerToLineNumbers (4), + // NumberOfRelocations (2), NumberOfLineNumbers (2), Characteristics (4) + if (!Skip(4 + 4 + 4 + 4 + 4 + 4 + 2 + 2 + 4, reader)) + { + return s_empty; + } + } + } + + return s_empty; + } + + private static Guid ReadMvidSection(BinaryReader reader, uint pointerToMvidSection) + { + if (!MoveTo(pointerToMvidSection, reader)) + { + return s_empty; + } + + if (!ReadBytes(reader, 16, out byte[] guidBytes)) + { + return s_empty; + } + + return new Guid(guidBytes); + } + + private static bool ReadUInt16(BinaryReader reader, out ushort output) + { + if (reader.BaseStream.Position + 2 >= reader.BaseStream.Length) + { + output = 0; + return false; + } + + output = reader.ReadUInt16(); + return true; + } + + private static bool ReadUInt32(BinaryReader reader, out uint output) + { + if (reader.BaseStream.Position + 4 >= reader.BaseStream.Length) + { + output = 0; + return false; + } + + output = reader.ReadUInt32(); + return true; + } + + private static bool ReadBytes(BinaryReader reader, int count, out byte[] output) + { + if (reader.BaseStream.Position + count >= reader.BaseStream.Length) + { + output = null; + return false; + } + + output = reader.ReadBytes(count); + return true; + } + + private static bool Skip(int bytes, BinaryReader reader) + { + if (reader.BaseStream.Position + bytes >= reader.BaseStream.Length) + { + return false; + } + + reader.BaseStream.Seek(bytes, SeekOrigin.Current); + return true; + } + + private static bool MoveTo(uint position, BinaryReader reader) + { + if (position >= reader.BaseStream.Length) + { + return false; + } + + reader.BaseStream.Seek(position, SeekOrigin.Begin); + return true; + } + } +} diff --git a/src/Compilers/Core/MSBuildTask/project.json b/src/Compilers/Core/MSBuildTask/project.json index c9efa1ac00175..ac0c51a5b3082 100644 --- a/src/Compilers/Core/MSBuildTask/project.json +++ b/src/Compilers/Core/MSBuildTask/project.json @@ -15,7 +15,6 @@ "System.IO.Pipes": "4.3.0", "System.Linq": "4.3.0", "System.Reflection": "4.3.0", - "System.Reflection.Metadata": "1.4.2", "System.Security.AccessControl": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", "System.Security.Principal.Windows": "4.3.0", diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index 1581cbd140c2f..d871379ace22a 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -140,6 +140,7 @@ + diff --git a/src/Compilers/Core/Portable/PEWriter/ExtendedPEBuilder.cs b/src/Compilers/Core/Portable/PEWriter/ExtendedPEBuilder.cs new file mode 100755 index 0000000000000..608d66faed3c2 --- /dev/null +++ b/src/Compilers/Core/Portable/PEWriter/ExtendedPEBuilder.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Cci +{ + /// + /// This PEBuilder adds an .mvid section. + /// + internal sealed class ExtendedPEBuilder + : ManagedPEBuilder + { + private const string MvidSectionName = ".mvid"; + public const int SizeOfGuid = 16; + + // When the section is built with a placeholder, the placeholder blob is saved for later fixing up. + private Blob _mvidSectionFixup = default(Blob); + + // Only include the .mvid section in ref assemblies + private readonly bool _withMvidSection; + + public ExtendedPEBuilder( + PEHeaderBuilder header, + MetadataRootBuilder metadataRootBuilder, + BlobBuilder ilStream, + BlobBuilder mappedFieldData, + BlobBuilder managedResources, + ResourceSectionBuilder nativeResources, + DebugDirectoryBuilder debugDirectoryBuilder, + int strongNameSignatureSize, + MethodDefinitionHandle entryPoint, + CorFlags flags, + Func, BlobContentId> deterministicIdProvider, + bool withMvidSection) + : base(header, metadataRootBuilder, ilStream, mappedFieldData, managedResources, nativeResources, + debugDirectoryBuilder, strongNameSignatureSize, entryPoint, flags, deterministicIdProvider) + { + _withMvidSection = withMvidSection; + } + + protected override ImmutableArray
CreateSections() + { + var baseSections = base.CreateSections(); + + if (_withMvidSection) + { + var builder = ArrayBuilder
.GetInstance(baseSections.Length + 1); + + builder.Add(new Section(MvidSectionName, SectionCharacteristics.MemRead | + SectionCharacteristics.ContainsInitializedData | + SectionCharacteristics.MemDiscardable)); + + builder.AddRange(baseSections); + return builder.ToImmutableAndFree(); + } + else + { + return baseSections; + } + } + + protected override BlobBuilder SerializeSection(string name, SectionLocation location) + { + if (name.Equals(MvidSectionName, StringComparison.Ordinal)) + { + Debug.Assert(_withMvidSection); + return SerializeMvidSection(location); + } + + return base.SerializeSection(name, location); + } + + internal BlobContentId Serialize(BlobBuilder peBlob, out Blob mvidSectionFixup) + { + var result = base.Serialize(peBlob); + mvidSectionFixup = _mvidSectionFixup; + return result; + } + + private BlobBuilder SerializeMvidSection(SectionLocation location) + { + var sectionBuilder = new BlobBuilder(); + + // The guid will be filled in later: + _mvidSectionFixup = sectionBuilder.ReserveBytes(SizeOfGuid); + var mvidWriter = new BlobWriter(_mvidSectionFixup); + mvidWriter.WriteBytes(0, _mvidSectionFixup.Length); + Debug.Assert(mvidWriter.RemainingBytes == 0); + + return sectionBuilder; + } + } +} diff --git a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs index cd9011bb617ab..a84e660f48385 100644 --- a/src/Compilers/Core/Portable/PEWriter/PeWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/PeWriter.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; @@ -69,7 +68,7 @@ internal static bool WritePeToStream( MethodDefinitionHandle entryPointHandle; MethodDefinitionHandle debugEntryPointHandle; mdWriter.GetEntryPoints(out entryPointHandle, out debugEntryPointHandle); - + if (!debugEntryPointHandle.IsNil) { nativePdbWriterOpt?.SetEntryPoint((uint)MetadataTokens.GetToken(debugEntryPointHandle)); @@ -186,7 +185,7 @@ internal static bool WritePeToStream( debugDirectoryBuilder = null; } - var peBuilder = new ManagedPEBuilder( + var peBuilder = new ExtendedPEBuilder( peHeaderBuilder, metadataRootBuilder, ilBuilder, @@ -197,12 +196,13 @@ internal static bool WritePeToStream( CalculateStrongNameSignatureSize(context.Module), entryPointHandle, properties.CorFlags, - deterministicIdProvider); + deterministicIdProvider, + metadataOnly && !context.IncludePrivateMembers); var peBlob = new BlobBuilder(); - var peContentId = peBuilder.Serialize(peBlob); + var peContentId = peBuilder.Serialize(peBlob, out Blob mvidSectionFixup); - PatchModuleVersionIds(mvidFixup, mvidStringFixup, peContentId.Guid); + PatchModuleVersionIds(mvidFixup, mvidSectionFixup, mvidStringFixup, peContentId.Guid); try { @@ -216,7 +216,7 @@ internal static bool WritePeToStream( return true; } - private static void PatchModuleVersionIds(Blob guidFixup, Blob stringFixup, Guid mvid) + private static void PatchModuleVersionIds(Blob guidFixup, Blob guidSectionFixup, Blob stringFixup, Guid mvid) { if (!guidFixup.IsDefault) { @@ -225,6 +225,13 @@ private static void PatchModuleVersionIds(Blob guidFixup, Blob stringFixup, Guid Debug.Assert(writer.RemainingBytes == 0); } + if (!guidSectionFixup.IsDefault) + { + var writer = new BlobWriter(guidSectionFixup); + writer.WriteGuid(mvid); + Debug.Assert(writer.RemainingBytes == 0); + } + if (!stringFixup.IsDefault) { var writer = new BlobWriter(stringFixup);