public static unsafe CompilationDiff Create( FileInfo originalBinaryPath, CompilationOptionsReader optionsReader, Compilation producedCompilation, IMethodSymbol?debugEntryPoint, ILogger logger, Options options) { using var rebuildPeStream = new MemoryStream(); // By default the Roslyn command line adds a resource that we need to replicate here. using var win32ResourceStream = producedCompilation.CreateDefaultWin32Resources( versionResource: true, noManifest: producedCompilation.Options.OutputKind == OutputKind.DynamicallyLinkedLibrary, manifestContents: null, iconInIcoFormat: null); var sourceLink = optionsReader.GetSourceLinkUTF8(); var emitResult = producedCompilation.Emit( peStream: rebuildPeStream, pdbStream: null, xmlDocumentationStream: null, win32Resources: win32ResourceStream, manifestResources: optionsReader.GetManifestResources(), options: new EmitOptions( debugInformationFormat: DebugInformationFormat.Embedded, highEntropyVirtualAddressSpace: true), debugEntryPoint: debugEntryPoint, metadataPEStream: null, pdbOptionsBlobReader: optionsReader.GetMetadataCompilationOptionsBlobReader(), sourceLinkStream: sourceLink != null ? new MemoryStream(sourceLink) : null, embeddedTexts: producedCompilation.SyntaxTrees .Select(st => (path: st.FilePath, text: st.GetText())) .Where(pair => pair.text.CanBeEmbedded) .Select(pair => EmbeddedText.FromSource(pair.path, pair.text)), cancellationToken: CancellationToken.None); if (!emitResult.Success) { using var diagsScope = logger.BeginScope($"Diagnostics"); foreach (var diag in emitResult.Diagnostics) { logger.LogError(diag.ToString()); } return(new CompilationDiff(emitResult.Diagnostics, originalBinaryPath.FullName)); } else { var originalBytes = File.ReadAllBytes(originalBinaryPath.FullName); var rebuildBytes = rebuildPeStream.ToArray(); var bytesEqual = originalBytes.SequenceEqual(rebuildBytes); if (!bytesEqual) { logger.LogError($"Rebuild of {originalBinaryPath.Name} was not equivalent to the original."); if (!options.Debug) { logger.LogInformation("Pass the --debug argument and re-run to write the visualization of the original and rebuild to disk."); } else { logger.LogInformation("Creating a diff..."); var debugPath = options.DebugPath; logger.LogInformation($@"Writing diffs to ""{Path.GetFullPath(debugPath)}"""); var assemblyName = Path.GetFileNameWithoutExtension(originalBinaryPath.Name); var assemblyDebugPath = Path.Combine(debugPath, assemblyName); var originalPath = Path.Combine(assemblyDebugPath, "original"); var rebuildPath = Path.Combine(assemblyDebugPath, "rebuild"); var sourcesPath = Path.Combine(assemblyDebugPath, "sources"); Directory.CreateDirectory(originalPath); Directory.CreateDirectory(rebuildPath); Directory.CreateDirectory(sourcesPath); // TODO: output source files should include the entire relative path instead of just the file name. foreach (var tree in producedCompilation.SyntaxTrees) { var sourceFilePath = Path.Combine(sourcesPath, Path.GetFileName(tree.FilePath)); using var file = File.OpenWrite(sourceFilePath); var writer = new StreamWriter(file); tree.GetText().Write(writer); writer.Flush(); } var originalAssemblyPath = Path.Combine(originalPath, originalBinaryPath.Name); File.WriteAllBytes(originalAssemblyPath, originalBytes); var rebuildAssemblyPath = Path.Combine(rebuildPath, originalBinaryPath.Name); File.WriteAllBytes(rebuildAssemblyPath, rebuildBytes); var originalPeMdvPath = Path.Combine(originalPath, assemblyName + ".pe.mdv"); var originalPdbMdvPath = Path.Combine(originalPath, assemblyName + ".pdb.mdv"); writeVisualization(originalPeMdvPath, optionsReader.PeReader.GetMetadataReader()); writeVisualization(originalPdbMdvPath, optionsReader.PdbReader); var rebuildPeMdvPath = Path.Combine(rebuildPath, assemblyName + ".pe.mdv"); var rebuildPdbMdvPath = Path.Combine(rebuildPath, assemblyName + ".pdb.mdv"); fixed(byte *ptr = rebuildBytes) { using var rebuildPeReader = new PEReader(ptr, rebuildBytes.Length); writeVisualization(rebuildPeMdvPath, rebuildPeReader.GetMetadataReader()); if (rebuildPeReader.TryOpenAssociatedPortablePdb( rebuildAssemblyPath, path => File.Exists(path) ? File.OpenRead(path) : null, out var provider, out _) && provider is { })