Exemplo n.º 1
0
        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 { })