diff --git a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs index b83f09914846ff282328877f4218659cfecef2d6..79bf8ece258bf2b257f7ff6252fafa8cbc43a8fe 100644 --- a/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs +++ b/src/Compilers/CSharp/Portable/Emitter/EditAndContinue/EmitHelpers.cs @@ -40,7 +40,6 @@ internal static class EmitHelpers throw; } - var pdbName = FileNameUtilities.ChangeExtension(compilation.SourceModule.Name, "pdb"); var diagnostics = DiagnosticBag.GetInstance(); var emitOptions = EmitOptions.Default; @@ -64,11 +63,11 @@ internal static class EmitHelpers testData.Module = moduleBeingBuilt; } - baseline = moduleBeingBuilt.PreviousGeneration; - var definitionMap = moduleBeingBuilt.PreviousDefinitions; var changes = moduleBeingBuilt.Changes; + EmitBaseline newBaseline = null; + if (compilation.Compile( moduleBeingBuilt, win32Resources: null, @@ -81,48 +80,26 @@ internal static class EmitHelpers // Map the definitions from the previous compilation to the current compilation. // This must be done after compiling above since synthesized definitions // (generated when compiling method bodies) may be required. - baseline = MapToCompilation(compilation, moduleBeingBuilt); - - var pdbOutputInfo = new Cci.PdbOutputInfo(pdbName, pdbStream); - using (var pdbWriter = new Cci.PdbWriter(pdbOutputInfo, (testData != null) ? testData.SymWriterFactory : null)) - { - var context = new EmitContext(moduleBeingBuilt, null, diagnostics); - var encId = Guid.NewGuid(); - - try - { - var writer = new DeltaMetadataWriter( - context, - compilation.MessageProvider, - baseline, - encId, - definitionMap, - changes, - cancellationToken); - - Cci.MetadataSizes metadataSizes; - writer.WriteMetadataAndIL(pdbWriter, metadataStream, ilStream, out metadataSizes); - writer.GetMethodTokens(updatedMethods); - - bool hasErrors = diagnostics.HasAnyErrors(); - - return new EmitDifferenceResult( - success: !hasErrors, - diagnostics: diagnostics.ToReadOnlyAndFree(), - baseline: hasErrors ? null : writer.GetDelta(baseline, compilation, encId, metadataSizes)); - } - catch (Cci.PdbWritingException e) - { - diagnostics.Add(ErrorCode.FTL_DebugEmitFailure, Location.None, e.Message); - } - catch (PermissionSetFileReadException e) - { - diagnostics.Add(ErrorCode.ERR_PermissionSetAttributeFileReadError, Location.None, e.FileName, e.PropertyName, e.Message); - } - } + var mappedBaseline = MapToCompilation(compilation, moduleBeingBuilt); + + newBaseline = compilation.SerializeToDeltaStreams( + moduleBeingBuilt, + mappedBaseline, + definitionMap, + changes, + metadataStream, + ilStream, + pdbStream, + updatedMethods, + diagnostics, + testData?.SymWriterFactory, + cancellationToken); } - return new EmitDifferenceResult(success: false, diagnostics: diagnostics.ToReadOnlyAndFree(), baseline: null); + return new EmitDifferenceResult( + success: newBaseline != null, + diagnostics: diagnostics.ToReadOnlyAndFree(), + baseline: newBaseline); } /// diff --git a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs index 6fcc2d4a6d3dde2a31b7d9c8331a95195941772c..0c9e60f9339150abdc60f3495ade1499fca5168a 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/DeterministicTests.cs @@ -32,13 +32,10 @@ private Guid CompiledGuid(string source, string assemblyName) return result; } - private ImmutableArray GetBytesEmitted(string source, Platform platform, bool debug, bool deterministic) + private ImmutableArray EmitDeterministic(string source, Platform platform, bool debug) { var options = (debug ? TestOptions.DebugExe : TestOptions.ReleaseExe).WithPlatform(platform); - if (deterministic) - { - options = options.WithFeatures((new[] { "dEtErmInIstIc" }).AsImmutable()); // expect case-insensitivity - } + options = options.WithFeatures((new[] { "dEtErmInIstIc" }).AsImmutable()); // expect case-insensitivity var compilation = CreateCompilation(source, assemblyName: "DeterminismTest", references: new[] { MscorlibRef }, options: options); @@ -49,19 +46,6 @@ private ImmutableArray GetBytesEmitted(string source, Platform platform, b return compilation.EmitToArray(); } - private class ImmutableByteArrayEqualityComparer : IEqualityComparer> - { - public bool Equals(ImmutableArray x, ImmutableArray y) - { - return x.SequenceEqual(y); - } - - public int GetHashCode(ImmutableArray obj) - { - return obj.GetHashCode(); - } - } - [Fact(Skip = "900646"), WorkItem(900646)] public void Simple() { @@ -80,28 +64,37 @@ public void Simple() } [Fact] - public void CompareAllBytesEmitted() + public void CompareAllBytesEmitted_Release() { var source = @"class Program { public static void Main(string[] args) {} }"; - var comparer = new ImmutableByteArrayEqualityComparer(); + var result1 = EmitDeterministic(source, platform: Platform.AnyCpu32BitPreferred, debug: false); + var result2 = EmitDeterministic(source, platform: Platform.AnyCpu32BitPreferred, debug: false); + AssertEx.Equal(result1, result2); - var result1 = GetBytesEmitted(source, platform: Platform.AnyCpu32BitPreferred, debug: true, deterministic: true); - var result2 = GetBytesEmitted(source, platform: Platform.AnyCpu32BitPreferred, debug: true, deterministic: true); - Assert.Equal(result1, result2, comparer); + var result3 = EmitDeterministic(source, platform: Platform.X64, debug: false); + var result4 = EmitDeterministic(source, platform: Platform.X64, debug: false); + AssertEx.Equal(result3, result4); + } - var result3 = GetBytesEmitted(source, platform: Platform.X64, debug: false, deterministic: true); - var result4 = GetBytesEmitted(source, platform: Platform.X64, debug: false, deterministic: true); - Assert.Equal(result3, result4, comparer); - Assert.NotEqual(result1, result3, comparer); + [Fact(Skip="https://github.com/dotnet/roslyn/issues/926"), WorkItem(926)] + public void CompareAllBytesEmitted_Debug() + { + var source = +@"class Program +{ + public static void Main(string[] args) {} +}"; + var result1 = EmitDeterministic(source, platform: Platform.AnyCpu32BitPreferred, debug: true); + var result2 = EmitDeterministic(source, platform: Platform.AnyCpu32BitPreferred, debug: true); + AssertEx.Equal(result1, result2); - var result5 = GetBytesEmitted(source, platform: Platform.X64, debug: false, deterministic: false); - var result6 = GetBytesEmitted(source, platform: Platform.X64, debug: false, deterministic: false); - Assert.NotEqual(result5, result6, comparer); - Assert.NotEqual(result3, result5, comparer); + var result3 = EmitDeterministic(source, platform: Platform.X64, debug: true); + var result4 = EmitDeterministic(source, platform: Platform.X64, debug: true); + AssertEx.Equal(result3, result4); } [Fact] diff --git a/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs b/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs index f2ec0b0df1acfa9631109826ea08c05bd48e7380..4fc434be30370500b2a94e2fb241007e3e2d9af4 100644 --- a/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs +++ b/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.CompilerEmitStreamProvider.cs @@ -16,46 +16,46 @@ internal abstract partial class CommonCompiler /// private sealed class CompilerEmitStreamProvider : Compilation.EmitStreamProvider, IDisposable { + private static Stream s_uninitialized = Stream.Null; + private readonly CommonCompiler _compiler; - private readonly TouchedFileLogger _touchedFileLogger; private readonly string _filePath; - private Stream _stream; + private readonly bool _streamCreatedByNativePdbWriter; + + private Stream _lazyStream; - internal CompilerEmitStreamProvider(CommonCompiler compiler, TouchedFileLogger touchedFileLogger, string filePath) + internal CompilerEmitStreamProvider(CommonCompiler compiler, string filePath, bool streamCreatedByNativePdbWriter) { _compiler = compiler; - _touchedFileLogger = touchedFileLogger; _filePath = filePath; + _streamCreatedByNativePdbWriter = streamCreatedByNativePdbWriter; + _lazyStream = s_uninitialized; } public void Dispose() { - _stream?.Dispose(); - _stream = null; + if (_lazyStream != s_uninitialized) + { + _lazyStream?.Dispose(); + _lazyStream = s_uninitialized; + } } public override Stream GetStream(DiagnosticBag diagnostics) { - if (_stream == null) + if (_lazyStream == s_uninitialized) { - _stream = OpenFile(_filePath, diagnostics); + _lazyStream = _streamCreatedByNativePdbWriter ? null : OpenFile(_filePath, diagnostics); } - return _stream; + return _lazyStream; } private Stream OpenFile(string filePath, DiagnosticBag diagnostics) { try { - Stream stream = _compiler.FileOpen(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None); - - if (_touchedFileLogger != null) - { - _touchedFileLogger.AddWritten(filePath); - } - - return stream; + return _compiler.FileOpen(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.None); } catch (Exception e) { diff --git a/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.cs index 983cfdce143ee1e72ea93c907c1eecb6e31702aa..ea5ffa27caf72b17162a865ed2bb1fb43a0f8c71 100644 --- a/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Desktop/CommandLine/CommonCompiler.cs @@ -399,27 +399,27 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat string finalPdbFilePath; string finalXmlFilePath; - FileStream xml = null; + FileStream xmlStreamOpt = null; cancellationToken.ThrowIfCancellationRequested(); finalXmlFilePath = Arguments.DocumentationPath; if (finalXmlFilePath != null) { - xml = OpenFile(finalXmlFilePath, consoleOutput, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); - if (xml == null) + xmlStreamOpt = OpenFile(finalXmlFilePath, consoleOutput, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); + if (xmlStreamOpt == null) { return Failed; } - xml.SetLength(0); + xmlStreamOpt.SetLength(0); } cancellationToken.ThrowIfCancellationRequested(); IEnumerable errors; - using (var win32Res = GetWin32Resources(Arguments, compilation, out errors)) - using (xml) + using (var win32ResourceStreamOpt = GetWin32Resources(Arguments, compilation, out errors)) + using (xmlStreamOpt) { if (ReportErrors(errors, consoleOutput, errorLogger)) { @@ -438,25 +438,27 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat WithOutputNameOverride(outputName). WithPdbFilePath(finalPdbFilePath); - var pdbOutputInfo = Arguments.EmitPdb - ? new Cci.PdbOutputInfo(finalPdbFilePath) - : Cci.PdbOutputInfo.None; - - using (var peStreamProvider = new CompilerEmitStreamProvider(this, touchedFilesLogger, finalOutputPath)) + using (var peStreamProvider = new CompilerEmitStreamProvider(this, finalOutputPath, streamCreatedByNativePdbWriter: false)) + using (var pdbStreamProviderOpt = Arguments.EmitPdb ? new CompilerEmitStreamProvider(this, finalOutputPath, streamCreatedByNativePdbWriter: true) : null) { emitResult = compilation.Emit( peStreamProvider, - pdbOutputInfo, - xml, - win32Res, + pdbStreamProviderOpt, + (xmlStreamOpt != null) ? new Compilation.SimpleEmitStreamProvider(xmlStreamOpt) : null, + (win32ResourceStreamOpt != null) ? new Compilation.SimpleEmitStreamProvider(win32ResourceStreamOpt) : null, Arguments.ManifestResources, emitOptions, getAnalyzerDiagnostics, cancellationToken); - if (emitResult.Success && !pdbOutputInfo.IsNone && touchedFilesLogger != null) + if (emitResult.Success && touchedFilesLogger != null) { - touchedFilesLogger.AddWritten(pdbOutputInfo.FileName); + if (pdbStreamProviderOpt != null) + { + touchedFilesLogger.AddWritten(finalPdbFilePath); + } + + touchedFilesLogger.AddWritten(finalOutputPath); } } } diff --git a/src/Compilers/Core/Portable/Compilation.EmitStreamProvider.cs b/src/Compilers/Core/Portable/Compilation.EmitStreamProvider.cs index 71a9c2e556e5734785669a259166216dfed38403..804b027baf73327557216f22e5b83392303737da 100644 --- a/src/Compilers/Core/Portable/Compilation.EmitStreamProvider.cs +++ b/src/Compilers/Core/Portable/Compilation.EmitStreamProvider.cs @@ -35,6 +35,7 @@ internal sealed class SimpleEmitStreamProvider : EmitStreamProvider internal SimpleEmitStreamProvider(Stream stream) { + Debug.Assert(stream != null); _stream = stream; } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index 9e8ba2067932e4caa985c25f455a095023ed5094..181162c204ce0e1e5eabe3cdca906c3cb4c13103 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -51,6 +51,8 @@ public abstract partial class Compilation /// private SmallDictionary _lazyMakeMemberMissingMap; + private Dictionary _lazyFeatures; + internal Compilation( string name, ImmutableArray references, @@ -1369,9 +1371,9 @@ internal void EnsureAnonymousTypeTemplates(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). + /// 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. @@ -1379,19 +1381,19 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) /// To cancel the emit process. internal EmitResult Emit( EmitStreamProvider peStreamProvider, - Cci.PdbOutputInfo pdbOutputInfo, - Stream xmlDocumentationStream = null, - Stream win32Resources = null, - IEnumerable manifestResources = null, - EmitOptions options = null, - Func> getHostDiagnostics = null, - CancellationToken cancellationToken = default(CancellationToken)) + EmitStreamProvider pdbStreamProvider, + EmitStreamProvider xmlDocumentationStreamProvider, + EmitStreamProvider win32ResourcesProvider, + IEnumerable manifestResources, + EmitOptions options, + Func> getHostDiagnostics, + CancellationToken cancellationToken) { return Emit( peStreamProvider, - pdbOutputInfo, - new SimpleEmitStreamProvider(xmlDocumentationStream), - new SimpleEmitStreamProvider(win32Resources), + pdbStreamProvider, + xmlDocumentationStreamProvider, + win32ResourcesProvider, manifestResources, options, testData: null, @@ -1399,6 +1401,34 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) 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 @@ -1481,35 +1511,7 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) 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( - Stream peStream, - Stream pdbStream, - Stream xmlDocumentationStream, - Stream win32Resources, - IEnumerable manifestResources, - EmitOptions options, - CompilationTestData testData, - Func> getHostDiagnostics, - CancellationToken cancellationToken) - { - return Emit( - new SimpleEmitStreamProvider(peStream), - new Cci.PdbOutputInfo(pdbStream), - new SimpleEmitStreamProvider(xmlDocumentationStream), - new SimpleEmitStreamProvider(win32Resources), - manifestResources, - options, - testData, - getHostDiagnostics, - 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. @@ -1517,7 +1519,7 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) /// True if emit succeeded. internal EmitResult Emit( EmitStreamProvider peStreamProvider, - Cci.PdbOutputInfo pdbOutputInfo, + EmitStreamProvider pdbStreamProvider, EmitStreamProvider xmlDocumentationStreamProvider, EmitStreamProvider win32ResourcesStreamProvider, IEnumerable manifestResources, @@ -1568,13 +1570,13 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) return ToEmitResultAndFree(diagnostics, success: false); } - var win32Resources = win32ResourcesStreamProvider.GetStream(diagnostics); - var xmlDocumentationStream = xmlDocumentationStreamProvider.GetStream(diagnostics); + var win32Resources = win32ResourcesStreamProvider?.GetStream(diagnostics); + var xmlDocumentationStream = xmlDocumentationStreamProvider?.GetStream(diagnostics); if (!this.Compile( moduleBeingBuilt, win32Resources, xmlDocumentationStream, - generateDebugInfo: pdbOutputInfo.IsValid, + generateDebugInfo: pdbStreamProvider != null, diagnostics: diagnostics, filterOpt: null, cancellationToken: cancellationToken)) @@ -1583,6 +1585,7 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) } var hostDiagnostics = getHostDiagnostics?.Invoke() ?? ImmutableArray.Empty; + diagnostics.AddRange(hostDiagnostics); if (hostDiagnostics.Any(x => x.Severity == DiagnosticSeverity.Error)) { @@ -1592,7 +1595,7 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) bool success = SerializeToPeStream( moduleBeingBuilt, peStreamProvider, - pdbOutputInfo, + pdbStreamProvider, testData?.SymWriterFactory, diagnostics, metadataOnly: options.EmitMetadataOnly, @@ -1605,11 +1608,11 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su { return new EmitResult(success, diagnostics.ToReadOnlyAndFree()); } - + internal bool SerializeToPeStream( CommonPEModuleBuilder moduleBeingBuilt, EmitStreamProvider peStreamProvider, - Cci.PdbOutputInfo pdbOutputInfo, + EmitStreamProvider pdbStreamProvider, Func testSymWriterFactory, DiagnosticBag diagnostics, bool metadataOnly, @@ -1621,7 +1624,7 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su Stream signingInputStream = null; DiagnosticBag metadataDiagnostics = null; DiagnosticBag pdbBag = null; - Stream pdbOriginalStream = null; + Stream pdbStream = null; Stream pdbTempStream = null; Stream peStream = null; Stream peTempStream = null; @@ -1630,29 +1633,36 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su try { - if (pdbOutputInfo.IsValid) + if (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. - if (pdbOutputInfo.Stream != null && pdbOutputInfo.FileName == null) + Func getNativePdbStream = () => { - pdbOutputInfo = new Cci.PdbOutputInfo(FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"), pdbOutputInfo.Stream); - } + pdbStream = pdbStreamProvider.GetStream(diagnostics); + if (pdbStream == null) + { + return null; + } - // Native PDB writer is able to update an existing stream. - // It checks for length to determine whether the given stream has existing data to be updated, - // or whether it should start writing PDB data from scratch. Thus if not writing to a seekable empty stream , - // let's create an in-memory temp stream for the PDB writer and copy all data to the actual stream at once at the end. - if (pdbOutputInfo.Stream != null && (!pdbOutputInfo.Stream.CanSeek || pdbOutputInfo.Stream.Length != 0)) - { - pdbOriginalStream = pdbOutputInfo.Stream; - pdbTempStream = new MemoryStream(); - pdbOutputInfo = pdbOutputInfo.WithStream(pdbTempStream); - } + var retStream = pdbStream; + + // Native PDB writer is able to update an existing stream. + // It checks for length to determine whether the given stream has existing data to be updated, + // or whether it should start writing PDB data from scratch. Thus if not writing to a seekable empty stream , + // let's create an in-memory temp stream for the PDB writer and copy all data to the actual stream at once at the end. + if (!retStream.CanSeek || retStream.Length != 0) + { + retStream = pdbTempStream = new MemoryStream(); + } + return retStream; + }; + + // 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( - pdbOutputInfo, + getNativePdbStream, + moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"), testSymWriterFactory); } @@ -1719,7 +1729,7 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su nativePdbWriter.WritePdbToOutput(); pdbTempStream.Position = 0; - pdbTempStream.CopyTo(pdbOriginalStream); + pdbTempStream.CopyTo(pdbStream); } } catch (Cci.PdbWritingException ex) @@ -1772,7 +1782,57 @@ private static EmitResult ToEmitResultAndFree(DiagnosticBag diagnostics, bool su return true; } - private Dictionary _lazyFeatures; + 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( + () => pdbStream, + moduleBeingBuilt.EmitOptions.PdbFilePath ?? FileNameUtilities.ChangeExtension(SourceModule.Name, "pdb"), + testSymWriterFactory)) + { + 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); + + 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) { if (_lazyFeatures == null) diff --git a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs b/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs index aaa6eb6ec95c0770e5c1fd7e75aab1437d16adec..d81d122d3f316aa3e8654eb05c31ca997bfcee34 100644 --- a/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs +++ b/src/Compilers/Core/Portable/PEWriter/PdbWriter.cs @@ -29,64 +29,6 @@ internal sealed class PdbWritingException : Exception } } - /// - /// This struct abstracts away the possible values for specifying the output information - /// for a PDB. It is legal to specify a file name, a stream or both. In the case both - /// are specified though the value will be preferred. - /// - /// - /// The file name is still used within the PDB writing code hence is not completely - /// redundant in the face of a value. - /// - internal struct PdbOutputInfo - { - internal static PdbOutputInfo None - { - get { return new PdbOutputInfo(); } - } - - internal readonly string FileName; - internal readonly Stream Stream; - - internal bool IsNone - { - get { return FileName == null && Stream == null; } - } - - internal bool IsValid - { - get { return !IsNone; } - } - - internal PdbOutputInfo(string fileName) - { - Debug.Assert(fileName != null); - FileName = fileName; - Stream = null; - } - - internal PdbOutputInfo(Stream stream) - { - FileName = null; - Stream = stream; - } - - internal PdbOutputInfo(string fileName, Stream stream) - { - Debug.Assert(fileName != null); - Debug.Assert(stream != null && stream.CanWrite); - FileName = fileName; - Stream = stream; - } - - internal PdbOutputInfo WithStream(Stream stream) - { - return FileName != null - ? new PdbOutputInfo(FileName, stream) - : new PdbOutputInfo(stream); - } - } - internal sealed class PdbWriter : IDisposable { internal const uint HiddenLocalAttributesValue = 1u; @@ -94,7 +36,8 @@ internal sealed class PdbWriter : IDisposable private static Type s_lazyCorSymWriterSxSType; - private readonly PdbOutputInfo _pdbOutputInfo; + private readonly Func _streamProvider; + private readonly string _fileName; private readonly Func _symWriterFactory; private MetadataWriter _metadataWriter; private ISymUnmanagedWriter2 _symWriter; @@ -111,10 +54,11 @@ internal sealed class PdbWriter : IDisposable private uint[] _sequencePointEndLines; private uint[] _sequencePointEndColumns; - public PdbWriter(PdbOutputInfo pdbOutputInfo, Func symWriterFactory = null) + public PdbWriter(Func streamProvider, string fileName, Func symWriterFactory = null) { - Debug.Assert(pdbOutputInfo.IsValid); - _pdbOutputInfo = pdbOutputInfo; + Debug.Assert(streamProvider != null); + _streamProvider = streamProvider; + _fileName = fileName; _symWriterFactory = symWriterFactory; CreateSequencePointBuffers(capacity: 64); } @@ -131,8 +75,8 @@ public void Dispose() } /// - /// Close the PDB writer and write the contents to the location specified by the - /// value. If a file name was specified this is the method which will cause it to be created. + /// Close the PDB writer and write the contents to the stream provided by + /// or file name specified by value if no stream has been provided. /// public void WritePdbToOutput() { @@ -615,11 +559,14 @@ private static Type GetCorSymWriterSxSType() public void SetMetadataEmitter(MetadataWriter metadataWriter) { + Stream streamOpt = _streamProvider(); + try { var instance = (ISymUnmanagedWriter2)(_symWriterFactory != null ? _symWriterFactory() : Activator.CreateInstance(GetCorSymWriterSxSType())); - var comStream = _pdbOutputInfo.Stream != null ? new ComStreamWrapper(_pdbOutputInfo.Stream) : null; - instance.Initialize(new PdbMetadataWrapper(metadataWriter), _pdbOutputInfo.FileName, comStream, fullBuild: true); + var comStream = (streamOpt != null) ? new ComStreamWrapper(streamOpt) : null; + + instance.Initialize(new PdbMetadataWrapper(metadataWriter), _fileName, comStream, fullBuild: true); _metadataWriter = metadataWriter; _symWriter = instance; diff --git a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb index 26b00bb4aa74623ff61598dc58041f3a4dfd99d7..e27cd190bf8b3bce8b9aeb2a4f687bde8d9ffb54 100644 --- a/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb +++ b/src/Compilers/VisualBasic/Portable/Emit/EditAndContinue/EmitHelpers.vb @@ -55,11 +55,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit testData.Module = moduleBeingBuilt End If - baseline = moduleBeingBuilt.PreviousGeneration - Dim definitionMap = moduleBeingBuilt.PreviousDefinitions Dim changes = moduleBeingBuilt.Changes + Dim newBaseline As EmitBaseline = Nothing + If compilation.Compile(moduleBeingBuilt, win32Resources:=Nothing, xmlDocStream:=Nothing, @@ -71,43 +71,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Emit ' Map the definitions from the previous compilation to the current compilation. ' This must be done after compiling above since synthesized definitions ' (generated when compiling method bodies) may be required. - baseline = MapToCompilation(compilation, moduleBeingBuilt) - - Dim pdbOutputInfo = New Cci.PdbOutputInfo(pdbName, pdbStream) - Using pdbWriter = New Cci.PdbWriter(pdbOutputInfo, If(testData IsNot Nothing, testData.SymWriterFactory, Nothing)) - Dim context = New EmitContext(moduleBeingBuilt, Nothing, diagnostics) - Dim encId = Guid.NewGuid() - - Try - Dim writer = New DeltaMetadataWriter( - context, - compilation.MessageProvider, - baseline, - encId, - definitionMap, - changes, - cancellationToken) - - Dim metadataSizes As Cci.MetadataSizes = Nothing - writer.WriteMetadataAndIL(pdbWriter, metadataStream, ilStream, metadataSizes) - writer.GetMethodTokens(updatedMethods) - - Dim hasErrors = diagnostics.HasAnyErrors() - - Return New EmitDifferenceResult( - success:=Not hasErrors, - diagnostics:=diagnostics.ToReadOnlyAndFree(), - baseline:=If(hasErrors, Nothing, writer.GetDelta(baseline, compilation, encId, metadataSizes))) - - Catch e As Cci.PdbWritingException - diagnostics.Add(ERRID.ERR_PDBWritingFailed, Location.None, e.Message) - Catch e As PermissionSetFileReadException - diagnostics.Add(ERRID.ERR_PermissionSetAttributeFileReadError, Location.None, e.FileName, e.PropertyName, e.Message) - End Try - End Using + Dim mappedBaseline = MapToCompilation(compilation, moduleBeingBuilt) + + newBaseline = compilation.SerializeToDeltaStreams( + moduleBeingBuilt, + mappedBaseline, + definitionMap, + changes, + metadataStream, + ilStream, + pdbStream, + updatedMethods, + diagnostics, + testData?.SymWriterFactory, + cancellationToken) End If - Return New EmitDifferenceResult(success:=False, diagnostics:=diagnostics.ToReadOnlyAndFree(), baseline:=Nothing) + Return New EmitDifferenceResult( + success:=newBaseline IsNot Nothing, + diagnostics:=diagnostics.ToReadOnlyAndFree(), + baseline:=newBaseline) End Function Friend Function MapToCompilation( diff --git a/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb b/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb index 036e20bdb6e3f2afcd329d3eef16dfc6007ac3af..88957341d156084af0cd27396db8d7d6deb318b5 100644 --- a/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/Emit/DeterministicTests.vb @@ -1,20 +1,18 @@ -Imports Microsoft.CodeAnalysis.Test.Utilities -Imports System -Imports System.Collections.Generic +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + Imports System.Collections.Immutable -Imports System.Linq Imports System.Threading -Imports Xunit +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Roslyn.Test.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Emit - Public Class DeterministicTests : Inherits BasicTestBase + Public Class DeterministicTests + Inherits BasicTestBase - Private Function GetBytesEmitted(source As String, platform As Platform, debug As Boolean, deterministic As Boolean) As ImmutableArray(Of Byte) + Private Function GetBytesEmitted(source As String, platform As Platform, debug As Boolean) As ImmutableArray(Of Byte) Dim options = If(debug, TestOptions.DebugExe, TestOptions.ReleaseExe).WithPlatform(platform) - If deterministic Then - options = options.WithFeatures({"dEtErmInIstIc"}.AsImmutable()) ' expect case-insensitivity - End If + options = options.WithFeatures({"dEtErmInIstIc"}.AsImmutable()) ' expect case-insensitivity Dim compilation = CreateCompilationWithMscorlib({source}, assemblyName:="DeterminismTest", compOptions:=options) @@ -25,40 +23,36 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UnitTests.Emit Return compilation.EmitToArray() End Function - Private Class ImmutableByteArrayEqualityComparer : Implements IEqualityComparer(Of ImmutableArray(Of Byte)) - Public Overloads Function Equals(x As ImmutableArray(Of Byte), y As ImmutableArray(Of Byte)) As Boolean Implements IEqualityComparer(Of ImmutableArray(Of Byte)).Equals - Return x.SequenceEqual(y) - End Function - - Public Overloads Function GetHashCode(obj As ImmutableArray(Of Byte)) As Integer Implements IEqualityComparer(Of ImmutableArray(Of Byte)).GetHashCode - Return obj.GetHashCode() - End Function - End Class - - Public Sub CompareAllBytesEmitted() + Public Sub CompareAllBytesEmitted_Release() Dim source = "Class Program Shared Sub Main() End Sub End Class" - Dim comparer = New ImmutableByteArrayEqualityComparer() + Dim result1 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=False) + Dim result2 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=False) + AssertEx.Equal(result1, result2) - Dim result1 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=True, deterministic:=True) - Dim result2 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=True, deterministic:=True) - Assert.Equal(result1, result2, comparer) + Dim result3 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False) + Dim result4 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False) + AssertEx.Equal(result3, result4) + End Sub - Dim result3 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False, deterministic:=True) - Dim result4 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False, deterministic:=True) - Assert.Equal(result3, result4, comparer) - Assert.NotEqual(result1, result3, comparer) + + Public Sub CompareAllBytesEmitted_Debug() + Dim source = +"Class Program + Shared Sub Main() + End Sub +End Class" + Dim result1 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=True) + Dim result2 = GetBytesEmitted(source, platform:=Platform.AnyCpu32BitPreferred, debug:=True) + AssertEx.Equal(result1, result2) - Dim result5 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False, deterministic:=False) - Dim result6 = GetBytesEmitted(source, platform:=Platform.X64, debug:=False, deterministic:=False) - Assert.NotEqual(result5, result6, comparer) - Assert.NotEqual(result3, result5, comparer) + Dim result3 = GetBytesEmitted(source, platform:=Platform.X64, debug:=True) + Dim result4 = GetBytesEmitted(source, platform:=Platform.X64, debug:=True) + AssertEx.Equal(result3, result4) End Sub - End Class - End Namespace diff --git a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs index ebc3534e832a08e00c92c5dfe5cc65d5c7cb560c..73220503af0e6602e6de7c6045f0d3586363c480 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/AbstractMetadataAsSourceTests.cs @@ -35,7 +35,7 @@ internal static void TestNotReusedOnAssemblyDiffers(string projectLanguage) var metadataProject = context.CurrentSolution .AddProject(projectId, "Metadata", "Metadata", LanguageNames.CSharp).GetProject(projectId) .AddMetadataReference(TestReferences.NetFx.v4_0_30319.mscorlib) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release)); var references = new List(); diff --git a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs index 9cceb59a886fdbd67dae0eb9c49adbc432643494..a35c382891231d555de215d63c05a7eafda1bfcd 100644 --- a/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs +++ b/src/EditorFeatures/Test/MetadataAsSource/MetadataAsSourceTests.cs @@ -637,16 +637,16 @@ public void TestThrowsOnGenerateNamespace() using (var context = new TestContext()) { Assert.Throws(() => - { - try - { - context.GenerateSource(namespaceSymbol); - } - catch (AggregateException ae) - { - throw ae.InnerException; - } - }); + { + try + { + context.GenerateSource(namespaceSymbol); + } + catch (AggregateException ae) + { + throw ae.InnerException; + } + }); } } diff --git a/src/Test/Utilities/CompilationExtensions.cs b/src/Test/Utilities/CompilationExtensions.cs index baa5360f6a95e7f937a2cdfed8f1686289156ee4..3bcd43fc55c7bcb6d3076f2eebbbf337c6150116 100644 --- a/src/Test/Utilities/CompilationExtensions.cs +++ b/src/Test/Utilities/CompilationExtensions.cs @@ -22,10 +22,11 @@ public static class CompilationExtensions DiagnosticDescription[] expectedWarnings = null) { var stream = new MemoryStream(); + MemoryStream pdbStream = (compilation.Options.OptimizationLevel == OptimizationLevel.Debug) ? new MemoryStream() : null; var emitResult = compilation.Emit( peStream: stream, - pdbStream: null, + pdbStream: pdbStream, xmlDocumentationStream: null, win32Resources: null, manifestResources: null,